在类方法上使用 property()

2024-12-17 08:30:00
admin
原创
138
摘要:问题描述:我有一个类,它有两个类方法(使用classmethod()函数)用于获取和设置本质上是静态变量的内容。我尝试将property()函数与这些函数一起使用,但结果却出错了。我能够在解释器中使用以下内容重现该错误:class Foo(object): _var = 5 @classmet...

问题描述:

我有一个类,它有两个类方法(使用classmethod()函数)用于获取和设置本质上是静态变量的内容。我尝试将property()函数与这些函数一起使用,但结果却出错了。我能够在解释器中使用以下内容重现该错误:

class Foo(object):
    _var = 5
    @classmethod
    def getvar(cls):
        return cls._var
    @classmethod
    def setvar(cls, value):
        cls._var = value
    var = property(getvar, setvar)

我可以演示类方法,但它们不能作为属性发挥作用:

>>> f = Foo()
>>> f.getvar()
5
>>> f.setvar(4)
>>> f.getvar()
4
>>> f.var
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable
>>> f.var=5
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable

是否可以与修饰函数一起使用该property()函数?@classmethod


解决方案 1:

3.8 < Python < 3.11

可以同时使用这两个装饰器。请参阅此答案。

Python 2 和 Python 3(也适用于 3.9-3.10)

属性是在类上创建的,但会影响实例。因此,如果您想要一个classmethod属性,请在元类上创建该属性。

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         pass
...     @classmethod
...     def getvar(cls):
...         return cls._var
...     @classmethod
...     def setvar(cls, value):
...         cls._var = value
...     
>>> foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func)
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

但是由于您无论如何都要使用元类,所以如果您只将类方法移到其中,读取效果会更好。

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         @property
...         def var(cls):
...             return cls._var
...         @var.setter
...         def var(cls, value):
...             cls._var = value
... 
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

或者,使用 Python 3 的metaclass=...语法,以及在类主体之外定义的元类foo,以及负责设置初始值的元类_var

>>> class foo_meta(type):
...     def __init__(cls, *args, **kwargs):
...         cls._var = 5
...     @property
...     def var(cls):
...         return cls._var
...     @var.setter
...     def var(cls, value):
...         cls._var = value
...
>>> class foo(metaclass=foo_meta):
...     pass
...
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

解决方案 2:

更新:链接功能@classmethod已在Python 3.13@property删除。

Python 3.9中,您可以一起使用它们,但是(如 @xgt 的评论中所述)它在Python 3.11中已被弃用,因此不再受支持(但它可能会工作一段时间或在某个时候重新引入)。

版本备注请查看此处:

https://docs.python.org/3.11/library/functions.html#classmethod

然而,它以前的工作方式如下:

class G:
    @classmethod
    @property
    def __doc__(cls):
        return f'A doc for {cls.__name__!r}'

顺序很重要——由于描述符的交互方式,@classmethod顺序必须放在首位。

解决方案 3:

我希望这个非常简单的只读@classproperty装饰器能够帮助那些寻找类属性的人。

class classproperty(property):
    def __get__(self, owner_self, owner_cls):
        return self.fget(owner_cls)

class C(object):

    @classproperty
    def x(cls):
        return 1

assert C.x == 1
assert C().x == 1

解决方案 4:

阅读Python 2.2 发行说明时,我发现以下内容。

当将属性作为类属性 (Cx) 而不是实例属性 (C().x) 访问时,不会调用 [属性的] get 方法。如果要在将属性用作类属性时覆盖其 get 操作,您可以对属性进行子类化(它本身是一种新式类型)以扩展其 get 方法,或者您可以通过创建定义 get__、__setdelete 方法的新式类来从头定义描述符类型。

注意:下面的方法实际上不适用于 setter,只适用于 getter。

因此,我认为规定的解决方案是创建一个 ClassProperty 作为 property 的子类。

class ClassProperty(property):
    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()

class foo(object):
    _var=5
    def getvar(cls):
        return cls._var
    getvar=classmethod(getvar)
    def setvar(cls,value):
        cls._var=value
    setvar=classmethod(setvar)
    var=ClassProperty(getvar,setvar)

assert foo.getvar() == 5
foo.setvar(4)
assert foo.getvar() == 4
assert foo.var == 4
foo.var = 3
assert foo.var == 3

但是,setter 实际上不起作用:

foo.var = 4
assert foo.var == foo._var # raises AssertionError

foo._var保持不变,您只是用新值覆盖了该属性。

您还可以用作ClassProperty装饰器:

