在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将覆盖属性对象本身。

其他回答

我碰巧想出了一个与@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)

据我所知,如果不创建一个新的元类,就无法为类属性编写setter。

我发现下面的方法是有效的。定义一个具有所需的所有类属性和setter的元类。IE,我需要一个带有setter的title属性的类。以下是我所写的:

class TitleMeta(type):
    @property
    def title(self):
        return getattr(self, '_title', 'Default Title')

    @title.setter
    def title(self, title):
        self._title = title
        # Do whatever else you want when the title is set...

现在让您想要的实际类正常,除了让它使用您上面创建的元类。

# Python 2 style:
class ClassWithTitle(object):
    __metaclass__ = TitleMeta
    # The rest of your class definition...

# Python 3 style:
class ClassWithTitle(object, metaclass = TitleMeta):
    # Your class definition...

如果我们只在单个类上使用这个元类,那么像上面那样定义它就有点奇怪了。在这种情况下,如果您使用的是python2风格,您实际上可以在类主体中定义元类。这样它就不在模块范围内定义。

[基于python 3.4编写的答案;元类语法在2中有所不同,但我认为该技术仍然有效]

你可以通过元类来实现。Dappawit几乎可以,但我认为它有一个缺陷:

class MetaFoo(type):
    @property
    def thingy(cls):
        return cls._thingy

class Foo(object, metaclass=MetaFoo):
    _thingy = 23

这让你在Foo上获得一个类属性,但有一个问题…

print("Foo.thingy is {}".format(Foo.thingy))
# Foo.thingy is 23
# Yay, the classmethod-property is working as intended!
foo = Foo()
if hasattr(foo, "thingy"):
    print("Foo().thingy is {}".format(foo.thingy))
else:
    print("Foo instance has no attribute 'thingy'")
# Foo instance has no attribute 'thingy'
# Wha....?

这到底是怎么回事?为什么我不能从实例中到达class属性?

在找到我所相信的答案之前,我在这个问题上苦苦思索了很久。Python @properties是描述符的子集,并且,从描述符文档(强调我的):

属性访问的默认行为是获取、设置或删除 属性。例如,a.x有一个查找链 从a.__dict__['x']开始,然后输入(a)。__dict__['x'],并继续 通过类型(a)的基类,不包括元类。

因此方法解析顺序不包括我们的类属性(或元类中定义的任何其他属性)。有可能让内置属性装饰器的子类表现不同,但是(需要引用)我在谷歌上得到的印象是,开发人员有一个很好的理由(我不明白)这样做。

这并不意味着我们不走运;我们可以很好地访问类本身的属性……我们可以从实例中的type(self)中获取类,我们可以使用它来创建@property dispatchers:

class Foo(object, metaclass=MetaFoo):
    _thingy = 23

    @property
    def thingy(self):
        return type(self).thingy

现在Foo()。Thingy对类和实例都像预期的那样工作!如果派生类替换了它的底层_thingy(这是最初让我进行搜索的用例),它也将继续做正确的事情。

这对我来说不是百分之百的满意——必须在元类和对象类中进行设置,感觉它违反了DRY原则。但后者只是一个单行调度器;我基本同意它的存在,如果你真的想,你也可以把它压缩成。

如果你只需要惰性加载,那么你可以只需要一个类初始化方法。

EXAMPLE_SET = False
class Example(object):
   @classmethod 
   def initclass(cls):
       global EXAMPLE_SET 
       if EXAMPLE_SET: return
       cls.the_I = 'ok'
       EXAMPLE_SET = True

   def __init__( self ):
      Example.initclass()
      self.an_i = 20

try:
    print Example.the_I
except AttributeError:
    print 'ok class not "loaded"'
foo = Example()
print foo.the_I
print Example.the_I

但是元类方法看起来更简洁,行为更可预测。

也许您正在寻找的是单例设计模式。关于在Python中实现共享状态,有一个很好的SO QA。

我认为您可以通过元类来实现这一点。因为元类可以像类的类(如果有意义的话)。我知道你可以给元类赋一个__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__,其余部分相同。