在python中,我可以使用@classmethod装饰器向类中添加方法。是否有类似的装饰器可以将属性添加到类中?我可以更好地展示我在说什么。

class Example(object):
   the_I = 10
   def __init__( self ):
      self.an_i = 20

   @property
   def i( self ):
      return self.an_i

   def inc_i( self ):
      self.an_i += 1

   # is this even possible?
   @classproperty
   def I( cls ):
      return cls.the_I

   @classmethod
   def inc_I( cls ):
      cls.the_I += 1

e = Example()
assert e.i == 20
e.inc_i()
assert e.i == 21

assert Example.I == 10
Example.inc_I()
assert Example.I == 11

我上面使用的语法是可能的还是需要更多的东西?

我需要类属性的原因是我可以延迟加载类属性,这似乎很合理。


当前回答

如果您按如下方式定义classproperty,那么您的示例将完全按照您的要求工作。

class classproperty(object):
    def __init__(self, f):
        self.f = f
    def __get__(self, obj, owner):
        return self.f(owner)

需要注意的是,您不能将此用于可写属性。While . i = 20将引发AttributeError, Example。I = 20将覆盖属性对象本身。

其他回答

如果您按如下方式定义classproperty,那么您的示例将完全按照您的要求工作。

class classproperty(object):
    def __init__(self, f):
        self.f = f
    def __get__(self, obj, owner):
        return self.f(owner)

需要注意的是,您不能将此用于可写属性。While . i = 20将引发AttributeError, Example。I = 20将覆盖属性对象本身。

如果你使用Django,它有一个内置的@classproperty装饰器。

from django.utils.decorators import classproperty

对于Django 4,使用:

from django.utils.functional import classproperty

我碰巧想出了一个与@Andrew非常相似的解决方案,只是DRY

class MetaFoo(type):

    def __new__(mc1, name, bases, nmspc):
        nmspc.update({'thingy': MetaFoo.thingy})
        return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)

    @property
    def thingy(cls):
        if not inspect.isclass(cls):
            cls = type(cls)
        return cls._thingy

    @thingy.setter
    def thingy(cls, value):
        if not inspect.isclass(cls):
            cls = type(cls)
        cls._thingy = value

class Foo(metaclass=MetaFoo):
    _thingy = 23

class Bar(Foo)
    _thingy = 12

这是最好的答案:

“元属性”被添加到类中,因此它仍然是实例的属性

不需要在任何类中重新定义东西 该属性在实例和类中都作为“类属性”工作 您可以灵活地自定义_thingy的继承方式

在我的例子中,我实际上为每个子定制了_thingy,没有在每个类中定义它(并且没有默认值):

   def __new__(mc1, name, bases, nmspc):
       nmspc.update({'thingy': MetaFoo.services, '_thingy': None})
       return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)

我认为您可以通过元类来实现这一点。因为元类可以像类的类(如果有意义的话)。我知道你可以给元类赋一个__call__()方法来覆盖调用类MyClass()。我想知道在元类上使用属性装饰器的操作是否类似。

哇,真管用:

class MetaClass(type):    
    def getfoo(self):
        return self._foo
    foo = property(getfoo)
    
    @property
    def bar(self):
        return self._bar
    
class MyClass(object):
    __metaclass__ = MetaClass
    _foo = 'abc'
    _bar = 'def'
    
print MyClass.foo
print MyClass.bar

注意:这是在Python 2.7中。Python 3+使用不同的技术来声明元类。使用:class MyClass(metaclass= metaclass):,删除__metaclass__,其余部分相同。

def _create_type(meta, name, attrs):
    type_name = f'{name}Type'
    type_attrs = {}
    for k, v in attrs.items():
        if type(v) is _ClassPropertyDescriptor:
            type_attrs[k] = v
    return type(type_name, (meta,), type_attrs)


class ClassPropertyType(type):
    def __new__(meta, name, bases, attrs):
        Type = _create_type(meta, name, attrs)
        cls = super().__new__(meta, name, bases, attrs)
        cls.__class__ = Type
        return cls


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

    def __get__(self, obj, owner):
        if self in obj.__dict__.values():
            return self.fget(obj)
        return self.fget(owner)

    def __set__(self, obj, value):
        if not self.fset:
            raise AttributeError("can't set attribute")
        return self.fset(obj, value)

    def setter(self, func):
        self.fset = func
        return self


def classproperty(func):
    return _ClassPropertyDescriptor(func)



class Bar(metaclass=ClassPropertyType):
    __bar = 1

    @classproperty
    def bar(cls):
        return cls.__bar

    @bar.setter
    def bar(cls, value):
        cls.__bar = value

bar = Bar()
assert Bar.bar==1
Bar.bar=2
assert bar.bar==2
nbar = Bar()
assert nbar.bar==2