[英]How to process wrapped function arguments in the same order as visible for original function?
我試圖在調用之前檢查函數參數的類型(在這個例子中是foo
)。 我正在使用 python 裝飾器來實現這一點。 我看不到如何以與函數foo
可見的順序相同的順序獲取參數。 在下面的示例中,我得到兩個不同的排序,但具有基本相同的函數調用。
def wrapper(func):
def f(*args, **kwargs):
print([type(x) for x in args] + [type(v) for v in kwargs.values()])
return func(*args, **kwargs)
return f
@wrapper
def foo(a, b, c, d):
print(f"{a} {b} {c} {d}")
foo(10, 12.5, 14, 5.2) # all good: int, float, int, float
foo(10, 12.5, d=5.2, c=14) # not what I want: int, float, float, int
是否有可能以一致的順序獲得論點? 如果不是,那么至少有可能讓它們全部由參數名稱鍵入嗎? 看起來像這樣的東西:
def wrapper(func):
def f(**kwargs):
# kwargs = {'a': 10, 'b': 12.5, 'c': 14, 'd': 5.2}
print([type(v) for v in kwargs.values()])
return func(*args, **kwargs)
return f
foo(10, 12.5, 14, 5.2) # obviously doesn't work
類型檢查有點弱,只要您注釋代碼,注釋就可以工作,但是可以通過使用標准庫中的inspect
來實現更健壯的方法:
它提供對框架的完全訪問,...以及您可能需要的一切。 在這種情況下, inspect.signature
可用於獲取原始函數的簽名以獲取參數的原始順序。 然后只需重新組合參數並將最終組傳遞回原始函數。 更多細節在評論中。
from inspect import signature
def wrapper(func):
def f(*args, **kwargs):
# signature object
sign = signature(func)
# use order of the signature of the function as reference
order = order = dict.fromkeys(sign.parameters)
# update first key-values
order.update(**kwargs)
# update by filling with positionals
free_pars = (k for k, v in order.items() if v is None)
order.update(zip(free_pars, args))
return func(**order)
return f
@wrapper
def foo(a, b, c, d):
print(f"{a} {b} {c} {d}")
foo(10, 12.5, 14, 5.2)
#10 12.5 14 5.2
foo(10, 12.5, d=5.2, c=14)
#10 12.5 14 5.2
該代碼與annotations
兼容:
@wrapper
def foo(a: int, b: float, c: int, d: float) -> None:
print(f"{a} {b} {c} {d}")
注釋的方式,不需要導入:
它是上述代碼的副本,但使用__annotations__
屬性來獲取簽名。 請記住,注解可能有也可能沒有輸出注解
def wrapper(func):
def f(*args, **kwargs):
if not func.__annotations__:
raise Exception('No clue... inspect or annotate properly')
params = func.__annotations__
# set return flag
return_has_annotation = False
if 'return' in params:
return_has_annotation = True
# remove possible return value
return_ = params.pop('return', None)
order = dict.fromkeys(params)
order.update(**kwargs)
free_pars = (k for k, v in order.items() if v is None)
order.update(zip(free_pars, args))
# update with return annotation
if return_has_annotation:
func.__annotations__ = params | {'return': return_}
return func(**order)
return f
@wrapper
def foo(a: int, b: float, c: int, d: float) -> None:
print(f"{a} {b} {c} {d}")
首先要注意的是關鍵字參數的實現是因為順序對它們無關緊要,並且旨在在調用時按名稱將值映射到特定參數。 所以在kwargs
上執行任何特定的命令沒有多大意義(或者至少會讓任何試圖使用你的裝飾器的人感到困惑)。 因此,您可能需要檢查指定了哪些kwargs
並刪除相應的參數類型。
接下來,如果您希望能夠檢查參數類型,您將需要提供一種方法來告訴裝飾器您期望的類型,方法是向它傳遞一個參數(您可以在此處查看更多信息)。 這樣做的唯一方法是傳遞一個字典,將每個參數映射到預期的類型:
@wrapper({'a': int, 'b': int, c: float, d: int})
def f(a, b, c=6.0, d=5):
pass
def wrapper(types):
def inner(func):
def wrapped_func(*args, **kwargs):
# be careful here, this only works if kwargs is ordered,
# for python < 3.6 this portion will not work
expected_types = [v for k, v in types.items() if k not in kwargs]
actual_types = [type(arg) for arg in args]
# substitute these in case you are dead set on checking for key word arguments as well
# expected_types = types
# actual_types = [type(arg) for arg in args)] + [type(v) for v in kwargs.items]
if expected_types != actual_types:
raise TypeError(f"bad argument types:\n\tE: {expected_types}\n\tA: {actual_types}")
func(*args, **kwargs)
return wrapped_func
return inner
@wrapper({'a': int, 'b': float, 'c': int})
def f(a, b, c):
print('good')
f(10, 2.0, 10)
f(10, 2.0, c=10)
f(10, c=10, b=2.0)
f(10, 2.0, 10.0) # will raise exception
現在,在這一切之后,我想指出這個功能在 python 代碼中可能在很大程度上是不需要和不必要的。 Python 被設計為動態類型,因此任何類似於 Python 中的強類型的東西都違背了規律,大多數人都不會期望。
接下來,從 python 3.5 開始,我們可以訪問內置的typing
包。 這使您可以指定希望在函數調用中接收的類型:
def f(a: int, b: float, c: int) -> int:
return a + int(b) + c
現在這實際上不會為您做任何類型斷言,但它會讓您清楚地知道您期望什么類型,並且大多數(如果不是全部)IDE 會給您視覺警告,表明您將錯誤的類型傳遞給函數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.