我有一个带有两个类方法的类(使用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

是否可以使用属性()函数与@classmethod装饰函数?


当前回答

我找到了一个解决这个问题的简单方法。它是一个叫做classutilities的包(pip install classutilities),请参阅这里关于PyPi的文档。

考虑的例子:

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

如您所见,它在所有情况下都能正常工作。

其他回答

如果你想通过一个实例化的对象访问class属性,只在元类上设置它是没有帮助的,在这种情况下,你需要在对象上安装一个普通的属性(它分配给class属性)。我认为以下几点说得更清楚一些:

#!/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

基于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)

在读Python 2.2发布说明时,我发现了以下内容。

属性的get方法不会被调用 属性作为类访问 属性(C.x)而不是作为一个 实例属性(C().x)。如果你 想要重写__get__操作 当属性用作类时 属性,可以子类化属性- 它本身就是一种新型的字体 扩展它的__get__方法,或者你可以 从头定义描述符类型 通过创建一个新样式的类 定义__get__, __set__和 __delete__方法。

注意:下面的方法实际上并不适用于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

在搜索了不同的地方之后,我找到了一个定义classproperty的方法 适用于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)

希望这能帮助到一些人:)

Python < 3.9的代码补全友好解决方案

from typing import (
    Callable,
    Generic,
    TypeVar,
)


T = TypeVar('T')


class classproperty(Generic[T]):
    """Converts a method to a class property.
    """

    def __init__(self, f: Callable[..., T]):
        self.fget = f

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