【问题标题】:Python (3) class property weirdnessPython(3)类属性怪异
【发布时间】:2020-06-05 08:08:41
【问题描述】:

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

我试图了解创建类属性的各种方式(例如,@property 用于实例属性,但用于类属性/变量)。我已经尝试了这里的一些建议(例如,Using property() on classmethodsHow to make a class property?)。

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

请注意,来自“How to make a class property?”的代码的行为似乎符合我的预期,但我测试了“How to make a class property?”中的“最佳”答案,但它的行为与我预期的不符。

代码

为了弄清楚,我为 Python 3.x 指定的元类方法编写了一些测试代码:

class SomeClassMeta(type):
    meta_attr = "SomeClassMeta Default meta_attr"
    # From https://stackoverflow.com/questions/128573/using-property-on-classmethods
    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  https://stackoverflow.com/questions/5189699/how-to-make-a-class-property
    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  https://stackoverflow.com/questions/5189699/how-to-make-a-class-property
    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'

问题

  • 为什么SomeClass.inst_propSomeClass.norm_prop 返回property() 而所有其他property()s 无论是在类还是实例上都按预期运行(即使在第一次实例化之后)?
  • 我认为元类的目的是创建一个 Class 属性。那么为什么设置Some_Inst.meta_prop = "Set with Some_Inst.meta_prop" 会改变实例值而不是类值呢?请注意,Some_Inst.class_prop 的行为与我想象的一样。

【问题讨论】:

  • 你有没有机会把它剪掉一点?
  • 我的上帝。发的太多了。请参阅minimal reproducible example。很明显你已经付出了很多努力,但我不会经历所有这些
  • 我会尽快编辑。
  • 下面回答,但是除了多余的代码和例子,你为什么passing这么多?
  • @Grismar 我使用 pass 因为在协作时没有它,一些尝试重新格式化的工具会导致问题。我正在更新问题以更紧凑。

标签: python python-3.x properties python-decorators metaclass


【解决方案1】:

由于您对问题进行了重大更改,因此这里有一个新答案。

- 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 可以获得inst_prop 对类的含义:属性。它是在类上定义的属性,因此它将解析为实例的值和类的属性。

norm_prop 在您的第一个问题中也是如此。

关于第二个问题:“我认为元类的目的是创建一个Class属性。为什么设置Some_Inst.meta_prop = "Set with Some_Inst.meta_prop"会改变实例值而不是类值?”

因为Some_class 没有属性meta_prop,直到您将其分配给Some_Inst.meta_prop = "Set with Some_Inst.meta_prop"meta_prop 是您在元类上定义的属性,但由于 SomeClass 不是从 SomeClassMeta 继承的(它只是作为元类),它不在 SomeClass 上。

为什么要使用元类来定义类属性,为什么不简单地在类上定义它们呢?用蒂姆·彼得斯的话来说:

“元类是比 99% 的用户所担心的更深层次的魔法。如果你想知道是否需要它们,其实你不需要(真正需要它们的人肯定知道他们需要它们,但并不需要它们)解释为什么)。”

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

【讨论】:

  • 感谢您的解释。 “为什么要使用元类来定义类属性”的答案是我以前从未考虑过这一点,但与创建“类属性”的文章 (stackoverflow.com/questions/5189699/…) 最相关的内容是最佳解决方案。
  • 我明白了,谢谢你的链接。我认为提出这个问题的人必须回答同样的基本问题:为什么你需要一个“classproperty”开头?最后,从使用元类的普通 Python 解决方案的行为方式更改代码的行为方式通常是一个坏主意,因为使用您的代码的人不会期望这种解决方案,并且通常有更好的方法来实现相同的目标。总是有例外,但正如引用所暗示的那样:当你有例外时,你就会知道。
  • 以上是否回答了您的问题? (我觉得确实如此)
  • 是的,它确实比漂浮在“互联网”周围的其他东西更有意义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-24
  • 1970-01-01
  • 1970-01-01
  • 2017-11-05
  • 2017-04-10
相关资源
最近更新 更多