代码之家  ›  专栏  ›  技术社区  ›  N Chauhan Nathan Hinchey

元类中的property()setter问题

  •  0
  • N Chauhan Nathan Hinchey  · 技术社区  · 8 年前

    我想用 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() 用作setter的函数仅在以下情况下设置属性: True False ,但当我做一些 Foo.abstract = 'foo' ,它仍将值设置为 'foo'

    是我做错了什么还是我错过了什么?

    1 回复  |  直到 8 年前
        1
  •  2
  •   Olivier Melançon iacob    8 年前

    属性不是实例属性,而是 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__ 返回值。

    回到你的代码

    这意味着 必须是类属性 ,不是实例属性。或者,在你的情况下,财产必须是 元类 属性。

    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中的一个关键概念: 我们都是成年人 . 也许你应该相信你的用户不会 摘要 除了一个 bool 让它成为一个非属性属性。特别是,任何用户都可以更改 _abstract 属性本身。

    如果这不适合你,你想抽象成 财产 ,然后一种方法是为包含 财产 .

    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