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

Python子类化-如何更新由另一个类属性使用的类属性,python,inheritance,attributes,subclass,wtforms,Python,Inheritance,Attributes,Subclass,Wtforms,假设存在根据另一个类属性计算的类属性: class ClassA(object): attr1=5 attr2=attr1+10 >>ClassA.attr2 15 然后在一个子类中,我想更新第一个类的属性,以引起第二个类的更改,而不重新定义第二个类。e、 g: class ClassB(ClassA): attr1=10 我希望发生的是: >>ClassB.attr2 20 但是,情况并非如此,因为在重新定义attr1之前,会计算attr

假设存在根据另一个类属性计算的类属性:

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

>>ClassA.attr2
15
然后在一个子类中,我想更新第一个类的属性,以引起第二个类的更改,而不重新定义第二个类。e、 g:

class ClassB(ClassA):
    attr1=10
我希望发生的是:

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


(我的特定用例是使用从表单类属性派生的字段属性(格式、选项等)定义WTForms,因此我可以对基本表单进行子类化并更改这些属性,而无需重新定义整个字段。)

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

下面是一个
classproperty
decorator的简单实现

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

没有办法直接做到这一点

例如,使用实例属性,而不是类属性,可以非常轻松地模拟它。每次请求时,可以动态计算
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
创建了一个描述符,存储在类对象中,而不是实例中,所以当有人在
Spam
实例上查找
twoa
(在第一个示例中)或
a
(在第二个示例中)时,它会返回到类上查找描述符

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

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

例如:

>>> 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>
对于像
twoa
(或您的
attr2
)这样的简单情况,拖入元类是可怕的过火行为


但是对于一个很有可能已经在拖拽自定义元类的情况,比如复杂的表单和字段系统,这可能是合适的。

好的,您可以使用元类的“
\uuuuu new\uuuu
方法进行这些计算:

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

问题是,如果您编写
ClassB.attr2
——正如您希望能够为类属性所做的那样,您将得到
属性
描述符本身,而不是30。啊,类属性,明白了——将使用
@classproperty
实现进行调整,除非我弄错了,使用任何
@classproperty
都需要一个元类……一旦有了元类,只需在它上面放一个普通的
@property
就可以了。@abarnert检查一下!啊,是的,这对只读属性有效,因为你可以用非数据描述符而不是数据描述符来伪造它。很好的回答。@snakecharmerb我对wtforms知之甚少,因此在最后一段中保留了它的通用性(“但是对于一个很有可能已经拖入自定义元类的情况,比如一个复杂的表单和字段系统,这可能是合适的。”)如果您可以更新它,使其不那么模糊和空洞,请编辑答案(或者在这里解释,以便我可以编辑,如果您愿意的话)。
1 101
2 202