【问题标题】:Decorating methods: AttributeError: 'function' object has no attribute '__self__'?装饰方法:AttributeError:'function'对象没有属性'__self__'?
【发布时间】:2016-03-31 18:41:23
【问题描述】:

我正在与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__'
>>> 

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

    • 注意,这实际上可能不是您真正想要做的。考虑一个修饰方法是否设法在类的不同实例上调用相同的方法(或在线程上下文中......该方法可以在不同实例上并行调用)——现在你真的进入了 混淆状态,其中self.instance 指的是在其上调用方法的实例last。话虽如此,这并不是一个坏例子——我只是不希望人们在不了解后果的情况下复制/粘贴此代码。 . .
    • 为什么在使用类装饰器时传递给thinkagain.__get__ 的参数没有传入,例如thinkagain.__init__
    • 因为如果装饰器有自己的参数,装饰器的@ 只是func = decorator(dec_args)(func)(args) 的语法糖,如果没有,则func = decorator(func)(args)
    【解决方案2】:

    不知何故,装饰我的方法使它成为一个函数。

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

    【讨论】:

    • «然后它变成了一种方法。»属于哪个班级? thinkagain 类还是 DoSomething 类?
    • 属于其class 块定义的类。
    • 函数和方法是一回事。函数有一个描述符 __get__() 方法,这允许它们以方法的方式使用。
    • @JacobZimmerman:不,他们不是。函数只有在从实例访问时才成为方法。
    • @IgnacioVazquez-Abrams 他们只在以这种方式使用时类似于方法,但他们总是有 __get__() (描述符协议的一部分),这允许他们以这种方式行事。相信我,我正在写一本关于描述符的书。如果您指的是成为“方法”对象,那么您是对的,但这只是一个专门的 partial 函数,它预先设置了 self
    【解决方案3】:

    您可能需要再次包装内部函数以获取对您自己对象的引用。这是一个对我有用的示例代码。 并且调用装饰器时需要使用@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
    

    【讨论】:

      猜你喜欢
      • 2016-03-10
      • 2022-01-26
      • 2019-05-17
      • 2017-07-04
      • 2014-06-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多