2015-12-26 3 views
1

Я работаю с asyncio для того, чтобы методы планирования вызывались при определенных относительных временных интервалах. Я решил централизовать планирование в один метод класса, который я написал, чтобы уменьшить вероятность получения ошибок с логикой моего проекта.Способы отделки: AttributeError: объект 'function' не имеет атрибута '__self__'?

Такой метод следует вызывать каждый раз, когда запланированный метод завершен. Я, однако, добавляю loop.call_soon в конце каждого метода, но я решил сделать снимок decorators.

Я написал декоратор класса, а затем применил его к некоторым методам моего основного класса, написал остальную часть логики и все такое. Но при попытке проверить мои изменения в моем проекте я получаю исключение:

AttributeError: 'function' object has no attribute '__self__' 

Так или иначе, украшающие мой метод сделал это функция. Это то, что я не могу понять, почему это произошло? как я могу обойти это без отказа от декораторов?

Вот минимальный, полный и проверяемый пример того, что я пытаюсь сделать:

import asyncio 
from datetime import datetime 


class thinkagain: 
    loop = asyncio.get_event_loop() 

    def __init__(self, f): 
     self.fun = f 
     self.class_ = f.__self__ 

    def __call__(self): 
     self.fun(*args, **kwords) 
     # everything in Python is an object 
     setattr(self.fun, "called", datetime.utcnow()) 
     self.loop.call_later(self.class_.think, 5 * 60) 


class DoSomething: 
    loop = asyncio.get_event_loop() 

    @thinkagain 
    def think(self): 
     attr = getattr(self.dosomething, "called") 
     if attr: 
      elapsed = attr - datetime.utcnow() 
      seconds = elapsed.seconds 
     else: 
      seconds = 99999 

     if seconds >= 20 * 60: 
      self.loop.call_soon(self.dosomething) 

    @thinkagain 
    def dosomething(self): 
     print("I did something awesome!") 

loop = asyncio.get_event_loop() 
something = DoSomething() 
loop.call_soon(something.think) 
loop.run_forever() 

и здесь исключение я получаю:

Python 3.5.1 (default, Dec 7 2015, 13:41:59) 
[GCC 5.2.0] on linux 
Type "help", "copyright", "credits" or "license" for more information. 
>>> Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "/tmp/mcve.py", line 19, in <module> 
    class DoSomething: 
    File "/tmp/mcve.py", line 22, in DoSomething 
    @thinkagain 
    File "/tmp/mcve.py", line 10, in __init__ 
    self.class_ = f.__self__ 
AttributeError: 'function' object has no attribute '__self__' 
>>> 

ответ

3

Относительно декораторов, Graham Dumpleton дал отличные беседы Advanced methods for creating decorators, обсуждая внутренние реализации различных декоративных ароматов и приемов. Настоятельно рекомендуется.

Соответствующий модуль он представил в конце: https://github.com/GrahamDumpleton/wrapt

Тем не менее, я исправила пример с двумя версиями. Версия ниже хранит атрибуты непосредственно в методах, как вы планировали.

from datetime import datetime 

class thinkagain: 

    def __init__(self, f): 
     # Plain function as argument to be decorated 
     self.func = f 

    def __get__(self, instance, owner): 
     self.instance_ = instance 
     return self.__call__ 

    def __call__(self, *args, **kwargs): 
     """Invoked on every call of any decorated method""" 

     # set attribute directly within bound method 
     bound_method = getattr(self.instance_, self.func.__name__) 
     bound_method.__dict__['called'] = datetime.utcnow() 

     # returning original function with class' instance as self 
     return self.func(self.instance_, *args, **kwargs) 


class DoSomething_A: 

    @thinkagain 
    def think(self, *args, **kwargs): 
     print('\n%s' % locals()) 
     print(self.think.called, args, kwargs) 
     self.dosomething() 

    @thinkagain 
    def dosomething(self): 
     print('%s\n' % ('-'*30), locals()) 
     print("%s I did something awful" % self.dosomething.called) 

Вторая версия выглядит более чистой, и она пропускает сохранение атрибутов внутри методов и назначает их непосредственно внутри экземпляра.

from datetime import datetime 

class thinkagain: 

    def __init__(self, f): 
     # Plain function as argument to be decorated 
     self.func = f 

    def __get__(self, instance, owner): 
     self.instance_ = instance 
     return self.__call__ 

    def __call__(self, *args, **kwargs): 
     """Invoked on every call of decorated method""" 

     # set attribute on instance 
     name = '%s_called' % self.func.__name__ 
     setattr(self.instance_, name, datetime.utcnow()) 

     # returning original function with class' instance as self 
     return self.func(self.instance_, *args, **kwargs) 


class DoSomething_B: 

    @thinkagain 
    def think(self, *args, **kwargs): 
     print('\n%s' % locals()) 
     print(self.think_called) 
     self.dosomething() 

    @thinkagain 
    def dosomething(self): 
     print('%s\n' % ('-'*30), locals()) 
     print(self.dosomething_called) 

Оба производят такой же желаемое поведение:

>>> something = DoSomething_A().think(1, 2) 
{'args': (1, 2), 'kwargs': {}, 'self': <__main__.DoSomething_A object at  0x10209f128>} 
2015-12-26 04:13:25.629887 (1, 2) {} 
------------------------------ 
{'self': <__main__.DoSomething_A object at 0x10209f128>} 
2015-12-26 04:13:25.647476 I did something awful 

и

>>> something = DoSomething_B().think('arg_a', 'arg_b') 
{'args': ('arg_a', 'arg_b'), 'kwargs': {}, 'self': <__main__.DoSomething_B object at 0x10209f208>} 
2015-12-26 04:13:25.648039 
------------------------------ 
{'self': <__main__.DoSomething_B object at 0x10209f208>} 
2015-12-26 04:13:25.648390 
+2

. Обратите внимание: это может быть не совсем то, что вы действительно хотите делать. Подумайте, удалось ли украшенному методу вызвать тот же метод в другом экземпляре класса (или в поточном контексте ... где метод можно было бы вызывать параллельно в разных экземплярах). Теперь вы попадаете в неудобное состояние, в котором 'self.instance' относится к экземпляру, на который метод был вызван _last_. С учетом сказанного, это не плохой пример - я просто не хочу, чтобы люди копировали/вставляли этот код, не понимая последствий. , , – mgilson

+0

Почему аргументы, переданные 'thinkagain .__ get__', не передаются, например,' thinkagain .__ init__' при использовании декораторов классов? – shackra

+1

Поскольку '' '' декоратора является просто синтаксическим сахаром для 'func = decorator (dec_args) (func) (args)', если декоратор имеет собственные аргументы и 'func = decorator (func) (args)', если он этого не делает. –

2

Somehow, decorating my method made it a function.

Неправильно. Функция создана, затем оформляется, и затем становится способом. Вам нужно будет написать оболочку, которая фиксирует аргумент self во время выполнения, а затем вызывает действительную функцию.

+0

«и тогда он становится метод.», Какой класс? класс «thinkagain» или класс «DoSomething»? – shackra

+0

Класс, чей блок 'class' определен в. –

+0

Функции и методы - это одно и то же. Функции имеют дескриптор __get __(), который позволяет использовать их способами. –

Смежные вопросы