2017-02-21 20 views
0

Я хочу использовать что-то вроде обычного декоратора lazy property, но из-за того, как работает TensorFlow и как я его использую, мне нужно, чтобы все ленивые свойства были инициализированы автоматически на __init__ последний (часть TensorFlow не является частью вопроса, но см. here, что я имею в виду). Под «инициализацией» я имею в виду вызов getattr для запуска метода свойства один раз и кэширования результата.Свойство «Lazy», которое автоматически инициализируется на __init__

следующие работы: уже

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.input и self.do_stuff в __init__ - это быстро становится утомительным.

Я думал о нескольких путях «запоминания», которые являются объектами graph_property где-то в списке, но все, как мне кажется, все должно потерпеть неудачу, поскольку в то время, когда применяется декоратор, класс еще не известен ему (не говоря уже о self).

Один из способов я мог себе представить, чтобы работать дает Возвращаемый объект decorated некоторый атрибут тега, и написать метакласса для Test, который смотрит на все методы, собирает из них с этим тегом, и каким-то образом создает инициализатор для них. Я не смог реализовать это, потому что я очень не знаком с метаклассами, а дескриптор property не позволяет добавлять атрибуты.

Будет ли описанный подход осуществимым (если да, каким образом)? Или есть более простой способ (без ручных накладных расходов и с одинаково приятным синтаксисом), и я просто не вижу его?

ответ

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

Ах, этот подклассический трюк красив! У меня есть общий базовый класс, так что это идеально подходит. – phg

+1

Мне просто пришло в голову, что 'vars (type (self)). Items()' не будет включать переменные, определенные в суперклассах, - это просто проверяет дескрипторы на объявленном классе, а не на его предках. – jsbueno

+0

@jsbueno Хороший улов, обновленный для использования 'dir()', который [рекурсивно решается] (http://stackoverflow.com/a/33089239/846892), чтобы получить атрибуты. –

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 

А потом, на базе или подмешать класс для все ваши другие классы, сделайте это:

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) 

И это должно быть так.

+0

Это было именно моя первоначальная идея, но установка переменной на 'результатов decorated' в "AttributeError: 'свойство' объект не имеет атрибута '_graph_initialize'". – phg

+0

Это означает, что 'property' Python использует' __slots__' - просто используйте настраиваемое свойство, которое может быть простым: 'class MyProperty (свойство): pass' – jsbueno

+0

(я обновил ответ на этот пример) и фактически, вам даже нужна отметка, просто проверьте, является ли атрибут класса экземпляром MarcableProperty - точно так же, как в ответе @ Ashwini) – jsbueno