Python subclassing - 如何更新被另一个 class 属性使用的 class 属性
Python subclassing - how to update class attribute that is used by another class attribute
假设有一个 class 属性是根据另一个 class 属性计算的:
class ClassA(object):
attr1=5
attr2=attr1+10
>>ClassA.attr2
15
然后在 child class 中我想更新第一个 class 属性以导致第二个属性发生变化,而没有 re-defining 第二个。例如:
class ClassB(ClassA):
attr1=10
我希望发生的事情是:
>>ClassB.attr2
20
然而情况并非如此,因为 attr2 在 attr1 之前计算 re-defined。有没有办法在不重新定义第二个属性的情况下获得这种行为?
(我的特定用例是使用从表单 class 属性派生的字段属性(格式、选择等)定义 WTForms,因此我可以子class 一个基本表单并更改这些属性无需 re-define 整个字段。)
如果不想使用元类,可以使用 @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
无法直接执行此操作。
对于实例属性,而不是 class 属性,您可以使用 @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
创建了一个存储在 class 对象中的描述符,而不是实例,所以当有人查找 twoa
时(在第一个示例中)或 a
(第二个)在 Spam
实例上,它回退到在 class.
上查找描述符
如果您想要 class 属性的第一个,它在实例上查找值时按原样工作,而不是 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 的 class 上——即一个 metaclass:
class MetaSpam(type):
@property
def twoa(cls):
return 2 * cls.a
class Spam(metaclass=MetaSpam):
a = 2
class Eggs(Spam):
a = 3
对于像 twoa
(或您的 attr2
)这样的简单案例,拖入 metaclass 是可怕的矫枉过正。
但是对于您很有可能已经在拖入自定义元classes 的情况,例如复杂的表单和字段系统,它可能是合适的。
好吧,您可以在元类中进行这些计算'__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
假设有一个 class 属性是根据另一个 class 属性计算的:
class ClassA(object):
attr1=5
attr2=attr1+10
>>ClassA.attr2
15
然后在 child class 中我想更新第一个 class 属性以导致第二个属性发生变化,而没有 re-defining 第二个。例如:
class ClassB(ClassA):
attr1=10
我希望发生的事情是:
>>ClassB.attr2
20
然而情况并非如此,因为 attr2 在 attr1 之前计算 re-defined。有没有办法在不重新定义第二个属性的情况下获得这种行为?
(我的特定用例是使用从表单 class 属性派生的字段属性(格式、选择等)定义 WTForms,因此我可以子class 一个基本表单并更改这些属性无需 re-define 整个字段。)
如果不想使用元类,可以使用 @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
无法直接执行此操作。
对于实例属性,而不是 class 属性,您可以使用 @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
创建了一个存储在 class 对象中的描述符,而不是实例,所以当有人查找 twoa
时(在第一个示例中)或 a
(第二个)在 Spam
实例上,它回退到在 class.
如果您想要 class 属性的第一个,它在实例上查找值时按原样工作,而不是 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 的 class 上——即一个 metaclass:
class MetaSpam(type):
@property
def twoa(cls):
return 2 * cls.a
class Spam(metaclass=MetaSpam):
a = 2
class Eggs(Spam):
a = 3
对于像 twoa
(或您的 attr2
)这样的简单案例,拖入 metaclass 是可怕的矫枉过正。
但是对于您很有可能已经在拖入自定义元classes 的情况,例如复杂的表单和字段系统,它可能是合适的。
好吧,您可以在元类中进行这些计算'__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