Python - 像扩展函数一样扩展属性

Mat*_*eld 10 python inheritance extends properties

你怎么能扩展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)
Run Code Online (Sandbox Code Playgroud)

是否有类似于扩展函数的操作,但对于使用属性装饰器的函数?

我想在获得昂贵的计算属性后立即进行一些额外的计算.我需要保持属性的访问权限.我不希望用户必须调用特殊例程来进行计算.基本上,我不希望用户知道首先进行的计算.但是,该属性必须保留属性,因为我需要支持遗留代码.

也许这是装饰师的工作?如果我没有弄错,decorator是一个包装另一个函数的函数,我想用一些更多的计算来包装一个属性,然后再将它作为一个属性呈现,这看起来像一个类似的想法......但是我无法弄明白.

我的具体问题

我有一个基类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
Run Code Online (Sandbox Code Playgroud)

现在,当在交互式会话中使用这些类时,用户应该能够以相同的方式进行交互.

>>> 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 ####
Run Code Online (Sandbox Code Playgroud)

我有很多现有的代码,它采用一个LogFile实例,它提供了一个.dataframe属性和一些有趣的东西(主要是绘图).我希望让SensorLog实例呈现相同的接口,以便它们可以使用相同的代码.是否有可能扩展超类的数据帧getter以利用现有的例程?怎么样?或者我最好以不同的方式做这件事?

感谢您阅读那段巨大的文字.亲爱的读者,你是互联网超级英雄.有什么想法吗?

Dan*_*ite 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)
Run Code Online (Sandbox Code Playgroud)

并使用它:

b = B()
b.prop = dict((('a', 1), ('b', 2)))
print(b.prop)
Run Code Online (Sandbox Code Playgroud)

输出:

{'a': 1, 'b': 2, 'extra': 'stuff'}
Run Code Online (Sandbox Code Playgroud)

我通常建议将副作用放在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)
Run Code Online (Sandbox Code Playgroud)

通常也要避免在getter中进行昂贵的操作(例如你的解析方法).