从python子类中的属性中删除属性(getter / setter)

时间:2019-01-09 15:32:46

标签: python properties

这可能在其他地方得到了回答,但是我想知道是否有什么方法可以删除子类中用@property装饰的属性/方法。

示例:

from datetime import datetime

class A():
    def __init__(self, num):
        self._num = num

    @property
    def id(self):
        return self._num * datetime.now().timestamp()

class B(A):
    def __init__(self, id, num):
        super().__init__(num)
        self.id = id

如果您尝试创建类B的实例,则以上代码将无法运行。 AttributeError: can't set attribute

基类使用属性,因为它需要动态评估其ID,而我的子类在创建时便能够知道其ID。 id属性经常被访问,我发现性能受到重大影响,因为我必须使用属性来提供该属性,而不仅仅是直接访问它。 (据我所读,属性使访问时间增加了5倍)。我的应用程序当前花费了大约10%的运行时间来获取此属性。

有什么办法可以使子类中的属性短路?

谢谢!

2 个答案:

答案 0 :(得分:3)

添加C类作为共同祖先,不带id。从中继承A和B,并根据需要在其中实现ID。 Python不会在意C上不存在id。

将非ID代码/属性从A重构为C。

适用性取决于OP是否控制类的层次结构和实例化机制。

我还找到了一种变通方法以使其按原样工作:

from datetime import datetime

class A():
    def __init__(self, num):
        self._num = num

    @property
    def id(self):
        return self._num * datetime.now().timestamp()

class B(A):

    #this fixes the problem
    id = None

    def __init__(self, id, num):
        super().__init__(num)
        self.id = id

b = B("id", 3)
print(vars(b))

这将输出:

{'_num': 3, 'id': 'id'}

诀窍是在类B上使用id = None。基本上,Python的属性/方法查找机制将在 first 类处停止,并以id作为MRO中的属性。在类B上使用id = None时,查找将在那里停止,并且永远不会到达A上讨厌的@property。

如果我按照操作说明将其撤消,则该操作:

    self.id = id
AttributeError: can't set attribute

答案 1 :(得分:2)

我将在这里介绍几种可能性。他们中有些人按照您的要求做。其中一些不是,但是无论如何它们可能是更好的选择。

首先,由于时间的流逝,示例基类在每次访问时更改obj.id的值。这真的很奇怪,而且似乎不是“ ID”的有用概念。如果您的实际用例具有稳定的obj.id返回值,则可以对其进行缓存以避免重新计算的开销:

def __init__(self):
    ...
    self._id = None
@property
def id(self):
    if self._id is not None:
        return self._id
    retval = self._id = expensive_computation()
    return retval

这可以减轻财产费用。如果需要更多缓解措施,请寻找可重复访问id的地方,而应访问一次并将其保存在变量中。无论如何实现属性,局部变量查找都优于属性访问。 (当然,如果您确实有奇怪的时变ID,则这种重构可能无效。)

第二,您不能使用“常规”属性覆盖property,但您可以 创建自己的property版本,可以用这种方式覆盖该版本。您的property会阻止属性设置,即使您强行进入实例__dict__,也要优先于“常规”属性,因为property具有__set__方法(即使您不写二传手)。不使用__set__编写自己的描述符将允许覆盖。您可以使用通用的LowPriorityProperty

class LowPriorityProperty(object):
    """
    Like @property, but no __set__ or __delete__, and does not take priority
    over the instance __dict__.
    """
    def __init__(self, fget):
        self.fget = fget
    def __get__(self, instance, owner=None):
        if instance is None:
            return self
        return self.fget(instance)

class Foo(object):
    ...
    @LowPriorityProperty
    def id(self):
        ...

class Bar(Foo):
    def __init__(self):
        super(Bar, self).__init__()
        self.id = whatever
    ...

或具有特定于角色的描述符类:

class IDDescriptor(object):
    def __get__(self, instance, owner=None):
        if instance is None:
            return self
        # Remember, self is the descriptor. instance is the object you're
        # trying to compute the id attribute of.
        return whatever(instance)

class Foo(object):
    id = IDDescriptor()
    ...

class Bar(Foo):
    def __init__(self):
        super(Bar, self).__init__()
        self.id = whatever
        ...

特定于角色的描述符的性能要比通用LowPriorityProperty更好,但由于在Python中实现了更多的逻辑而不是C,因此两者的性能都比property差。

最后,您无法使用“常规”属性覆盖property,但是您可以可以用另一个描述符(例如另一个property或作为为__slots__创建的描述符。如果您确实非常渴望提高性能,那么__slots__可能比您可以手动实现的任何描述符都更具性能,但是__slots__与该属性之间的交互是奇怪而晦涩的,您可能想要发表评论以解释您的工作。

class Foo(object):
    @property
    def id(self):
        ...

class Bar(Foo):
    __slots__ = ('id',)
    def __init__(self):
        super(Bar, self).__init__()
        self.id = whatever
    ...