带可选参数的装饰器返回函数未执行,是否遗漏关键逻辑?
我写了一个工具函数,用来给普通函数添加可选参数装饰器的语法支持。但发现一个问题:用带参数的装饰器(比如@addToDict("notepad", log="Starting notepad"))装饰的函数,调用时内部的wrapper函数从未执行(看不到"foo"输出);但无参数的@addToDict装饰的函数,调用时wrapper能正常工作。我按照装饰器语法的理解,应该执行装饰器返回的函数,不知道哪里出错了,期望调用startNotepad时能打印wrapper里的"foo"。
工具函数代码
import typing def decoratorAddSupportForOptionalArguments(addToDict: typing.Callable): """Takes a function with arguments, where the first argument is callable and returns a modified version that enables decorator like syntax. """ def addToDictDecorator(*argumentsPassedTo_addToDict, **kargumentsPassedTo_addToDict): kargumentsPassedTo_addToDict_keys = kargumentsPassedTo_addToDict.keys() if len(argumentsPassedTo_addToDict) == 1 and callable(argumentsPassedTo_addToDict[0]) and 0 == len(kargumentsPassedTo_addToDict_keys): returnVal = addToDict(argumentsPassedTo_addToDict[0]) return returnVal if returnVal != None else addToDict # addToDict is being called as a function def decFinal(func): returnVal = addToDict(func, *argumentsPassedTo_addToDict, **kargumentsPassedTo_addToDict) print(f"decFinal {func.__name__} {returnVal}") return returnVal if returnVal != None else addToDict return decFinal return addToDictDecorator
使用示例代码
myDict = dict() import random @decoratorAddSupportForOptionalArguments def addToDict(func, key=None, log=None): print("addToDict") myDict[key if key else str(random.random())] = func def wrapper(*args, **kwargs): # @note This is supposed to be called every time the decorated function gets called. print("foo") # It never gets called however - and I cant figure out whats wrong if log: print(log) func(*args,**kwargs) return wrapper @addToDict def isValid(value): return value != None @addToDict("myFancyKey") def isPrivate(value): return value.__name__.startswith("_") @addToDict() def printRandom(value): print(random.random()) @addToDict("notepad", log="Starting notepad") def startNotepad(): import os print("notepad") os.system("notepad.exe") print(myDict) myDict["notepad"]() myDict["notepad"]() myDict["notepad"]()
问题分析与解决方案
问题根源
核心错误在于addToDict函数中,你把**原始的被装饰函数func**存入了myDict,而不是装饰器返回的wrapper函数。当你调用myDict["notepad"]()时,执行的是未被包装的原始startNotepad函数,自然不会触发wrapper里的"foo"输出。
而无参数装饰的isValid能正常打印"foo",是因为@addToDict语法会把isValid变量替换成wrapper函数——如果你直接调用isValid(),执行的是wrapper;但如果你去myDict里找对应的key调用,同样不会打印"foo",因为字典里存的还是原始func。
解决方案
修改addToDict中存入字典的对象,把func换成wrapper,调整代码顺序确保先定义wrapper再存入:
@decoratorAddSupportForOptionalArguments def addToDict(func, key=None, log=None): print("addToDict") def wrapper(*args, **kwargs): print("foo") if log: print(log) func(*args,**kwargs) # 先定义wrapper,再存入字典 myDict[key if key else str(random.random())] = wrapper return wrapper
这样修改后,myDict里存储的就是包装后的wrapper函数,调用myDict["notepad"]()时就会执行wrapper里的逻辑,打印"foo"和日志信息。
内容的提问来源于stack exchange,提问作者Willy Wonka

