Python子类化-如何更新另一个类属性使用的类属性

时间:2018-08-08 05:35:55

标签: python inheritance attributes subclass wtforms

说是否存在根据另一个类属性计算的类属性:

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

>>ClassA.attr2
15

然后在子类中,我想更新第一类属性以引起第二个属性的更改,而无需重新定义第二个属性。例如:

class ClassB(ClassA):
    attr1=10

我想发生的事情是:

>>ClassB.attr2
20

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

(我的特殊用例是使用从Form类属性派生的Field属性(格式,选择等)定义WTForm,因此我可以对基本Form进行子类化并更改这些属性,而不必重新定义整个Field )

3 个答案:

答案 0 :(得分:1)

无法直接执行此操作。

对于实例属性而不是类属性,您可以使用@property对其进行轻松模拟。每当您要求时,都可以随时计算attr2

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

(如果计算成本很高,则可能添加缓存),或者只要有人修改attr2,就重新计算attr1

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属性,则当在实例上查找值而不是在类本身上查找值时,它按原样工作(不幸的是,这是您在示例中所做的事情)。

如果您需要第二个,则根本无法使用。

例如:

>>> 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)这样的简单情况,拖入元类是可怕的过度杀伤力。

但是对于很有可能您已经拖入自定义元类(例如复杂的表单域系统)的情况,这可能是合适的。

答案 1 :(得分:0)

如果您不想使用元类,则可以使用@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

答案 2 :(得分:0)

好吧,您可以在元类的__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