如何不修改类装饰类方法?解决装饰器相关报错咨询
首先说你遇到的AttributeError: 'str' object has no attribute 'func'——这完全是因为你写了word2.func(*args, **kwargs),这里word2是个字符串,它根本没有func这个属性啊!另外还有个隐藏问题:str.split是类的实例方法,当你直接把str.split拿出来用的时候,它是未绑定方法,调用时必须明确把字符串实例作为第一个参数传进去,不能像调用实例方法那样直接传分隔符。
我们来重新写符合你需求的代码——看你的逻辑,应该是想把传入的word加上'bla'之后,再对这个新字符串执行split操作对吧?那修正后的代码应该是这样:
def bla_addition(word): def decorator(func): def wrapper(*args, **kwargs): # 构造要处理的目标字符串 target_word = word + 'bla' # 调用未绑定的split方法,第一个参数是字符串实例,后面是split的参数(分隔符等) return func(target_word, *args, **kwargs) return wrapper return decorator added = bla_addition('blu') split = added(str.split) new_word = split('l') print(new_word) # 输出: ['b', 'ub', 'a']
这样就完全没问题了。如果你实际要装饰的是pandas的DataFrame.agg方法,逻辑是一样的:agg是DataFrame的实例方法,提取后是未绑定方法,装饰器里要确保把DataFrame实例作为第一个参数传给agg,再加上你的自定义逻辑。
你遇到的UnboundLocalError是Python的变量作用域规则搞的鬼。当你在wrapper里写word += 'bla'时,Python会默认把word当成wrapper的局部变量,但你在赋值之前就引用了外层函数bla_addition里的word,这就导致了“局部变量未定义就引用”的错误。你的理解完全正确——直接修改外层函数的参数确实会触发这个问题。
解决这个问题有两种常用方法:
- 用
nonlocal关键字声明变量来源:告诉Python这个word不是局部变量,而是来自外层嵌套作用域的变量:
def bla_addition(word): def decorator(func): def wrapper(*args, **kwargs): nonlocal word word += 'bla' # 用修改后的word执行split return func(word, *args, **kwargs) return wrapper return decorator added = bla_addition('blu') split = added(str.split) new_word1 = split('l') # 此时word变成'blubla',输出['b','ub','a'] new_word2 = split('l') # 此时word变成'blublabla',输出['b','ub','','a']
- 用可变对象包裹变量:比如把
word放到列表里,因为可变对象的修改不会触发Python的局部变量判定:
def bla_addition(word): # 用列表包裹,让word变成可变对象的元素 word_box = [word] def decorator(func): def wrapper(*args, **kwargs): word_box[0] += 'bla' return func(word_box[0], *args, **kwargs) return wrapper return decorator added = bla_addition('blu') split = added(str.split) new_word1 = split('l') # word_box[0]是'blubla' new_word2 = split('l') # word_box[0]是'blublabla'
要注意的是,这两种方式都会让你的装饰器带有状态——多次调用装饰后的函数会持续修改word的值。如果不需要这个状态,建议每次直接构造新字符串(比如target_word = word + 'bla'),而不是修改原变量。
内容的提问来源于stack exchange,提问作者pierre_j

