Python (3) class 属性 古怪

Python (3) class property weirdness

注意 根据要求更新为更短的代码和问题。希望这会有所帮助。

我正在尝试了解可以创建 class 属性的各种方式(例如,对于实例属性,如 @属性,但对于 class attributes/variables ).我已经尝试了这里的一些建议(例如 Using property() on classmethods and How to make a class property?)。

简而言之,看起来使用为“Using property() on classmethods”中的 Python 3.x 推荐的元class 方法导致属性实际上没有被保留(见测试结果)。我想知道我是否犯了错误,或者是否有人可以解释为什么我看到的是预期的正确行为。

请注意,来自“How to make a class property?" seems to behave as I would expect, but I tested the "best" answer in "How to make a class property?”的代码并不像我预期的那样运行。

代码

为了解决问题,我为 Python 3.x:

指定的元 class 方法编写了一些测试代码
class SomeClassMeta(type):
    meta_attr = "SomeClassMeta Default meta_attr"
    # From 
    def __init__(self, *args, **kwargs):
        self.meta_attr = "SomeClassMeta.__init__() set meta_attr"
        pass
    @property
    def meta_prop(self):
        return self.meta_attr
    @meta_prop.setter
    def meta_prop(self, val):
        self.meta_attr = val
        pass

class ClasspropertyDescriptor(object):
    # From  
    def __init__(self, fget, fset=None):
        self.fget = fget
        self.fset = fset
    def __get__(self, obj, klass=None):
        """Get it."""
        if klass is None:
            klass = type(obj)
        return self.fget.__get__(obj, klass)()
    def __set__(self, obj, value):
        """Set it."""
        if not self.fset:
            raise AttributeError("can't set attribute")
        type_ = type(obj)
        return self.fset.__get__(obj, type_)(value)
    def setter(self, func):
        """Set some class value."""
        if not isinstance(func, (classmethod, staticmethod)):
            func = classmethod(func)
        self.fset = func
        return self

def classproperty(func):
    # From  
    if not isinstance(func, (classmethod, staticmethod)):
        func = classmethod(func)
        pass
    return ClasspropertyDescriptor(func)

class ClasspropertyMetaClass(type):
    def __setattr__(self, key, value):
        if key in self.__dict__:
            obj = self.__dict__.get(key)
        if obj and type(obj) is ClasspropertyDescriptor:
            return obj.__set__(self, value)
        return super(ClasspropertyMetaClass, self).__setattr__(key, value)

class SomeClass(metaclass=SomeClassMeta):
    class_attr = "SomeClass Default class_attr"
    norm_attr = "SomeClass Default norm_attr"
    inst_attr = "SomeClass Default inst_attr"
    _name = "SomeClass"
    def __init__(self,name):
        """Init this."""
        self.inst_attr = "SomeClass.__init__() set inst_attr"
        self._name = name
        pass
    @property
    def norm_prop(self):
        return self.norm_attr
    @norm_prop.setter
    def norm_prop(self, val):
        self.norm_attr = val
        pass
    @classproperty
    def class_prop(self):
        return self.class_attr
    @class_prop.setter
    def class_prop(self, val):
        self.class_attr = val
        pass
    @property
    def inst_prop(self):
        """Get the instance variable (attribute)."""
        return self.inst_attr
    @inst_prop.setter
    def inst_prop(self, val):
        self.inst_attr = val
        pass
    def _info(self,attr):
        attrval = getattr(self,attr,'No Such Attribute')
        attrcval = getattr(self.__class__,attr,'No Such Attribute')
        print(f" - {self._name}.{attr} = '{attrval}', {self._name}.__class__.{attr} = '{attrcval}'")
        if isinstance(attrval,property):
            print(f" - {self._name}.{attr}.__get__() = '{attrval.__get__(self)}'")
    def info(self):
        print(f"{self._name} is a {type(self).__name__}")
        for attr in [
                'class_prop',
                'class_attr',
                'inst_attr',
                'inst_prop',
                'meta_attr',
                'meta_prop',
                'norm_attr',
                'norm_prop',
                ]:
            self._info(attr)
            self.__class__._info(self.__class__,attr)
Some_Inst = SomeClass('Some_Inst')
Some_Inst.class_prop = "Set with Some_Inst.class_prop"
Some_Inst.inst_prop = "Set with Some_Inst.inst_prop"
Some_Inst.meta_prop = "Set with Some_Inst.meta_prop"
Some_Inst.norm_prop = "Set with Some_Inst.norm_prop"
Some_Inst.info()

