如何创建类属性?
在 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
我上面使用的语法是否可行,或者是否需要更多内容?
我想要类属性的原因是这样我可以延迟加载类属性,这似乎很合理。
In python I can add a method to a class with the @classmethod
decorator. Is there a similar decorator to add a property to a class? I can better show what I'm talking about.
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
Is the syntax I've used above possible or would it require something more?
The reason I want class properties is so I can lazy load class attributes, which seems reasonable enough.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
我将这样做:
当我们调用
Bar.bar
时,setter 不起作用,因为我们正在调用TypeOfBar.bar.__set__
,它不是Bar.bar.__set__
。添加元类定义可以解决这个问题:
现在一切都会好起来的。
Here's how I would do this:
The setter didn't work at the time we call
Bar.bar
, because we are callingTypeOfBar.bar.__set__
, which is notBar.bar.__set__
.Adding a metaclass definition solves this:
Now all will be fine.
如果您按如下方式定义
classproperty
,那么您的示例将完全按照您的要求工作。需要注意的是,您不能将其用于可写属性。
eI = 20
将引发AttributeError
,而Example.I = 20
将覆盖属性对象本身。If you define
classproperty
as follows, then your example works exactly as you requested.The caveat is that you can't use this for writable properties. While
e.I = 20
will raise anAttributeError
,Example.I = 20
will overwrite the property object itself.[基于python 3.4编写的答案;元类语法与 2 不同,但我认为该技术仍然有效]
您可以使用元类来完成此操作......大多数情况下。 Dappawit 几乎可以工作,但我认为它有一个缺陷:
这会为你提供 Foo 上的类属性,但有一个问题......
这里到底发生了什么?为什么我无法从实例访问类属性?
在找到我认为的答案之前,我为此苦苦思索了很长一段时间。 Python @properties 是描述符的子集,并且来自描述符文档(强调矿):
因此,方法解析顺序不包括我们的类属性(或元类中定义的任何其他内容)。 可以创建一个行为不同的内置属性装饰器的子类,但是(需要引用)我在谷歌上得到的印象是开发人员有一个很好的理由(我不明白)这样做。
这并不意味着我们运气不佳;我们可以很好地访问类本身的属性...并且我们可以从实例中的 type(self) 获取类,我们可以使用它来创建 @property 调度程序:
现在
Foo().thingy
按类和实例的预期工作!如果派生类替换其底层_thingy
(这是最初让我进行此搜索的用例),它也将继续做正确的事情。这对我来说并不是 100% 满意——必须在元类和对象类中进行设置,感觉违反了 DRY 原则。但后者只是一个单线调度程序;我基本上对它的存在感到满意,如果你真的想要的话,你可以将它压缩为 lambda 或其他东西。
[answer written based on python 3.4; the metaclass syntax differs in 2 but I think the technique will still work]
You can do this with a metaclass...mostly. Dappawit's almost works, but I think it has a flaw:
This gets you a classproperty on Foo, but there's a problem...
What the hell is going on here? Why can't I reach the class property from an instance?
I was beating my head on this for quite a while before finding what I believe is the answer. Python @properties are a subset of descriptors, and, from the descriptor documentation (emphasis mine):
So the method resolution order doesn't include our class properties (or anything else defined in the metaclass). It is possible to make a subclass of the built-in property decorator that behaves differently, but (citation needed) I've gotten the impression googling that the developers had a good reason (which I do not understand) for doing it that way.
That doesn't mean we're out of luck; we can access the properties on the class itself just fine...and we can get the class from
type(self)
within the instance, which we can use to make @property dispatchers:Now
Foo().thingy
works as intended for both the class and the instances! It will also continue to do the right thing if a derived class replaces its underlying_thingy
(which is the use case that got me on this hunt originally).This isn't 100% satisfying to me -- having to do setup in both the metaclass and object class feels like it violates the DRY principle. But the latter is just a one-line dispatcher; I'm mostly okay with it existing, and you could probably compact it down to a lambda or something if you really wanted.
如果您使用 Django,它有一个内置的
@classproperty
装饰器。对于 Django 4,请使用:
If you use Django, it has a built in
@classproperty
decorator.For Django 4, use:
我认为您可以使用元类来做到这一点。因为元类可以像类的类一样(如果这有意义的话)。我知道您可以为元类分配一个 __call__() 方法来覆盖调用类
MyClass()
。我想知道在元类上使用property
装饰器的操作是否类似。哇,它有效了:
注意:这是 Python 2.7 中的。 Python 3+ 使用不同的技术来声明元类。使用:
class MyClass(metaclass=MetaClass):
,去掉__metaclass__
,其余相同。I think you may be able to do this with the metaclass. Since the metaclass can be like a class for the class (if that makes sense). I know you can assign a
__call__()
method to the metaclass to override calling the class,MyClass()
. I wonder if using theproperty
decorator on the metaclass operates similarly.Wow, it works:
Note: This is in Python 2.7. Python 3+ uses a different technique to declare a metaclass. Use:
class MyClass(metaclass=MetaClass):
, remove__metaclass__
, and the rest is the same.据我所知,如果不创建新的元类,就无法为类属性编写 setter。
我发现以下方法有效。使用您想要的所有类属性和设置器定义一个元类。 IE,我想要一个带有
title
属性和 setter 的类。这是我写的:现在像平常一样创建您想要的实际类,除了让它使用您上面创建的元类。
如果我们只在单个类上使用它,像上面那样定义这个元类有点奇怪。在这种情况下,如果您使用 Python 2 样式,您实际上可以在类主体内定义元类。这样它就不会在模块范围中定义。
As far as I can tell, there is no way to write a setter for a class property without creating a new metaclass.
I have found that the following method works. Define a metaclass with all of the class properties and setters you want. IE, I wanted a class with a
title
property with a setter. Here's what I wrote:Now make the actual class you want as normal, except have it use the metaclass you created above.
It's a bit weird to define this metaclass as we did above if we'll only ever use it on the single class. In that case, if you're using the Python 2 style, you can actually define the metaclass inside the class body. That way it's not defined in the module scope.
我碰巧想出了一个与@Andrew非常相似的解决方案,只有 DRY
这具有最好的答案:
“元属性”被添加到类中,因此它仍然是实例的属性
在我的例子中,我实际上将 _thingy 自定义为不同的对于每个孩子,无需在每个类中定义它(并且没有默认值):
I happened to come up with a solution very similar to @Andrew, only DRY
This has the best of all answers:
The "metaproperty" is added to the class, so that it will still be a property of the instance
In my case, I actually customized
_thingy
to be different for every child, without defining it in each class (and without a default value) by:如果您只需要延迟加载,那么您可以只使用类初始化方法。
但元类方法看起来更干净,并且行为更可预测。
也许您正在寻找的是 Singleton 设计模式。有一个很好的SO QA在 Python 中实现共享状态。
If you only need lazy loading, then you could just have a class initialisation method.
But the metaclass approach seems cleaner, and with more predictable behavior.
Perhaps what you're looking for is the Singleton design pattern. There's a nice SO QA about implementing shared state in Python.