[英]How can I wrap a python function in a way that works with with inspect.signature?
[英]Can I use inspect.signature to interpret a decorator's (*args, **kwargs) as the decorated function's variables would be?
而是一個棘手的問題。 我希望我做得很好。 為了澄清,這里是一個示例裝飾器:
class my_decorator:
def __init__(self, option=None):
self.option = option
def __call__(self, fn):
@functools.wraps(fn)
def decorated(*args, **kwargs):
fn_args = inspect.signature(fn).parameters
# Code that creates local variables by taking the values that
# arrived in *args and **kwargs in exactly the same way that
# they are inside fn proper.
return fn(*args, **kwargs)
return decorated
如果還不清楚,我們可以通過查看所述裝飾器的應用程序來為提案添加解釋性細節:
@my_decorator()
def my_function(arg1, arg2, arg3, kwarg1=val1, kwarg2=val2)
# In here we see the variables arg1, arg2, arg3, kwarg1 and kwarg2
挑戰在於,我們能否在def decorated(*args, **kwargs):
中使用fn_args
創建變量arg1
、 arg2
、 arg3
、 kwarg1
和kwarg2
。
我有直覺認為這是可以做到的,但這需要一些深入的思考,我想知道這是否不是已經發明的輪子。 我的意思是,也許已經有一種規范的方法可以將函數簽名應用於匿名 *args、**kwargs 以生成與該簽名一致的局部變量。
我願意這樣想。 也就是說,我不是第一個發現這種願望/願望的人。 當然,我有一個真正的風險。 因為這對我來說一般效用似乎很低。 我有一個非常具體的應用程序,其中傳入的option
是格式字符串(樣式為“{arg1},{arg2} ...”),它使用修飾函數的參數列表中的名稱。
好吧,研究現有解決方案的大量努力沒有結果,所以我制定了一個可行的解決方案,盡管我不是 100% 滿意(因為我正在使用 'exec' 這實際上是可選的,主要技巧inspect.signature.parameters
幫助我們在裝飾函數的期望上下文中解釋傳入參數):
from inspect import signature
... then inside 'decorated' ...
allargs = signature(fn).parameters
# allargs is an OrderedDict, the keys of which are the arguments of fn.
# We build therfrom, a list of arguments we are seeking in args and kwargs
seeking = list(allargs.keys())
found = {}
# Methods by convention have the first argument "self". Even if it's
# not a method, setting a local variable 'self' is fraught with issues
# And so we need to remap it. Also if we've explicitly decorated a method
# we remap the first argument. We call it 'selfie' interanally, but in
# provided key_patterns, accept 'self' as a reference.
if seeking[0] == 'self' or is_method:
selfie = args[0]
found['selfie'] = selfie
seeking.pop(0)
sarg = 1
is_method = True
else:
sarg = 0
# For classifying arguments see:
# https://docs.python.org/3/library/inspect.html#inspect.Parameter
#
# We start by consuming all the args.
if seeking:
for arg in args[sarg:]:
# This should never happen, but if someone calls the decorated function
# with more args than the original function can accept that's clearly
# an erroneous call.
if len(seeking) == 0:
raise TooManyArgs(f"Decorated function has been called with {len(args)} positional arguments when only {len(allargs)} args are accepted by the decorated function. ")
# Set a local variable, feigning the conditions that fn would see
# if seeking[0] is 'self' this exhibits odd dysfunctional behaviour
# and so above we mapped 'self' to 'selfie' internall of this decorator.
found[seeking[0]] = arg
exec(f"{seeking[0]} = arg")
seeking.pop(0)
# If we did not find all that we seek by consuming args, consume kwargs
if seeking:
for kwarg, val in kwargs.items():
if kwarg in seeking:
# Should never happen, but if someone calls the decorated function
# with more args than the original function can accept that's clearly
# an erroneous call.
if len(seeking) == 0:
raise TooManyKwargs(f"Decorated function has been called with {len(kwargs)} keyword arguments after {len(args)} positional arguments when only {len(allargs)} args are accepted by the decorated function. ")
arg = seeking.index(kwarg)
found[seeking[arg]] = val
exec(f"{seeking[arg]} = val")
seeking.pop(arg)
if seeking:
# Any that remain we can check for default values
for arg in seeking:
props = allargs[arg]
if props.default != props.empty:
pos = seeking.index(arg)
found[seeking[pos]] = props.default
exec(f"{seeking[pos]} = props.default")
seeking.pop(pos)
# If any remain, then clearly not all the argument fn needs have been supplied
# to its decorated version.
if seeking:
raise TooFewArgs(f"Decorated function expects arguments ({', '.join(seeking)}), which it was not called with.")
這需要一些自定義異常:
class TooManyArgs(Exception):
pass
class TooManyKwargs(Exception):
pass
class TooFewArgs(Exception):
pass
但工作正常。 結果是decorated
中的局部變量,就像它們在裝飾函數中看到的一樣。
雖然現場需要,但在這里使用它:
它在這里使用:
https://pypi.org/project/django-cache-memoized/
並在這里采取行動:
(我們希望結束一個非常明智、經過充分研究的問題的似是而非的投票不會找到 2 個支持者。恕我直言,這是一個非常有趣的問題,而且似乎可行,盡管首選規范方法。)
正如評論中的 user2357112 所述,您可以使用 inspect.signature.bind 獲取參數和關鍵字參數的字典。
你的裝飾器可能是這樣的:
class my_decorator:
def __init__(self, option=None):
self.option = option
def __call__(self, fn):
@functools.wraps(fn)
def decorated(*args, **kwargs):
fn_args = inspect.signature(fn).bind(*args, **kwargs)
# Then if you really needed to make them as variables
# rather than a dict for a reason I can't imagine
locals.update(fn_args)
return fn(*args, **kwargs)
return decorated
如果像我一樣你需要保持 python 2.7 和 python 3 的兼容性,有一個類似的函數inspect.getcallargs對兩者都適用,即使它在 3.5 后已被棄用
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.