Python Lazy Loading属性不适用于Class方法

时间:2017-12-21 19:37:25

标签: python python-3.x

我有一个类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>"

2 个答案:

答案 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'