[英]Count how many arguments passed as positional
如果我有一個 function
def foo(x, y):
pass
從 function 內部,我如何判斷y
是按位置傳遞還是通過其關鍵字傳遞?
我想要類似的東西
def foo(x, y):
if passed_positionally(y):
print('y was passed positionally!')
else:
print('y was passed with its keyword')
所以我得到
>>> foo(3, 4)
y was passed positionally
>>> foo(3, y=4)
y was passed with its keyword
我意識到我最初並沒有指定這一點,但是否可以在保留類型注釋的同時做到這一點? 到目前為止,最佳答案建議使用裝飾器 - 但是,它不會保留返回類型
您可以創建一個裝飾器,如下所示:
def checkargs(func):
def inner(*args, **kwargs):
if 'y' in kwargs:
print('y passed with its keyword!')
else:
print('y passed positionally.')
result = func(*args, **kwargs)
return result
return inner
>>> @checkargs
...: def foo(x, y):
...: return x + y
>>> foo(2, 3)
y passed positionally.
5
>>> foo(2, y=3)
y passed with its keyword!
5
當然,您可以通過允許裝飾器接受 arguments 來改進這一點。 因此,您可以傳遞要檢查的參數。 這將是這樣的:
def checkargs(param_to_check):
def inner(func):
def wrapper(*args, **kwargs):
if param_to_check in kwargs:
print('y passed with its keyword!')
else:
print('y passed positionally.')
result = func(*args, **kwargs)
return result
return wrapper
return inner
>>> @checkargs(param_to_check='y')
...: def foo(x, y):
...: return x + y
>>> foo(2, y=3)
y passed with its keyword!
5
我認為添加functools.wraps
將保留注釋,以下版本還允許對所有 arguments 執行檢查(使用inspect
):
from functools import wraps
import inspect
def checkargs(func):
@wraps(func)
def inner(*args, **kwargs):
for param in inspect.signature(func).parameters:
if param in kwargs:
print(param, 'passed with its keyword!')
else:
print(param, 'passed positionally.')
result = func(*args, **kwargs)
return result
return inner
>>> @checkargs
...: def foo(x, y, z) -> int:
...: return x + y
>>> foo(2, 3, z=4)
x passed positionally.
y passed positionally.
z passed with its keyword!
9
>>> inspect.getfullargspec(foo)
FullArgSpec(args=[], varargs='args', varkw='kwargs', defaults=None,
kwonlyargs=[], kwonlydefaults=None, annotations={'return': <class 'int'>})
_____________HERE____________
在 Python 3.10+ 中引入了新的ParamSpec
類型注釋 ( PEP 612 ),以便更好地指定高階函數中的參數類型。 到目前為止,注釋這個裝飾器的正確方法是這樣的:
import inspect
from functools import wraps
from typing import Callable, ParamSpec, TypeVar, TYPE_CHECKING
T = TypeVar("T")
P = ParamSpec("P")
def check_args(func: Callable[P, T]) -> Callable[P, T]:
"""
Decorator to monitor whether an argument is passed
positionally or with its keyword, during function call.
"""
@wraps(func)
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
for param in inspect.signature(func).parameters:
if param in kwargs:
print(param, 'passed with its keyword!')
else:
print(param, 'passed positionally.')
return func(*args, **kwargs)
return inner
正確保留類型注釋:
if TYPE_CHECKING:
reveal_type(foo(2, 3))
# ─❯ mypy check_kwd.py
# check_kwd.py:34: note: Revealed type is "builtins.int"
# Success: no issues found in 1 source file
最后,如果您要執行以下操作:
def foo(x, y):
if passed_positionally(y):
raise Exception("You need to pass 'y' as a keyword argument")
else:
process(x, y)
你可以這樣做:
def foo(x, *, y):
pass
>>> foo(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes 1 positional argument but 2 were given
>>> foo(1, y=2) # works
或者只允許它們按位置傳遞:
def foo(x, y, /):
pass
>>> foo(x=1, y=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got some positional-only arguments passed as keyword arguments: 'x, y'
>>> foo(1, 2) # works
改編自@Cyttorak 的答案,這是一種維護類型的方法:
from typing import TypeVar, Callable, Any, TYPE_CHECKING
T = TypeVar("T", bound=Callable[..., Any])
from functools import wraps
import inspect
def checkargs() -> Callable[[T], T]:
def decorate(func):
@wraps(func)
def inner(*args, **kwargs):
for param in inspect.signature(func).parameters:
if param in kwargs:
print(param, 'passed with its keyword!')
else:
print(param, 'passed positionally.')
result = func(*args, **kwargs)
return result
return inner
return decorate
@checkargs()
def foo(x, y) -> int:
return x+y
if TYPE_CHECKING:
reveal_type(foo(2, 3))
foo(2, 3)
foo(2, y=3)
Output 是:
$ mypy t.py
t.py:27: note: Revealed type is 'builtins.int'
$ python t.py
x passed positionally.
y passed positionally.
x passed positionally.
y passed with its keyword!
這通常是不可能的。 從某種意義上說:該語言並非旨在讓您區分兩種方式。
您可以設計您的 function 以采用不同的參數 - 位置和命名,並檢查通過了哪個參數,如下所示:
def foo(x, y=None, /, **kwargs):
if y is None:
y = kwargs.pop(y)
received_as_positional = False
else:
received_as_positional = True
問題是,盡管通過使用上述僅位置參數,您可以通過兩種方式獲得y
,但對於檢查 function 簽名的用戶(或 IDE)來說,它不會顯示一次。
我有一種感覺,您只是想知道這一點 - 如果您真的打算將此用於設計 API,我建議您重新考慮您的 API - 行為應該沒有區別,除非兩者都是從用戶的角度來看,明確不同的參數。
也就是說,通往 go 的方法是檢查調用者幀,並檢查調用 function 的位置周圍的字節碼:
In [24]: import sys, dis
In [25]: def foo(x, y=None):
...: f = sys._getframe().f_back
...: print(dis.dis(f.f_code))
...:
In [26]: foo(1, 2)
1 0 LOAD_NAME 0 (foo)
2 LOAD_CONST 0 (1)
4 LOAD_CONST 1 (2)
6 CALL_FUNCTION 2
8 PRINT_EXPR
10 LOAD_CONST 2 (None)
12 RETURN_VALUE
None
In [27]: foo(1, y=2)
1 0 LOAD_NAME 0 (foo)
2 LOAD_CONST 0 (1)
4 LOAD_CONST 1 (2)
6 LOAD_CONST 2 (('y',))
8 CALL_FUNCTION_KW 2
10 PRINT_EXPR
12 LOAD_CONST 3 (None)
14 RETURN_VALUE
因此,如您所見,當y
被稱為命名參數時,調用的操作碼是CALL_FUNCTION_KW
,並且參數的名稱在它之前立即加載到堆棧中。
您可以欺騙用戶並向 function 添加另一個參數,如下所示:
def foo(x,y1=None,y=None):
if y1 is not None:
print('y was passed positionally!')
else:
print('y was passed with its keyword')
我不建議這樣做,但它確實有效
在foo
中,您可以將調用堆棧從traceback
傳遞到positionally
,然后它將解析行,找到調用foo
本身的行,然后使用ast
解析該行以定位位置參數規范(如果有):
import traceback, ast, re
def get_fun(name, ast_obj):
if isinstance(ast_obj, ast.Call) and ast_obj.func.id == name:
yield from [i.arg for i in getattr(ast_obj, 'keywords', [])]
for a, b in getattr(ast_obj, '__dict__', {}).items():
yield from (get_fun(name, b) if not isinstance(b, list) else \
[i for k in b for i in get_fun(name, k)])
def passed_positionally(stack):
*_, [_, co], [trace, _] = [re.split('\n\s+', i.strip()) for i in stack]
f_name = re.findall('(?:line \d+, in )(\w+)', trace)[0]
return list(get_fun(f_name, ast.parse(co)))
def foo(x, y):
if 'y' in passed_positionally(traceback.format_stack()):
print('y was passed with its keyword')
else:
print('y was passed positionally')
foo(1, y=2)
Output:
y was passed with its keyword
筆記:
foo
。 只需要捕獲回溯。foo
調用,此解決方案必須在文件中運行,而不是在 shell 中運行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.