Как я могу получить доступ к атрибутам класса отложенных переменных?

#python

#python

Вопрос:

Я создал себе класс lazy variable и использовал его в другом классе. Как я могу затем получить доступ к атрибутам класса отложенных переменных? Я пытался __getattr__ , но безуспешно. Вот пример:

 class lazyobject(object):
    def __init__(self,varname,something='This is the something I want to access'):
        self.varname = varname
        self.something = something

    def __get__(self, obj, type=None):
        if obj.__dict__.has_key(self.varname):
            print "Already computed %s" % self.varname
            return obj.__dict__[self.varname]
        else:
            print "computing %s" % self.varname
            obj.__dict__[self.varname] = "something else"
            return obj.__dict__[self.varname]

class lazyobject2(lazyobject):
    def __getattr__(self):
        return self.something

class dummy(object):
    def __init__(self):
        setattr(self.__class__, 'lazy', lazyobject('lazy'))

class dummy2(object):
    def __init__(self):
        setattr(self.__class__, 'lazy', lazyobject2('lazy'))

d1 = dummy()
d2 = dummy2()

try:
    print "d1.lazy.something - no getattr: ",d1.lazy.something
except:
    print "d2.lazy is already computed - can't get its .something because it's now a string!"
print "d1.lazy - no getattr: ",d1.lazy

try:
    print "d2.lazy.something - has getattr: ",d2.lazy.something
except:
    print "d2.lazy is already computed - can't get its .something because it's now a string!"
print "d2.lazy - no getattr: ",d2.lazy
  

Это выводит:

 d1.lazy.something - no getattr:  computing lazy
d2.lazy is already computed - can't get its .something because it's now a string!
d1.lazy - no getattr:  something else
d2.lazy.something - has getattr:  computing lazy
d2.lazy is already computed - can't get its .something because it's now a string!
d2.lazy - no getattr:  something else
  

Что бы я хотел, чтобы это печатало:

 d1.lazy.something - no getattr:  This is the something I want to access
computing lazy
d1.lazy - no getattr:  something else
  

Приведенный выше пример надуманный, но я надеюсь, что суть понятна. Другой способ сформулировать мой вопрос таков: как я могу обойти __get__ метод при доступе к атрибуту класса?

Ответ №1:

Способ обойти __get__ при доступе к атрибуту класса — это искать его через словарь классов, а не использовать точечный доступ.

Это легко продемонстрировать, используя функциональные объекты. Например:

 >>> class A(object):
        def f(self):
            pass

>>> A.f                         # dotted access calls f.__get__
<unbound method A.f>
>>> vars(A)['f']                # dict access bypasses f.__get__
<function f at 0x101723500>

>>> a = A()
>>> a.f                         # dotted access calls f.__get__
<bound method A.f of <__main__.A object at 0x10171e810>>
>>> vars(a.__class__)['f']      # dict access bypasses f.__get__
<function f at 0x101723500>
  

Другая информация, которую вы упустили, заключается в том, что унаследованный __get__ выполняется перед __getattr__, который выполняется только в том случае, если атрибут не найден. Эта логика управляется __getattribute__ который наследуется от объекта. Итак, если вы хотите обойти __get__, вам нужно будет либо написать новый __get__ в подклассе, либо изменить логику поиска, определив __getattribute__ в подклассе.

Чтобы исправить класс lazyobject2, замените __getattr__ на:

 class lazyobject2(lazyobject):

    def __getattribute__(self, key):
        # bypass __get__
        return object.__getattribute__(self, '__dict__')[key]
  

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

  • object.__getattribute__ управляет логикой поиска.
  • Сначала выполняется поиск __get__, независимо от того, определен он в текущем классе или унаследован.
  • Только если ничего не найдено, выполняется попытка вызвать object.__getattr__.
  • Вышеуказанные три шага выполняются только для точечного поиска.
  • Этот шаг можно обойти, напрямую обратившись к dict через __dict__ или vars().

Полную информацию о логике дескриптора можно найти в этой статье или в этой презентации.