(Python 3)首先,我觉得我的标题不是应该的,所以如果你坚持这个问题并想出一个更好的标题,请随时编辑它。
我最近学习了 Python 装饰器和 Python 注释,所以我写了两个小函数来测试我最近学到的东西。其中一个,被调用wraps
应该模仿 的行为functools wraps
,而另一个,被调用ensure_types
应该检查给定函数并通过它的注释,如果传递给某个函数的参数是正确的。这是我为这些功能提供的代码:
def wraps(original_func):
"""Update the decorated function with some important attributes from the
one that was decorated so as not to lose good information"""
def update_attrs(new_func):
# Update the __annotations__
for key, value in original_func.__annotations__.items():
new_func.__annotations__[key] = value
# Update the __dict__
for key, value in original_func.__dict__.items():
new_func.__dict__[key] = value
# Copy the __name__
new_func.__name__ = original_func.__name__
# Copy the docstring (__doc__)
new_func.__doc__ = original_func.__doc__
return new_func
return update_attrs # return the decorator
def ensure_types(f):
"""Uses f.__annotations__ to check the expected types for the function's
arguments. Raises a TypeError if there is no match.
If an argument has no annotation, object is returned and so, regardless of
the argument passed, isinstance(arg, object) evaluates to True"""
@wraps(f) # say that test_types is wrapping f
def test_types(*args, **kwargs):
# Loop through the positional args, get their name and check the type
for i in range(len(args)):
# function.__code__.co_varnames is a tuple with the names of the
##arguments in the order they are in the function def statement
var_name = f.__code__.co_varnames[i]
if not(isinstance(args[i], f.__annotations__.get(var_name, object))):
raise TypeError("Bad type for function argument named '{}'".format(var_name))
# Loop through the named args, get their value and check the type
for key in kwargs.keys():
if not(isinstance(kwargs[key], f.__annotations__.get(key, object))):
raise TypeError("Bad type for function argument named '{}'".format(key))
return f(*args, **kwargs)
return test_types
据说,到目前为止一切都很好。thewraps
和 theensure_types
都应该用作装饰器。当我定义第三个装饰器时问题就来了,debug_dec
它应该在调用函数及其参数时打印到控制台。功能:
def debug_dec(f):
"""Does some annoying printing for debugging purposes"""
@wraps(f)
def profiler(*args, **kwargs):
print("{} function called:".format(f.__name__))
print("\tArgs: {}".format(args))
print("\tKwargs: {}".format(kwargs))
return f(*args, **kwargs)
return profiler
这也很酷。当我尝试同时使用时,问题就来debug_dec
了ensure_types
。
@ensure_types
@debug_dec
def testing(x: str, y: str = "lol"):
print(x)
print(y)
testing("hahaha", 3) # raises no TypeError as expected
但是,如果我更改调用装饰器的顺序,它就可以正常工作。有人可以帮我理解出了什么问题,除了交换这两行之外是否有任何解决问题的方法?
编辑 如果我添加以下行:
print(testing.__annotations__)
print(testing.__code__.co_varnames)
输出如下:
#{'y': <class 'str'>, 'x': <class 'str'>}
#('args', 'kwargs', 'i', 'var_name', 'key')