class foo(object):
    _var = 5

    @ClassProperty
    @classmethod
    def var(cls):
        return cls._var

    @var.setter
    @classmethod
    def var(cls, value):
        cls._var = value

assert foo.var == 5

解决方案 5:

是否可以将 property() 函数与 classmethod 修饰函数一起使用?

不。

然而,类方法仅仅是类上的一个绑定方法(部分函数),可从该类的实例访问。

由于实例是类的一个函数,并且您可以从实例派生类,因此您可以从类属性中获取所需的任何行为property

class Example(object):
    _class_property = None
    @property
    def class_property(self):
        return self._class_property
    @class_property.setter
    def class_property(self, value):
        type(self)._class_property = value
    @class_property.deleter
    def class_property(self):
        del type(self)._class_property

此代码可用于测试 - 它应该通过而不会引发任何错误:

ex1 = Example()
ex2 = Example()
ex1.class_property = None
ex2.class_property = 'Example'
assert ex1.class_property is ex2.class_property
del ex2.class_property
assert not hasattr(ex1, 'class_property')

请注意,我们根本不需要元类 - 而且您无论如何都不会通过其类的实例直接访问元类。

写一个@classproperty装饰器

classproperty实际上,您可以通过子类化仅用几行代码创建一个装饰器(它是用 C 实现的,但您可以在此处property看到等效的 Python ):

class classproperty(property):
    def __get__(self, obj, objtype=None):
        return super(classproperty, self).__get__(objtype)
    def __set__(self, obj, value):
        super(classproperty, self).__set__(type(obj), value)
    def __delete__(self, obj):
        super(classproperty, self).__delete__(type(obj))

然后将装饰器视为与属性相结合的类方法:

class Foo(object):
    _bar = 5
    @classproperty
    def bar(cls):
        """this is the bar attribute - each subclass of Foo gets its own.
        Lookups should follow the method resolution order.
        """
        return cls._bar
    @bar.setter
    def bar(cls, value):
        cls._bar = value
    @bar.deleter
    def bar(cls):
        del cls._bar

并且此代码应该可以正常运行:

def main():
    f = Foo()
    print(f.bar)
    f.bar = 4
    print(f.bar)
    del f.bar
    try:
        f.bar
    except AttributeError:
        pass
    else:
        raise RuntimeError('f.bar must have worked - inconceivable!')
    help(f)  # includes the Foo.bar help.
    f.bar = 5

    class Bar(Foo):
        "a subclass of Foo, nothing more"
    help(Bar) # includes the Foo.bar help!
    b = Bar()
    b.bar = 'baz'
    print(b.bar) # prints baz
    del b.bar
    print(b.bar) # prints 5 - looked up from Foo!

    
if __name__ == '__main__':
    main()

但我不确定这样做是否明智。一篇旧的邮件列表文章表明它不应该起作用。

让属性在类上起作用:

上述的缺点是“类属性”不能从类中访问,因为它只会覆盖类中的数据描述符__dict__

但是,我们可以使用元类中定义的属性来覆盖它__dict__。例如:

class MetaWithFooClassProperty(type):
    @property
    def foo(cls):
        """The foo property is a function of the class -
        in this case, the trivial case of the identity function.
        """
        return cls

然后,元类的类实例可以具有一个属性,该属性可以使用前面章节中已经演示的原理来访问该类的属性:

class FooClassProperty(metaclass=MetaWithFooClassProperty):
    @property
    def foo(self):
        """access the class's property"""
        return type(self).foo

现在我们看到了两个例子

>>> FooClassProperty().foo
<class '__main__.FooClassProperty'>

和班级

>>> FooClassProperty.foo
<class '__main__.FooClassProperty'>

可以访问类属性。

解决方案 6:

Python 3!

请参阅@Amit Portnoy 的答案,了解 python >= 3.9 中更简洁的方法


老问题,很多观点,迫切需要一个真正的 Python 3 方法。

幸运的是,使用 kwarg 可以轻松实现这一点metaclass

class FooProperties(type):
    
    @property
    def var(cls):
        return cls._var

class Foo(object, metaclass=FooProperties):
    _var = 'FOO!'

然后,>>> Foo.var

“FOO!”

解决方案 7:

目前没有合理的方法可以让这个“类属性”系统在 Python 中发挥作用。

这是让它工作的一种不合理方法。你当然可以通过增加元类魔法的数量让它更加无缝。

class ClassProperty(object):
    def __init__(self, getter, setter):
        self.getter = getter
        self.setter = setter
    def __get__(self, cls, owner):
        return getattr(cls, self.getter)()
    def __set__(self, cls, value):
        getattr(cls, self.setter)(value)

