【问题标题】: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,但我想使用元类来完成。您是否知道如何做到这一点或有可能做到这一点?

【问题讨论】:

    标签: python properties metaclass


    【解决方案1】:

    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 已经结束,所以keylast 值就是找到的值。 Python3 的dict.item 方法以不可预知的顺序返回项目,所以有时最后一个键是__qualname__,这就是为什么有时会引发令人惊讶的错误,有时会返回相同的错误值对于所有没有错误的属性。


    当您使用带默认值的参数定义函数时,默认值在定义函数时绑定到参数。 因此,当您将默认值绑定到 k 时,key 的当前值将更正绑定到 fgetfset

    与以前不同,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 的封闭范围内找到。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-26
      • 2018-06-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-07
      • 2011-11-01
      • 1970-01-01
      相关资源
      最近更新 更多