如何扩展python属性?
子类可以通过在重载版本中调用它来扩展超类的函数,然后对结果进行操作。这是我说“扩展函数”时的一个例子:
# 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)
扩展函数是否有类似的操作,但是对于使用属性装饰器的函数?
我想在获得昂贵的计算属性后立即进行一些额外的计算。我需要保持属性的访问权限。我不希望用户必须调用特殊例程来进行计算。基本上,我不希望用户知道首先进行的计算。但是,该属性必须保留为属性,因为我需要支持遗留代码。
也许这是装饰员的工作?如果我没有弄错,装饰器是一个包装另一个函数的函数,我想用一些更多的计算包装一个属性,然后再将它作为属性呈现,这看起来像是一个类似的想法......但是我无法弄明白。
我有一个基类 LogFile ,其中包含昂贵的构建属性 .dataframe 。我已经将它实现为属性(使用属性装饰器),因此在我要求数据帧之前它不会实际解析日志文件。到目前为止,它运作良好。我可以构造一堆(100+)LogFile对象,并使用更便宜的方法来过滤并仅选择要解析的重要方法。每当我反复使用相同的LogFile时,我只需在第一次访问数据帧时解析它。
现在我需要编写一个LogFile子类 SensorLog ,它会为基类的dataframe属性添加一些额外的列,但是我无法弄清楚调用超类的数据帧结构的语法例程(不知道它们的内部工作情况),然后对结果数据帧进行操作,然后缓存/返回它。
# 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
现在,当在交互式会话中使用这些类时,用户应该能够以相同的方式进行交互。
>>> 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 ####
我有很多现有代码需要一个 LogFile 实例,它提供了一个 .dataframe 属性和一些有趣的东西(主要是绘图)。我希望 SensorLog 实例提供相同的接口,以便它们可以使用相同的代码。是否有可能扩展超类的数据帧getter以利用现有的例程?怎么样?或者我最好以不同的方式做这件事?
感谢您阅读那段巨大的文字。亲爱的读者,你是互联网超级英雄。有什么想法吗?
答案 0 :(得分:10)
您应该调用超类属性,而不是通过self._dataframe
绕过它们。这是一个通用的例子:
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)
使用它:
b = B()
b.prop = dict((('a', 1), ('b', 2)))
print(b.prop)
输出:
{'a': 1, 'b': 2, 'extra': 'stuff'}
我一般建议将副作用放在setter而不是getter中,如下所示:
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 :(得分:1)
如果我理解你想要做的是从子实例调用父方法。通常的方法是使用内置的super
。
我采取了你的舌头示例并修改它以使用super
来向您展示:
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)
在您的日志示例中,而不是调用:
self._dataframe = LogFile.dataframe.getter()
你应该致电:
self._dataframe = super(SensorLog, self).dataframe
您可以阅读有关超级here
的更多信息编辑:即使我给你的例子处理方法,对@properties做同样的事也不应该是个问题。
答案 2 :(得分:1)
您有一些可能性需要考虑:
1 /从logfile
继承并覆盖派生传感器类中的parse
。应该可以修改适用于dataframe
的方法,无论dataframe
拥有多少成员,都可以使用 pandas ,其中很多都已完成对你而言。
2 /使sensor
成为logfile
的实例,然后提供自己的解析方法。
3 /概括parse
,可能还有一些其他方法,可以使用数据描述符列表,也可以使用类初始化程序中设置的方法/规则字典,或者通过方法设置。
4 /看看是否更多地使用pandas中已有的方法,或者可能扩展pandas以提供缺少的方法,如果你和其他人认为它们将被接受为pandas作为有用的扩展。
我个人认为你会发现选项3或4的好处是最强大的。
答案 3 :(得分:0)
问题在于你错过了进入父类的自我。如果你的父母是单身人士,那么@staticmethod就可以了。
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