【问题标题】:Python subclassing - how to update class attribute that is used by another class attributePython子类化 - 如何更新另一个类属性使用的类属性
【发布时间】:2018-08-08 05:35:55
【问题描述】:

如果有一个类属性是根据另一个类属性计算的:

class ClassA(object):
    attr1=5
    attr2=attr1+10

>>ClassA.attr2
15

然后在一个子类中,我想更新第一个类属性以导致第二个发生变化,而不重新定义第二个。例如:

class ClassB(ClassA):
    attr1=10

我希望发生的是:

>>ClassB.attr2
20

但情况并非如此,因为 attr2 是在重新定义 attr1 之前计算的。有没有办法在不重新定义第二个属性的情况下获得这种行为?

(我的特定用例是定义具有从 Form 类属性派生的 Field 属性(格式、选择等)的 WTForms,因此我可以子类化基本 Form 并更改这些属性,而无需重新定义整个 Field .)

【问题讨论】:

  • attr2 成为一个方法怎么样?

标签: python inheritance attributes subclass wtforms


【解决方案1】:

没有办法直接做到这一点。

对于实例属性,而不是类属性,您可以使用@property 轻松模拟它。每次请求时都即时计算attr2

class Spam:
    def __init__(self, a):
        self.a = a
    @property
    def twoa(self):
        return 2*self.a

(如果计算成本很高,可能会添加缓存),或者每当有人修改attr1时重新计算attr2

class Spam:
    def __init__(self, a):
        self.a = a
    @property
    def a(self):
        return self._a
    @a.setter
    def a(self):
        self._a = a
        self.twoa = 2*a

这只是因为property 创建了一个描述符,您存储在类对象中,而不是实例中,所以当有人查找twoa(在第一个示例中)或a(在第二个示例中)时Spam 实例,它回退到查找类上的描述符。

如果您希望类属性的第一个属性,它在查找实例值时按原样工作,而不是在类本身上(不幸的是,这就是您在示例中所做的)。

如果你需要第二个,它根本不起作用。

例如:

>>> class Spam:
...     a = 10
...     @property
...     def twoa(self):
...         return 2*self.a
>>> spam = Spam()
>>> spam.twoa
20
>>> Spam.twoa
<property at 0x12345678>
>>> class Eggs(Spam):
...     a = 5
>>> eggs = Eggs()
>>> eggs.twoa
10
>>> Eggs.twoa
<property at 0x12345678>

如果这不是问题,那就太好了。但如果是,则需要将描述符放在类的类上——即元类:

class MetaSpam(type):
    @property
    def twoa(cls):
        return 2 * cls.a

class Spam(metaclass=MetaSpam):
    a = 2

class Eggs(Spam):
    a = 3

对于像twoa(或你的attr2)这样的简单案例,拖入一个元类是可怕的矫枉过正。

但如果您很有可能已经在拖入自定义元类,例如复杂的表单和字段系统,那么它可能是合适的。

【讨论】:

  • @snakecharmerb 我对 wtforms 知之甚少,因此在最后一段中保持其通用性(“但如果您很有可能已经在拖入自定义元类,例如复杂的表单 -和字段系统,它可能是合适的。”)如果您可以更新它以使其不那么模糊和虚伪,请编辑答案(或在此处解释它以便我可以编辑,如果您愿意的话)。
  • 您能否在元类示例中缓存来自twoa 的结果?
  • 谢谢,是的,我想我的用例必须使用元类方法
【解决方案2】:

如果您不想使用元类,可以使用@classproperty 描述符(类似于@property)。

这是classproperty装饰器的简单实现

class classproperty(object):
    def __init__(self, fget):
        self.fget = classmethod(fget)

    def __get__(self, obj, owner):
        return self.fget.__get__(None, owner)()

还有一些示例用法:

class A(object):
    attr1 = 10

    @classproperty
    def attr2(cls):
        return cls.attr1 + 10

class B(A):
    attr1 = 8991

以及它的用法:

>>> A.attr1
10
>>> A.attr2
20
>>> B.attr2
9001
>>> A().attr2
20
>>> B().attr2
9001

【讨论】:

  • 这样的问题是,如果你写ClassB.attr2——正如你期望能够为类属性做的那样——你会得到property描述符本身,而不是30。
  • 啊,类属性,明白了——将使用@classproperty 实现进行调整
  • 除非我弄错了,否则使用任何@classproperty 都需要一个元类......一旦你有了一个元类,只需在它上面放一个普通的@property 就可以了。
  • @abarnert 看看!
  • 啊,是的,这适用于只读属性,因为您可以使用非数据描述符而不是数据描述符来伪造它。很好的收获。
【解决方案3】:

嗯,你可以在元类的__new__ 方法中进行这些计算:

class MyCalculatedClassAttrsMeta(type):
    def __new__(cls, name, bases, dct):
        c = super().__new__(cls, name, bases, dct)
        c.y = c.x + 100
        return c


class A(metaclass=MyCalculatedClassAttrsMeta):
    x = 1


class B(A):
    x = 2


print(A.x, A.y)
print(B.x, B.y)

输出将是:

1 101
2 202

【讨论】:

    猜你喜欢
    • 2020-03-05
    • 1970-01-01
    • 2017-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多