Привязка метода без постоянного изменения объекта - PullRequest
0 голосов
/ 20 октября 2019

В настоящее время я учусь писать классы Python и цепочки методов. По сути, мне нужен класс python (2.7), который хранит мои данные и имеет (цепочечные) методы, которые позволяют мне фильтровать данные, не изменяя мои исходные данные. Я провел поиск в Google, и кажется, что мой ответ может иметь отношение к return self, но я не уверен, как реализовать это так, чтобы методы не изменяли мои исходные данные.

Допустим, яхранить данные в файле Excel с именем file следующим образом:

+--------+-----+-------+
| Person | Sex | Score |
+--------+-----+-------+
| A      | M   |    10 |
| B      | F   |     9 |
| C      | M   |     8 |
| D      | F   |     7 |
+--------+-----+-------+

Я хотел бы написать класс с именем MyData, чтобы я мог выполнять некоторые базовые вызовы и фильтрацию данных.

Это то, что я получил до сих пор

class MyData:
    def __init__ (self, file):
        import pandas as pd
        self.data = pd.read_excel (file)
        self.Person = self.data['Person']
        self.Sex = self.data['Sex']
        self.Score = self.data['Score']

    def male_only(self):
        self.data = self.data[self.Sex=="M"]
        self.Person = self.Person[self.Sex=="M"]
        self.Score = self.Score[self.Sex=="M"]
        self.Sex = self.Sex[self.Sex=="M"]
        return self

    def female_only(self):
        self.data = self.data[self.Sex=="F"]
        self.Person = self.Person[self.Sex=="F"]
        self.Score = self.Score[self.Sex=="F"]
        self.Sex = self.Sex[self.Sex=="F"]
        return self

Кажется, это работает, но, к сожалению, мои исходные данные постоянно видоизменяются с помощью этого кода. Например:

Data = MyData(file)
Data.data
>>> Data.data
  Person Sex  Score
0      A   M     10
1      B   F      9
2      C   M      8
3      D   F      7

Data.male_only().data
>>> Data.male_only().data
  Person Sex  Score
0      A   M     10
2      C   M      8

Data.data
>>> Data.data
  Person Sex  Score
0      A   M     10
2      C   M      8

Мне бы хотелось, чтобы класс возвращал те же ответы для Data.male_only().Person и Data.Person.male_only() или для Data.male_only().data и Data.data.male_only() без постоянного изменения Data.data или Data.Person.

Ответы [ 3 ]

0 голосов
/ 20 октября 2019

Я хочу немного уточнить ответ @ Деми-Луне. Я не думаю, что есть способ создать экземпляр MyData, изменить его и вернуть его из методов цепочки. Причина, по которой все это работает в первую очередь, заключается в том, что все ваши цепочечные методы принадлежат одному и тому же классу и возвращают экземпляр этого класса.

Например, str.swapcase, str.zfill и str.replace являются частью str, и все они возвращают str с.

>>> string = "Hello World"
>>> string.swapcase().zfill(16).replace("L", "T")
'00000hETTO wORTD'
>>> string
'Hello World'
>>> 

То, что вы пытаетесь сделать (Data.Person.male_only()), нарушает этот шаблон, так какТеперь подразумевается, что метод male_only не является частью класса MyData, а является методом, принадлежащим объекту Person. Что такое self.Person или self.data["Person"]? Я не очень знаком с пандами. Это строка? Список строк? В любом случае, что бы вы ни пытались достичь, в основном вы добавите новый метод с именем male_only в класс этого типа.

0 голосов
/ 20 октября 2019

Я согласен с @ Demi-Lune.

Я изменил код OP, чтобы методы male_only() и female_only() всегда возвращали копию принадлежащего ему объекта. И я изменил __init__() метод, потому что я думаю, что вы не хотите вызывать pd.read_csv() метод каждый раз, когда создаете этот новый объект. Так что male_only() и female_only() метод всегда возвращают новый объект, это не повлияет на другие объекты.

import pandas as pd

# Added for creating file on memory.
import io
csv = '''Person,Sex,Score
p1,M,1
p2,M,2
p3,M,3
p4,F,4
p5,F,5
p6,F,6'''
file = io.StringIO(csv)