class MetaFoo(type):
    var = ClassProperty('getvar', 'setvar')

class Foo(object):
    __metaclass__ = MetaFoo
    _var = 5
    @classmethod
    def getvar(cls):
        print "Getting var =", cls._var
        return cls._var
    @classmethod
    def setvar(cls, value):
        print "Setting var =", value
        cls._var = value

x = Foo.var
print "Foo.var = ", x
Foo.var = 42
x = Foo.var
print "Foo.var = ", x

问题的关键在于,属性就是 Python 所称的“描述符”。没有简单易懂的方法来解释这种元编程的工作原理,所以我必须向您指出描述符 howto。

只有在实施相当先进的框架时,您才需要了解这类事情。例如透明对象持久性或 RPC 系统,或某种特定于领域的语言。

然而,在对先前答案的评论中,你说你

需要以某种方式修改属性,以便类的所有实例都可以看到该属性,并且在调用这些类方法的范围中没有对该类的所有实例的引用。

在我看来,你真正想要的是观察者设计模式。

解决方案 8:

如果您想通过实例化对象访问类属性,则仅在元类上设置它没有帮助,在这种情况下,您还需要在对象上安装一个普通属性(它会分派到类属性)。我认为以下内容更清楚一点:

#!/usr/bin/python

class classproperty(property):
    def __get__(self, obj, type_):
        return self.fget.__get__(None, type_)()

    def __set__(self, obj, value):
        cls = type(obj)
        return self.fset.__get__(None, cls)(value)

class A (object):

    _foo = 1

    @classproperty
    @classmethod
    def foo(cls):
        return cls._foo

    @foo.setter
    @classmethod
    def foo(cls, value):
        cls.foo = value

a = A()

print a.foo

b = A()

print b.foo

b.foo = 5

print a.foo

A.foo = 10

print b.foo

print A.foo

解决方案 9:

解决方案只有一半,类上的 set 仍然不起作用。解决方案是实现属性和静态方法的自定义属性类

class ClassProperty(object):
    def __init__(self, fget, fset):
        self.fget = fget
        self.fset = fset

    def __get__(self, instance, owner):
        return self.fget()

    def __set__(self, instance, value):
        self.fset(value)

class Foo(object):
    _bar = 1
    def get_bar():
        print 'getting'
        return Foo._bar

    def set_bar(value):
        print 'setting'
        Foo._bar = value

    bar = ClassProperty(get_bar, set_bar)

f = Foo()
#__get__ works
f.bar
Foo.bar

f.bar = 2
Foo.bar = 3 #__set__ does not

解决方案 10:

因为我需要修改一个属性,以便该属性可以被类的所有实例看到,并且在调用这些类方法的范围内没有对该类的所有实例的引用。

您是否有权访问该类的至少一个实例?我可以想到一种方法来做到这一点:

class MyClass (object):
    __var = None

    def _set_var (self, value):
        type (self).__var = value

    def _get_var (self):
        return self.__var

    var = property (_get_var, _set_var)

a = MyClass ()
b = MyClass ()
a.var = "foo"
print b.var

解决方案 11:

尝试一下,它可以完成工作,而无需更改/添加大量现有代码。

>>> class foo(object):
...     _var = 5
...     def getvar(cls):
...         return cls._var
...     getvar = classmethod(getvar)
...     def setvar(cls, value):
...         cls._var = value
...     setvar = classmethod(setvar)
...     var = property(lambda self: self.getvar(), lambda self, val: self.setvar(val))
...
>>> f = foo()
>>> f.var
5
>>> f.var = 3
>>> f.var
3

property函数需要两个callable参数。给他们 lambda 包装器(它将实例作为第一个参数传递),一切就好了。

解决方案 12:

对于 Python 3.9 之前的功能方法,你可以使用以下命令:

def classproperty(fget):
  return type(
      'classproperty',
      (),
      {'__get__': lambda self, _, cls: fget(cls), '__module__': None}
  )()
  
class Item:
  a = 47

  @classproperty
  def x(cls):
    return cls.a

Item.x

解决方案 13:

这是一个解决方案,它既适用于通过类进行访问,也适用于通过使用元类的实例进行访问。

In [1]: class ClassPropertyMeta(type):
   ...:     @property
   ...:     def prop(cls):
   ...:         return cls._prop
   ...:     def __new__(cls, name, parents, dct):
   ...:         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
   ...:         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
   ...:         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
   ...:         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
   ...:

