元类中的属性()设置器问题

Posted

技术标签:

【中文标题】元类中的属性()设置器问题【英文标题】:property() setter issues in metaclass 【发布时间】:2018-11-19 23:57:35 【问题描述】:

我试图在我的元类中使用property() 来提供一种访问/设置内部属性的方法。我使用property()@property 相对,因为它位于元类中,我需要将该属性应用于__new__ 方法中传递的类。

这是我的代码与相关部分的基本示例:

def __new__(mcls, name, bases, dct, **kwargs):

    def getabstract(obj):
        return getattr(obj, '_abstract', False)


    def setabstract(obj, value):
        if str(value) in ('True', 'False'):
            return setattr(obj, '_abstract', value)
        else: print(f'invalid abstract assignment value')
        return None        

    cls = super().__new__(mcls, name, bases, dct)

    for name, value in cls.__dict__.items():
        if callable(value):
            # only applies to callable (method)
            value._abstract = getattr(value, '_abstract', False)
            # add an _abstract attribute to the method or return its
            # current value if it already has one

    for base in bases:
        base._abstract = getattr(base, '_abstract', False)
        # give each base class an _abstract attribute if not already given

    cls.abstract = property(getabstract(cls),
                            setabstract(cls, getattr(cls, '_abstract', False)))
    # make an abstract property for class
    for name, value in cls.__dict__.items():
        if callable(value):
            # make an abstract property for functions
            value.abstract = property(getabstract(value),
                                      setabstract(value, getattr(value, '_abstract', False)))

当我运行它时,不会发生错误,但是当访问由这个元类创建的新类时,例如Foo.abstract 它返回:

    <property object at 0x.....>

此外,用作设置器的setabstract() 函数仅在TrueFalse 时设置属性,但是当我执行Foo.abstract = 'foo' 之类的操作时,它仍将值设置为'foo'

我在这里做错了什么还是我错过了什么?

【问题讨论】:

更新:删除属性的使用并简单地将_abstract 属性重命名为abstract 似乎可以完成这项工作,但只是失去了仅允许True/False 设置值的功能跨度> 【参考方案1】:

属性不是实例属性,它是绑定到访问它的对象的类的descriptor 属性。这是一个简化的例子:

class A:
    @property
    def x(self):
        return True

print(A.x) # <property object at 0x0000021AE2944548>
print(A().x) # True

当获取A.x 时,您将获得未绑定的属性。获取A().x时,属性绑定到一个实例,A.x.__get__返回一个值。

回到你的代码

这意味着属性必须是类属性,而不是实例属性。或者,在您的情况下,该属性必须是 metaclass 属性。

class Meta(type):
    @property
    def abstract(cls):
        return getattr(cls, '_abstract', False)

    @abstract.setter
    def abstract(cls, value):
        if value in (True, False):
            setattr(cls, '_abstract', value)
        else:
            raise TypeError(f'invalid abstract assignment value')

class Foo(metaclass=Meta):
    pass

print(Foo.abstract) # False
Foo.abstract = 'Not at bool' # TypeError: invalid abstract assignment Not at bool

虽然,这只是部分解决了您的问题,因为您希望类的每个方法都具有 abstract 属性。为此,您需要他们的班级拥有property

在我们深入之前,让我回顾一下 Python 中的一个关键概念:我们都是同意的成年人。也许您应该相信您的用户不要将abstract 设置为bool 之外的任何其他内容,并让它成为非属性属性。特别是,任何用户都可以自行更改_abstract 属性。

如果这不适合您,并且您希望抽象成为 property,那么一种方法是为包含 property 的方法定义一个包装类。

class Abstract:
    @property
    def abstract(cls):
        return getattr(cls, '_abstract', False)

    @abstract.setter
    def abstract(cls, value):
        if value in (True, False):
            setattr(cls, '_abstract', value)
        else:
            raise TypeError(f'invalid abstract assignment value')

class AbstractCallable(Abstract):
    def __init__(self, f):
        self.callable = f

    def __call__(self, *args, **kwargs):
        return self.callable(*args, **kwargs)

class Meta(type, Abstract):
    def __new__(mcls, name, bases, dct, **kwargs):
        for name, value in dct.items():
            if callable(value):
                dct[name] = AbstractCallable(value)

        return super().__new__(mcls, name, bases, dct)

class Foo(metaclass=Meta):
    def bar(self):
        pass

print(Foo.abstract) # False
print(Foo.bar.abstract) # False
Foo.bar.abstract = 'baz' # TypeError: invalid abstract assignment baz

【讨论】:

以上是关于元类中的属性()设置器问题的主要内容,如果未能解决你的问题,请参考以下文章

Python元类编程

如何在 Groovy 类中“隐藏”元类属性

元类中 __new__ 的行为(也在继承的上下文中)

我们如何根据元类中 init 传递的属性来改变属性

将装饰器附加到类中的所有函数

Python 元类中的继承如何工作?