class MyData:
    def __init__ (self, file=None, data=None):
        import pandas as pd
        if file:
            self.data = pd.read_csv(file)
        else:
            self.data = data
        self.Person = self.data['Person']
        self.Sex = self.data['Sex']
        self.Score = self.data['Score']

    def copy_d(self):
        return MyData(data=self.data.copy())

    def male_only(self):
        d = self.copy_d()
        d.data = self.data[self.Sex=="M"]
        d.Person = self.Person[self.Sex=="M"]
        d.Score = self.Score[self.Sex=="M"]
        d.Sex = self.Sex[self.Sex=="M"]
        return d

    def female_only(self):
        d = self.copy_d()
        d.data = self.data[self.Sex=="F"]
        d.Person = self.Person[self.Sex=="F"]
        d.Score = self.Score[self.Sex=="F"]
        d.Sex = self.Sex[self.Sex=="F"]
        return d

d = MyData(file)
print(d.female_only().data)
#   Person Sex  Score
# 3     p4   F      4
# 4     p5   F      5
# 5     p6   F      6

print(d.male_only().data)
#   Person Sex  Score
# 0     p1   M      1
# 1     p2   M      2
# 2     p3   M      3

print(d.data)
#   Person Sex  Score
# 0     p1   M      1
# 1     p2   M      2
# 2     p3   M      3
# 3     p4   F      4
# 4     p5   F      5
# 5     p6   F      6

Но если вы просто используете pandas.DataFrame, другой подход просто использует голый pandas.DataFrame,Во-первых, в большинстве случаев объект pandas.DataFrame уже имеет имя свойства, равное имени столбца. Так что на самом деле вам не нужно определять свойства, такие как Person, Sex, Score, потому что они уже существуют в объекте DataFrame.

т.е.:

import pandas as pd
import numpy as np
df = pd.DataFrame(np.eye(3,3), columns=['Person', 'Sex', 'Score'])

# `df` already has these properteis.
df.Person
df.Sex
df.Score
# In [986]: df.Person
# Out[986]: 
# 0    1.0
# 1    0.0
# 2    0.0
# Name: Person, dtype: float64

# In [987]: df.Sex
# Out[987]: 
# 0    0.0
# 1    1.0
# 2    0.0
# Name: Sex, dtype: float64

# In [988]: df.Score
# Out[988]: 
# 0    0.0
# 1    0.0
# 2    1.0
# Name: Score, dtype: float64

Так, ваши male_only() и female_only() методы написаны следующим образом.

import pandas as pd

# Added for creating file on memory.
import io
csv = '''Person,Sex,Score
p1,M,1
p2,M,2
p3,M,3
p4,F,4
p5,F,5
p6,F,6'''
file = io.StringIO(csv)

def male_only(df):
    return df[df.Sex=='M']

def female_only(df):
    return df[df.Sex=='F']

df = pd.read_csv(file)
male_only(df)
# In [1034]: male_only(df)
# Out[1037]: 
#   Person Sex  Score
# 0     p1   M      1
# 1     p2   M      2
# 2     p3   M      3

female_only(df)
# In [1038]: female_only(df)
# Out[1041]: 
#   Person Sex  Score
# 3     p4   F      4
# 4     p5   F      5
# 5     p6   F      6

Надеюсь, это поможет вам.

0 голосов
/ 20 октября 2019

Вы явно изменяете self.data в 1-й строке, когда пишете self.data = .... Вы можете вернуть новый экземпляр Data:

    def male_only(self):
        newdata = MyData()
        newdata.data = self.data[self.Sex=="M"]
        newdata.Person = self.Person[self.Sex=="M"]
        newdata.Score = self.Score[self.Sex=="M"]
        newdata.Sex = self.Sex[self.Sex=="M"]
        return newdata

После ваших комментариев, вот предложение фильтрационного решения: там были бы функции для активации некоторых флагов / фильтров, тогда вам нужно было бы написать функции дляполучить атрибуты:

# self.filters should be initialized to [] in __init__
def male_only(self):
    self.filters.append('male_only')
def person(self):
    if "male_only" in self.filters:
        return self.Person[self.Sex=="M"]
    else: 
        return self.Person

Чтобы увидеть, может ли это пойти куда-то, вы должны действительно завершить тест-кейс (ы), чтобы помочь вам исправить ваши идеи (всегда полезно сначала написать тест-кейс)затем классы).

...