关于python:在__init__自动初始化的“Lazy”属性

“Lazy” property that gets initialized automatically at __init__

我想使用一些类似于通常的惰性属性修饰器的东西,但是由于TensorFlow是如何工作的以及如何使用它,我需要在最新的__init__处自动初始化所有的惰性属性(TensorFlow部分不是问题的一部分,但请参阅此处了解我的意思)。"初始化"的意思是调用getattr运行一次属性方法并缓存结果。

以下工作已经开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import functools

def graph_property(getter):
    property_name = getter.__name__
    attribute = '_cache_' + property_name

    @property
    @functools.wraps(getter)
    def decorated(self):
        if not hasattr(self, attribute):
            setattr(self, attribute, getter(self))
            self._graph.append(property_name) # for illustration
            print('Initializing ' + property_name)
        return getattr(self, attribute)

    return decorated


class Test:
    def __init__(self):
        self._graph = []
        self.inputs    # DON'T LIKE TO DO THIS
        self.do_stuff  # AND THIS

    @graph_property
    def inputs(self):
        return 42.0

    @graph_property
    def do_stuff(self):
        return self.inputs + 1.0


if __name__ == '__main__':
    t = Test()
    print(t._graph)

不过,最好不要在__init__中手动调用self.inputself.do_stuff,这很快就会变得单调乏味。

我在考虑多种方法来"记住"哪些属性是graph_property在列表中的某个位置,但我认为,所有这些都必须失败,因为在应用decorator时,该类还不知道(更不用说self)。

我可以想象的一种工作方式是给返回的decorated对象一些标记属性,并为Test编写一个元类,它查看所有方法,收集带有此标记的方法,并以某种方式为它们创建初始值设定项。我未能实现这一点,因为我对元类不太熟悉,并且property描述符不允许我添加属性。

所述方法是否可行(如果可行,如何实现)?或者有一个更简单的方法(没有手动开销,语法也同样好)而我只是没有看到它?


您可以添加一个简单的mixin并定义property的子类,然后在mixin的__init__方法中执行与此自定义属性相关的所有初始化。通过这种方式,您可以选择希望它们在哪个类中初始化,以及不希望它们在什么时候初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import functools


class lazy_property(property):
   """
    This class will help us in identifying our lazy properties, so that we
    don't confuse them with normal properties.
   """

    pass

def graph_property(getter):
    property_name = getter.__name__
    attribute = '_cache_' + property_name

    @lazy_property
    @functools.wraps(getter)
    def decorated(self):
        if not hasattr(self, attribute):
            setattr(self, attribute, getter(self))
            self._graph.append(property_name)  # for illustration
            print('Initializing ' + property_name)
        return getattr(self, attribute)

    return decorated

class InitializeLazyPropertiesMixin:
   """
    This mixin does all of the work of initializing lazy properties
   """

    def __init__(self):
        cls = type(self)
        fields = (k for k in dir(cls) if isinstance(getattr(cls, k), lazy_property))
        for field in fields:
            getattr(self, field)


class Test(InitializeLazyPropertiesMixin):
    def __init__(self):
        self._graph = []
        # Whenever you're inheriting from this mixin make sure to call
        # super `__init__` method.
        super().__init__()

    @graph_property
    def inputs(self):
        return 42.0

    @graph_property
    def do_stuff(self):
        return self.inputs + 1.0

class Test1:
   """
    Just another class that doesn't require initializing any of the lazy properties
   """

    def __init__(self):
        self._graph = []

    @graph_property
    def inputs(self):
        return 42.0

    @graph_property
    def do_stuff(self):
        return self.inputs + 1.0

演示输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> t = Test()
Initializing inputs
Initializing do_stuff
>>> print(t._graph)
['inputs', 'do_stuff']
>>> t = Test1()
>>> print(t._graph)
[]
>>> t.inputs
Initializing inputs
42.0
>>> t._graph
['inputs']


由于您可以完全控制属性和类层次结构,因此只需标记要初始化的属性,并在将调用所有属性的基类__init__方法中使用代码。

因此,首先,在您的decorator中,在您的图形属性decorator上设置一个变量,以便它标记要初始化的方法。由于与函数不同,property对象不能被分配任意属性,因此解决方法是将python的本机属性包装在用户定义的类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MarcableProperty(property):
    pass

def graph_property(getter):
    property_name = getter.__name__
    attribute = '_cache_' + property_name

    @MarcableProperty
    @functools.wraps(getter)
    def decorated(self):
        ...

    decorated._graph_initialize = True
    return decorated

然后,在所有其他类的基础类或混合类上,执行以下操作:

1
2
3
4
5
6
7
8
9
def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    for cls_member_name in dir(self.__class__):
         #"dir" is good because it automatically looks
         # at the superclasses as well
         cls_member = getattr(self.__class__, cls_member_name)
         if getattr(cls_member,"_graph_initialize", False):
              # Fetch property, initializing its value:
              getattr(self, cls_member_name)

就这样吧。