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


当前回答

一半的解决方案,类上的__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

其他回答

尝试一下,它可以在不更改/添加大量现有代码的情况下完成工作。

>>> 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

属性函数需要两个可调用参数。给它们lambda包装器(它将实例作为第一个参数传递),一切都很好。

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)

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

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

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

一半的解决方案,类上的__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