如何向元类实例添加属性?

时间:2014-12-24 00:23:02

标签: python properties metaclass

我想定义元类,它将使我能够在类的属性的基础上在新类中创建属性(即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 个答案:

答案 0 :(得分:16)

key当前值绑定到fgetfset定义中的参数:

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__,这就是为什么有时会出现令人惊讶的错误,有时为所有属性返回相同的错误值,没有错误。


使用带有默认值的参数定义函数时,默认值将绑定到定义函数时的参数。 因此,当您将默认值绑定到key时,fget的当前值会更正绑定到fsetk

与以前不同,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))

现在,当fgetfset被调用时,key的正确值会在make_fgetmake_fset的封闭范围内找到。