首次使用时初始化属性

时间:2019-04-26 14:02:48

标签: python python-3.x

我很好奇哪种更好的模式来创建属性属性,以在第一次使用时初始化其值。下面是一个在主题上有多种变体的课程。

def some_initializer(s):
    return f"Value: {s}"

class Foo(object):
    """ Initialize on first use properties """
    def __init__(self):
        self._prop1 = None

    @property
    def prop1(self):
        """ Existing private member attribute """
        if not self._prop1:
            self._prop1 = some_initializer("prop1")
        return self._prop1

    @property
    def prop2(self):
        """ Create private attribute on demand """
        if not hasattr(self, "_prop2"):
            self._prop2 = some_initializer("prop2")
        return self._prop2

    @property
    def prop3(self):
        """ Create private attribute on demand - shorter """
        self._prop3 = getattr(self, "_prop3", some_initializer("prop3"))
        return self._prop3

    @property
    def prop4(self):
        """ Stash value in attribute with same name as property """
        _prop4 = self.__dict__.get('_prop4')
        if _prop4 is not None:
            return _prop4
        self._prop4 = _prop4 = some_initializer("prop4")
        return _prop4

>> f = Foo()
>> print(f.prop1)
>> print(f.prop2)
>> print(f.prop3)
>> print(f.prop4)
Value: prop1
Value: prop2
Value: prop3
Value: prop4

过去,我使用了prop1,prop2和prop3。最近,我被介绍到prop4变体,虽然在技术上也许是正确的,但我仍然感到困惑。这些变化有什么弊端,或者也许有更好的方法?

编辑:理想情况下,保持属性设置器和删除器装饰器的兼容性也是很好的选择。

3 个答案:

答案 0 :(得分:2)

我只写一个自定义描述符,并使用它:

class cached_property:
    def __init__(self, f):
        self.f = f

    def __get__(self, instance, owner):
        if not instance:
            return self

        res = instance.__dict__[self.f.__name__] = self.f(instance)
        return res

用法:

class C:
    @cached_property
    def prop(self):
        print('you will see me once')
        return 4

答案 1 :(得分:1)

您可以使用functools.lru_cache来记住属性值:

from functools import lru_cache

class Foo(object):

    @property
    @lru_cache()
    def prop(self):
        print("called once")
        return 42


foo = Foo()
print(foo.prop)
print(foo.prop)

答案 2 :(得分:0)

我也想到了描述符,但是想出了这种方法

from weakref import WeakKeyDictionary
def five():
    return 5
def six():
    return 6
def seven():
    return 7

class FirstInit:
    def __init__(self, initializer):
        self.initializer = initializer
        self.data = WeakKeyDictionary()
    def __get__(self, instance, owner):
        try:
            value = self.data[instance]
        except KeyError as e:
            value = self.initializer()
            self.data[instance] = value
        return self.data[instance]

用法:

class F:
    a = FirstInit(five)
    b = FirstInit(six)
    c = FirstInit(seven)
    def __init__(self,name):
        self.name = f'{name}:{self.c}'

>>> f = F('foo')
>>> f.name
'foo:7'
>>> f.a, f.b
(5, 6)
>>> f.a = 'sixteen'
>>> f.a, f.b
('sixteen', 6)
>>> f.b += 13
>>> f.a, f.b
('sixteen', 19)
>>> 

对于带有参数的初始化器

d = {'P1':5, 'P2':6, 'P3':7}
def initializer(which):
    return d[which]

class FirstInit:
    def __init__(self, initializer, prop):
        self.initializer = initializer
        self.prop = prop
        self.data = WeakKeyDictionary()
    def __get__(self, instance, owner):
        try:
            value = self.data[instance]
        except KeyError as e:
            value = self.initializer(self.prop)
            self.data[instance] = value
        return self.data[instance]

class G:
    a = FirstInit(initializer, 'P1')
    b = FirstInit(initializer, 'P2')
    c = FirstInit(initializer, 'P3')
    def __init__(self,name):
        self.name = f'{name}:{self.c}'

...

>>> g = G('foo')
>>> g.name
'foo:7'
>>> g.b += 16
>>> g.a,g.b
(5, 22)
>>> g.a = 'four'
>>> g.a,g.b
('four', 22)
>>>