In [2]: class ClassProperty(object):
   ...:     __metaclass__ = ClassPropertyMeta
   ...:     _prop = 42
   ...:     def __getattr__(self, attr):
   ...:         raise Exception('Never gets called')
   ...:

In [3]: ClassProperty.prop
Out[3]: 42

In [4]: ClassProperty.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-e2e8b423818a> in <module>()
----> 1 ClassProperty.prop = 1

AttributeError: can't set attribute

In [5]: cp = ClassProperty()

In [6]: cp.prop
Out[6]: 42

In [7]: cp.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-e8284a3ee950> in <module>()
----> 1 cp.prop = 1

<ipython-input-1-16b7c320d521> in <lambda>(cls, attr, val)
      6         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
      7         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
----> 8         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
      9         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)

AttributeError: can't set attribute

这也适用于元类中定义的 setter。

解决方案 14:

我找到了一个解决此问题的简洁方法。这是一个称为classutilities ( ) 的包,请参阅PyPi 上的pip install classutilities文档。

考虑以下例子:

import classutilities

class SomeClass(classutilities.ClassPropertiesMixin):
    _some_variable = 8  # Some encapsulated class variable

    @classutilities.classproperty
    def some_variable(cls):  # class property getter
        return cls._some_variable

    @some_variable.setter
    def some_variable(cls, value):  # class property setter
        cls._some_variable = value

您可以在类级别和实例级别使用它:

# Getter on class level:
value = SomeClass.some_variable
print(value)  # >>> 8
# Getter on instance level
inst = SomeClass()
value = inst.some_variable
print(value)  # >>> 8

# Setter on class level:
new_value = 9
SomeClass.some_variable = new_value
print(SomeClass.some_variable)   # >>> 9
print(SomeClass._some_variable)  # >>> 9
# Setter on instance level
inst = SomeClass()
inst.some_variable = new_value
print(SomeClass.some_variable)   # >>> 9
print(SomeClass._some_variable)  # >>> 9
print(inst.some_variable)        # >>> 9
print(inst._some_variable)       # >>> 9

如您所见,它在任何情况下都可以正常工作。

解决方案 15:

基于https://stackoverflow.com/a/1800999/2290820


class MetaProperty(type):

    def __init__(cls, *args, **kwargs):
        super()

    @property
    def praparty(cls):
        return cls._var

    @praparty.setter
    def praparty(cls, val):
        cls._var = val


class A(metaclass=MetaProperty):
    _var = 5


print(A.praparty)
A.praparty = 6
print(A.praparty)

解决方案 16:

在搜索了不同的地方之后,我找到了一种定义适用于 Python 2 和 3 的类属性的方法。

from future.utils import with_metaclass

class BuilderMetaClass(type):
    @property
    def load_namespaces(self):
        return (self.__sourcepath__)

class BuilderMixin(with_metaclass(BuilderMetaClass, object)):
    __sourcepath__ = 'sp'        

print(BuilderMixin.load_namespaces)

希望这可以帮助到别人:)

解决方案 17:

这是我的解决方案,它还缓存了类属性

class class_property(object):
    # this caches the result of the function call for fn with cls input
    # use this as a decorator on function methods that you want converted
    # into cached properties

    def __init__(self, fn):
        self._fn_name = fn.__name__
        if not isinstance(fn, (classmethod, staticmethod)):
            fn = classmethod(fn)
        self._fn = fn

    def __get__(self, obj, cls=None):
        if cls is None:
            cls = type(obj)
        if (
            self._fn_name in vars(cls) and
            type(vars(cls)[self._fn_name]).__name__ != "class_property"
        ):
            return vars(cls)[self._fn_name]
        else:
            value = self._fn.__get__(obj, cls)()
            setattr(cls, self._fn_name, value)
            return value

解决方案 18:

这是我的建议。不要使用类方法。

严重地。

在这种情况下使用类方法的原因是什么?为什么不拥有普通类的普通对象?


如果你只是想改变值,属性其实没什么用,对吧?只需设置属性值即可。

仅当有某些内容需要隐藏时才应使用属性——某些内容在未来的实现中可能会发生变化。

也许你的例子太过简化,而且你省略了一些复杂的计算。但看起来该房产并没有增加显著的价值。

受 Java 影响的“隐私”技术(在 Python 中,属性名称以 _ 开头)实际上没什么用。对谁保密?当您有源代码时(就像在 Python 中一样),隐私的意义就有点模糊了。

受 Java 影响的 EJB 样式 getter 和 setter(在 Python 中通常作为属性实现)是为了方便 Java 的原始自省以及通过静态语言编译器的检查。所有这些 getter 和 setter 在 Python 中都不太有用。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用