装饰方法:AttributeError:' function'对象没有属性' __ self __'?

时间:2015-12-26 01:03:33

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

我正在使用asyncio来调度在某些相对时间间隔内调用的方法。我决定将调度集中到我编写的类的一个方法中,以减少我的项目逻辑出错的可能性。

每次计划方法完成时都应调用此方法。我虽然在每个方法的末尾添加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 个答案:

答案 0 :(得分:4)

关于装饰者,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

答案 1 :(得分:2)

  

不知何故,装饰我的方法使它成为一种功能。

不正确的。创建该函数,然后进行修饰,然后它成为一个方法。您需要编写一个包装器,在运行时捕获self参数,然后调用实际函数。

答案 2 :(得分:0)

您需要再次包装内部函数以获取对 self 对象的引用。这是一个对我有用的示例代码。 并且在调用装饰器时需要使用 @time()

def timer():
    def inner(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            ts = time.time()
            result = func(self, *args, **kwargs)
            elapsed_time = time.time() - ts
            write_ot = f"Elapsed time for {self.__class__.__name__} is :{elapsed_time: 0.4f}\n"
            with open("profiler_timing.txt", 'a+') as f:
                f.write(write_ot)
            return result
        return wrapper
    return inner