输出

Some_Inst is a SomeClass
 - Some_Inst.class_prop = 'Set with Some_Inst.class_prop', Some_Inst.__class__.class_prop = 'Set with Some_Inst.class_prop'
 - SomeClass.class_prop = 'Set with Some_Inst.class_prop', SomeClass.__class__.class_prop = 'No Such Attribute'
 - Some_Inst.class_attr = 'Set with Some_Inst.class_prop', Some_Inst.__class__.class_attr = 'Set with Some_Inst.class_prop'
 - SomeClass.class_attr = 'Set with Some_Inst.class_prop', SomeClass.__class__.class_attr = 'No Such Attribute'
 - Some_Inst.inst_attr = 'Set with Some_Inst.inst_prop', Some_Inst.__class__.inst_attr = 'SomeClass Default inst_attr'
 - SomeClass.inst_attr = 'SomeClass Default inst_attr', SomeClass.__class__.inst_attr = 'No Such Attribute'
 - Some_Inst.inst_prop = 'Set with Some_Inst.inst_prop', Some_Inst.__class__.inst_prop = '<property object at 0x7fdc48c594a8>'
 - SomeClass.inst_prop = '<property object at 0x7fdc48c594a8>', SomeClass.__class__.inst_prop = 'No Such Attribute'
 - SomeClass.inst_prop.__get__() = 'SomeClass Default inst_attr'
 - Some_Inst.meta_attr = 'SomeClassMeta.__init__() set meta_attr', Some_Inst.__class__.meta_attr = 'SomeClassMeta.__init__() set meta_attr'
 - SomeClass.meta_attr = 'SomeClassMeta.__init__() set meta_attr', SomeClass.__class__.meta_attr = 'SomeClassMeta Default meta_attr'
 - Some_Inst.meta_prop = 'Set with Some_Inst.meta_prop', Some_Inst.__class__.meta_prop = 'SomeClassMeta.__init__() set meta_attr'
 - SomeClass.meta_prop = 'SomeClassMeta.__init__() set meta_attr', SomeClass.__class__.meta_prop = '<property object at 0x7fdc48c59908>'
 - Some_Inst.norm_attr = 'Set with Some_Inst.norm_prop', Some_Inst.__class__.norm_attr = 'SomeClass Default norm_attr'
 - SomeClass.norm_attr = 'SomeClass Default norm_attr', SomeClass.__class__.norm_attr = 'No Such Attribute'
 - Some_Inst.norm_prop = 'Set with Some_Inst.norm_prop', Some_Inst.__class__.norm_prop = '<property object at 0x7fdc48c596d8>'
 - SomeClass.norm_prop = '<property object at 0x7fdc48c596d8>', SomeClass.__class__.norm_prop = 'No Such Attribute'
 - SomeClass.norm_prop.__get__() = 'SomeClass Default norm_attr'

问题

由于您对问题进行了实质性更改,这里有一个新答案。

- Some_Inst.inst_prop = 'Set with Some_Inst.inst_prop', Some_Inst.__class__.inst_prop = '<property object at 0x000002062A3C9B38>'

作为实例 Some_Inst 的 属性,评估 Some_Inst.inst_prop 会得到 属性 值,而评估 Some_Inst.__class__.inst_prop 会得到 [=14] =] 用于 class:一个 属性。它是在 class 上定义的 属性,因此它将解析为实例的值和 class.

的 属性

在第一个问题的上下文中,norm_prop 也是如此。

关于第二个问题:"I thought the purpose of the metaclass was to create a Class property. Why then does setting Some_Inst.meta_prop = "Set with Some_Inst.meta_prop" change the instance value but not the class value?"

因为 Some_class 在您用 Some_Inst.meta_prop = "Set with Some_Inst.meta_prop" 赋值之前没有属性 meta_propmeta_prop 是您在 meta-class 上定义的 属性,但由于 SomeClass 不是从 SomeClassMeta 继承的(它只是将其作为元class), 它不在 SomeClass.

为什么要使用 meta-classes 来定义 class-properties,为什么不简单地在 class 上定义它们?用蒂姆·彼得斯的话来说:

"Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why)."

这篇文章有很好的进一步解释:https://realpython.com/python-metaclasses/