如何向元类实例添加属性?
Posted
技术标签:
【中文标题】如何向元类实例添加属性?【英文标题】:How to add properties to a metaclass instance? 【发布时间】:2015-02-22 03:52:02 【问题描述】:我想定义元类,使我能够基于类的属性在新类中创建属性(即 setter、getter)。
例如,我想定义类:
class Person(metaclass=MetaReadOnly):
name = "Ketty"
age = 22
def __str__(self):
return ("Name: " + str(self.name) + "; age: "
+ str(self.age))
但我想得到这样的东西:
class Person():
__name = "Ketty"
__age = 22
@property
def name(self):
return self.__name;
@name.setter
def name(self, value):
raise RuntimeError("Read only")
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
raise RuntimeError("Read only")
def __str__(self):
return ("Name: " + str(self.name) + "; age: "
+ str(self.age))
这是我编写的元类:
class MetaReadOnly(type):
def __new__(cls, clsname, bases, dct):
result_dct =
for key, value in dct.items():
if not key.startswith("__"):
result_dct["__" + key] = value
fget = lambda self: getattr(self, "__%s" % key)
fset = lambda self, value: setattr(self, "__"
+ key, value)
result_dct[key] = property(fget, fset)
else:
result_dct[key] = value
inst = super(MetaReadOnly, cls).__new__(cls, clsname,
bases, result_dct)
return inst
def raiseerror(self, attribute):
raise RuntimeError("%s is read only." % attribute)
但是它不能正常工作。
client = Person()
print(client)
有时我会得到:
Name: Ketty; age: Ketty
有时:
Name: 22; age: 22
甚至是错误:
Traceback (most recent call last):
File "F:\Projects\TestP\src\main.py", line 38, in <module>
print(client)
File "F:\Projects\TestP\src\main.py", line 34, in __str__
return ("Name: " + str(self.name) + "; age: " + str(self.age))
File "F:\Projects\TestP\src\main.py", line 13, in <lambda>
fget = lambda self: getattr(self, "__%s" % key)
AttributeError: 'Person' object has no attribute '____qualname__'
我找到了如何以其他方式完成的示例Python classes: Dynamic properties,但我想使用元类来完成。您是否知道如何做到这一点或有可能做到这一点?
【问题讨论】:
【参考方案1】:将key
的当前 值绑定到fget
和fset
定义中的参数:
fget = lambda self, k=key: getattr(self, "__%s" % k)
fset = lambda self, value, k=key: setattr(self, "__" + k, value)
这是一个经典的 Python 陷阱。当你定义
fget = lambda self: getattr(self, "__%s" % key)
key
的值是在调用 fget
时确定的,而不是在定义 fget
时确定的。由于它是一个非局部变量,它的值可以在__new__
函数的封闭范围内找到。当fget
被调用时,for-loop
已经结束,所以key
的last 值就是找到的值。 Python3 的dict.item
方法以不可预知的顺序返回项目,所以有时最后一个键是__qualname__
,这就是为什么有时会引发令人惊讶的错误,有时会返回相同的错误值对于所有没有错误的属性。
当您使用带默认值的参数定义函数时,默认值在定义函数时绑定到参数。
因此,当您将默认值绑定到 k
时,key
的当前值将更正绑定到 fget
和 fset
。
与以前不同,k
现在是一个局部变量。默认值存储在fget.__defaults__
和fset.__defaults__
中。
另一种选择是使用closure。您可以在元类之外定义它:
def make_fget(key):
def fget(self):
return getattr(self, "__%s" % key)
return fget
def make_fset(key):
def fset(self, value):
setattr(self, "__" + key, value)
return fset
并像这样在元类中使用它:
result_dct[key] = property(make_fget(key), make_fset(key))
现在当fget
或fset
被调用时,key
的正确值会在make_fget
或make_fset
的封闭范围内找到。
【讨论】:
以上是关于如何向元类实例添加属性?的主要内容,如果未能解决你的问题,请参考以下文章
如何创建可以为类提供实例数组并提供作用于所有类实例的“巫毒”实例的元类?