Python - extending properties like you'd extend a function
如何扩展python属性?
子类可以通过在重载版本中调用超类的函数,然后对结果进行操作来扩展该类的函数。下面是我说"扩展函数"时的一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # Extending a function (a tongue-in-cheek example) class NormalMath(object): def __init__(self, number): self.number = number def add_pi(self): n = self.number return n + 3.1415 class NewMath(object): def add_pi(self): # NewMath doesn't know how NormalMath added pi (and shouldn't need to). # It just uses the result. n = NormalMath.add_pi(self) # In NewMath, fractions are considered too hard for our users. # We therefore silently convert them to integers. return int(n) |
除了使用属性修饰器的函数之外,是否还有类似于扩展函数的操作?
我想在得到一个昂贵的计算属性后立即进行一些额外的计算。我需要保持属性的访问延迟。我不希望用户必须调用一个特殊的例程来进行计算。基本上,我不希望用户知道计算是在第一时间进行的。但是,该属性必须保持为属性,因为我有需要支持的遗留代码。
也许这是装饰师的工作?如果我没有弄错,decorator是一个包装另一个函数的函数,我希望用更多的计算来包装一个属性,然后将它再次呈现为一个属性,这看起来像是一个类似的想法…但我不太明白。
我的具体问题我有一个基类日志文件,它的构造attribute.dataframe代价很高。我已经将它实现为一个属性(使用属性修饰器),所以在我请求数据帧之前,它不会真正解析日志文件。到目前为止,效果很好。我可以构建一组(100+个)日志文件对象,并使用更便宜的方法来过滤和选择要解析的重要对象。每当我一次又一次地使用相同的日志文件时,我只需要在第一次访问数据帧时解析它。
现在,我需要编写一个日志文件子类sensorlog,它向基类的dataframe属性添加了一些额外的列,但是我还不能很好地找到调用超级类的dataframe构造例程的语法(不知道它们的内部工作),然后对生成的dataframe进行操作,然后缓存/返回它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | # Base Class - rules for parsing/interacting with data. class LogFile(object): def __init__(self, file_name): # file name to find the log file self.file_name = file_name # non-public variable to cache results of parse() self._dataframe = None def parse(self): with open(self.file_name) as infile: ... ... # Complex rules to interpret the file ... ... self._dataframe = pandas.DataFrame(stuff) @property def dataframe(self): """ Returns the dataframe; parses file if necessary. This works great! """ if self._dataframe is None: self.parse() return self._dataframe @dataframe.setter def dataframe(self,value): self._dataframe = value # Sub class - adds more information to data, but does't parse # must preserve established .dataframe interface class SensorLog(LogFile): def __init__(self, file_name): # Call the super's constructor LogFile.__init__(self, file_name) # SensorLog doesn't actually know about (and doesn't rely on) the ._dataframe cache, so it overrides it just in case. self._dataframe = None # THIS IS THE PART I CAN'T FIGURE OUT # Here's my best guess, but it doesn't quite work: @property def dataframe(self): # use parent class's getter, invoking the hidden parse function and any other operations LogFile might do. self._dataframe = LogFile.dataframe.getter() # Add additional calculated columns self._dataframe['extra_stuff'] = 'hello world!' return self._dataframe @dataframe.setter def dataframe(self, value): self._dataframe = value |
号
现在,当这些类在交互会话中使用时,用户应该能够以相同的方式与之交互。
1 2 3 4 5 6 | >>> log = LogFile('data.csv') >>> print log.dataframe #### DataFrame with 10 columns goes here #### >>> sensor = SensorLog('data.csv') >>> print sensor.dataframe #### DataFrame with 11 columns goes here #### |
我有很多现有的代码,它们采用一个日志文件实例,它提供.dataframe属性,并做一些有趣的事情(主要是绘图)。我希望SensorLog实例具有相同的接口,这样它们就可以使用相同的代码。是否可以扩展超级类的数据帧getter以利用现有的例程?怎样?还是我最好换一种方式?
感谢你阅读那面巨大的文本墙。你是一个互联网超级英雄,亲爱的读者。有什么想法吗?
您应该调用超类属性,而不是通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | class A(object): def __init__(self): self.__prop = None @property def prop(self): return self.__prop @prop.setter def prop(self, value): self.__prop = value class B(A): def __init__(self): super(B, self).__init__() @property def prop(self): value = A.prop.fget(self) value['extra'] = 'stuff' return value @prop.setter def prop(self, value): A.prop.fset(self, value) |
使用它:
1 2 3 | b = B() b.prop = dict((('a', 1), ('b', 2))) print(b.prop) |
。
输出:
1 | {'a': 1, 'b': 2, 'extra': 'stuff'} |
。
我通常建议在setter中放置副作用,而不是getter,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class A(object): def __init__(self): self.__prop = None @property def prop(self): return self.__prop @prop.setter def prop(self, value): self.__prop = value class B(A): def __init__(self): super(B, self).__init__() @property def prop(self): return A.prop.fget(self) @prop.setter def prop(self, value): value['extra'] = 'stuff' A.prop.fset(self, value) |
在getter中进行昂贵的操作通常也是可以避免的(例如解析方法)。
您可以考虑:
1/从
2/使
3/将
4/如果您和其他人认为熊猫会被接受为有用的扩展,请考虑更多地利用熊猫已有的方法,或者可能扩展熊猫以提供缺失的方法。
我个人认为,你会发现选项3或4的好处是最强大的。
如果我正确理解您要做的是从子实例调用父级的方法。通常的方法是使用
我以你的舌头为例,修改为使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class NormalMath(object): def __init__(self, number): self.number = number def add_pi(self): n = self.number return n + 3.1415 class NewMath(NormalMath): def add_pi(self): # this will call NormalMath's add_pi with normal_maths_pi_plus_num = super(NewMath, self).add_pi() return int(normal_maths_pi_plus_num) |
在日志示例中,不要调用:
1 | self._dataframe = LogFile.dataframe.getter() |
号
你应该打电话给:
1 | self._dataframe = super(SensorLog, self).dataframe |
你可以在这里读到更多关于超级的东西
编辑:即使我给你的例子是关于方法的,对@properties做同样的事情也不成问题。
问题是你缺少进入父类的自我。如果您的父级是单例的,那么@staticmethod应该可以工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class X(): x=1 @staticmethod def getx(): return X.x class Y(X): y=2 def getyx(self): return X.getx()+self.y wx = Y() wx.getyx() 3 |
。