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

时间:2014-02-18 04:15:03

标签: python inheritance properties extends

问题

如何扩展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以利用现有的例程?怎么样?或者我最好以不同的方式做这件事?

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

4 个答案:

答案 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