【问题标题】:Strange AttributeError only when assignment奇怪的AttributeError仅在赋值时
【发布时间】:2020-11-08 03:20:04
【问题描述】:

我正在实现What is the Python equivalent of static variables inside a function? 中描述的装饰器。在答案中,装饰器具有正常功能,并且在我的环境中也可以使用。

现在我想把装饰器放到一个类方法上。

源代码:

#decorator
def static_variabls(**kwargs):
    def new_func(old_func):
        for key in kwargs:
            setattr(old_func, key, kwargs[key])
        return old_func
    return new_func

class C:
    @static_variabls(counter = 1)
    def f_(self) -> None:
        print(self.f_.counter)
        self.f_.counter += 1

c1 = C()
c1.f_()
c1.f_()
c1.f_()

预期结果:

1
2
3

实际结果:

1
Traceback (most recent call last):
  File "1.py", line 16, in <module>
    c1.f_()
  File "1.py", line 13, in f_
    self.f_.counter += 1
AttributeError: 'method' object has no attribute 'counter'

我不明白为什么这段代码不起作用。根据错误消息,self.f_.counter 不存在但print(self.f_.counter) 有效。这里发生了什么?

【问题讨论】:

    标签: python decorator


    【解决方案1】:

    c1.f_() 等价于C.f_.__get__(c1, C)()(由于描述符协议)。 __get__ 返回实际被调用的 method 对象。您将counter 附加到原始函数对象,这不是method 包装的内容:它包装了def new_func 创建的函数。

    请注意,使用更简单的装饰器也有同样的问题,它对属性及其初始值进行硬编码。

    def add_counter(f):
        f.counter = 1
        return f
    
    
    class C:
        @add_counter
        def f_(self) -> None:
            print(self.f_.counter)
            self.f_.counter += 1
    

    甚至没有装饰器:

    class C:
        def f_(self) -> None:
            print(self.f_.counter)
            self.f_.counter += 1
    
        f_.counter = 1
    

    【讨论】:

    • 原来的函数对象和new_func()创建的函数对象有区别吗?我认为装饰器会在适当的位置分配属性。
    • 装饰器可以,但你不是这种情况。您定义了一个新函数,然后在不调用它的情况下返回它。在您实际调用该函数之前,原始函数不会获得其新属性。我还不太确定如何解决这个问题;不过,我可能会删除这个答案。
    • 真的吗?我认为@decorator(&lt;arg(s)&gt;) def f 根据the official documentation 定义decorator(&lt;arg(s)&gt;)(f)。如果在OP中没有调用new_func,为什么print(self.f_.counter)会成功?
    • 是的,但是还有描述符协议的问题需要处理。
    • 好的,我想我至少可以解释为什么它第一次有效,但在第二次和后续尝试中失败。让我在另一个答案中写下来;在这个过程中,我可能会弄清楚解决方案是什么。
    【解决方案2】:

    您不能将变量函数对象定义/使用为静态成员 ,然后使用self 访问它们。

    如果是self.f_.counter,我猜print() 函数可以工作,因为它尝试在您的代码f_.counter 中直接访问(类本身的)内存地址,而不是绑定使用self 地址。,它与 ,C.f_ adderss 本身绑定。

    解决方案 - 1

    def static_variabls(**kwargs):
      
      def new_func(old_func):
        
        for key in kwargs:
          setattr(old_func, key, kwargs[key])
    
        return old_func
    
      return new_func
    
    class C:
        @static_variabls(counter = 1)
        def f_(self) -> None:
            print(C.f_.counter) # OR self.f_.counter
            C.f_.counter += 1
    

    解决方案 - 2(不使用装饰器)

    class C:
      
      counter = 0 # static variable
    
      def __init__(self):
        C.counter += 1
        print(self.counter)
    

    c1 = C()
    c1 = C()
    c1 = C()
    

    【讨论】:

      猜你喜欢
      • 2011-04-08
      • 1970-01-01
      • 2013-08-19
      • 1970-01-01
      • 2012-12-15
      • 2014-11-04
      • 1970-01-01
      • 2015-04-01
      相关资源
      最近更新 更多