"懒"在__init__自动初始化的属性

时间:2017-02-21 11:54:47

标签: python python-3.x python-decorators

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

以下作品已经开始:

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)

然而,摆脱self.inputself.do_stuff__init__的手动调用会很快好 - 这很快就会变得乏味。

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

我可以想象的一种方法是为返回的decorated对象提供一些标记属性,并为Test编写一个元类,查看所有方法,收集带有此标记的方法,并以某种方式创建他们的初始化者。我没有实现这个,因为我非常不熟悉元类,property描述符不允许我添加属性。

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

2 个答案:

答案 0 :(得分:1)

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

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

演示输出:

>>> 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']

答案 1 :(得分:0)

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

首先,在装饰器中,在graph_property装饰器上设置一个变量,以便标记要初始化的方法。 由于property对象与函数不同,因此无法为其分配任意属性,因此修复此问题是将Python的本机属性包装在用户定义的类中:

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

然后,在所有其他课程的Base或mixin课程上,执行以下操作:

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)

那应该是它。