我有一个类Foo
,它使用Foo.bar
的延迟加载。
class Foo(object):
@property
def bar(self):
if not hasattr(self, '_bar'):
self._initBar()
return self._bar
def _initBar(self):
self._bar = 'bar'
foo = Foo()
print(foo.bar) # prints "bar"
但是,当我尝试将Foo
转换为仅使用类方法时,Foo.bar
并没有给我bar
,而是给予我:
<property object at 0x000001CA1C344728>
为什么不给我bar
?
class Foo(object):
@property
@classmethod
def bar(cls):
if not hasattr(cls, '_bar'):
cls._initBar()
return cls._bar
@classmethod
def _initBar(cls):
cls._bar = 'bar'
print(Foo.bar) # prints "<property object at 0x000001CA1C344728>"
答案 0 :(得分:2)
你可以使用元类,因为property
对象是通过实例访问的,如果你想要的实例是类本身,那么你需要把属性放在类的类上,即元类:
In [37]: class MetaFoo(type):
...: @property
...: def bar(cls):
...: if not hasattr(cls, '_bar'):
...: print("hard at work!")
...: cls._init_bar()
...: return cls._bar
...:
In [38]: class Foo(metaclass=MetaFoo):
...: @classmethod
...: def _init_bar(cls):
...: cls._bar = 'bar'
...:
In [39]: Foo.bar
hard at work!
Out[39]: 'bar'
In [40]: Foo.bar
Out[40]: 'bar'
当然,虽然这可能,但我不知道是否 建议。
正如@jsbueno演示的那样,简单地定义自己的描述符会更加明智,这可以为您提供更灵活的行为。
答案 1 :(得分:2)
property
内置是Python中的一个方便工具,它提供了一个更强大机制的简单用例,即#34;描述符协议&#34;。
基本上,首先检查从实例或类中检索的任何对象,如果它具有__get__
,__set__
或__del__
方法之一。当从实例中检索属性时,property
包装要由__get__
调用的getter函数,但在从类中检索属性对象时返回它。 (这甚至是其他描述符的常见用例)
因此,如果您希望类属性具有property
类似的行为,则必须创建自己的描述符类,运行__get__
方法 - 或者,只需使用元类创建类,并使用property
按原样,在元类上。后者的缺点很多:您需要为每个类创建一个自定义元类,您希望托管类属性只是其中的第一个。另一方面,创建自己的描述符非常简单:
class MyProperty:
def __init__(self, initializer):
self.initializer = initializer
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if not hasattr(owner, "_" + self.name):
initializer = getattr(owner, self.initializer)
initializer()
return getattr(owner, "_" + self.name)
class Foo:
bar = MyProperty("_initBar")
@classmethod
def _initBar(cls):
cls._bar = 'bar'
请注意,__set_name__
仅在Python 3.6上实现。在较旧的Python上,包括Python 2.x,您应该使用:
class MyProperty(object):
def __init__(self, initializer, name):
self.initializer = initializer
self.name = name
def __get__(self, instance, owner):
if not hasattr(owner, "_" + self.name):
initializer = getattr(owner, self.initializer)
initializer(owner)
return getattr(owner, "_" + self.name)
class Foo(object):
bar = MyProperty("_initBar", name='bar')
@classmethod
def _initBar(cls):
cls._bar = 'bar'