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


当前回答

这是我的解决方案,也缓存类属性

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

其他回答

没有合理的方法使这个“类属性”系统在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系统,或者一种领域特定的语言。

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

需要修改一个属性,使其能够被类的所有实例看到,并且在调用这些类方法的作用域中没有对类的所有实例的引用。

在我看来,您真正想要的是Observer设计模式。

我找到了一个解决这个问题的简单方法。它是一个叫做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

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

Python 3 !

参见@Amit Portnoy的回答,在python >= 3.9中有一个更清晰的方法


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

幸运的是,使用元类kwarg很简单:

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

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

然后>>> foo。var

“喷火!”

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)

如果你想通过一个实例化的对象访问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