由 Python 的 Ellipsis 到 *, /, *args, **kwargs 函数参数
早先对 Python *args, **kwargs 参数有所了解,也知道参数列表中的
如 FastAPI 官方文档 Request Forms and Files 中的
查看 FastAPI 的 Form 类
这样看
不定长的 Tuple, 可用 Ellipsis 指定
上面代码用
如果返回的值是
自己设想 Callable 就一个 int 的参数,不过
这时候你的回调函数写多少个参数都行,如
特殊函数参数
当单独的
比如对于一个常见的函数声明
简单的规则就是:
在 FastAPI 框架中声明函数常用
下面代码逐个验证
思考并复习上一节的内容:
因此,基于
args 是一个列表,怎么去理解这一个调用过程呢?传入单个值的,在函数中变成了一个列表,看着想是
同样的理解方式,把
再一个较典型的例子就是装饰器中,因为装饰器需要由外部的装饰器函数调用另一个被装饰的函数,而被修饰的函数的参数是不定的
这是摘自我之前 熟悉和应用 Python 的装饰器 一文中的例子
链接:
永久链接 https://yanbin.blog/python-ellipsis-star-slash-args-kwargs/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
/ 表示 Positional Only, * 很少见。然而在使用 FastAPI 时看到路由函数中表示默认值采用了 ... 的方式又重新激发起我对 Python 函数参数的 *, /, *args, 和 **kwargs 的兴趣。如 FastAPI 官方文档 Request Forms and Files 中的
@app.post("/files/")默认值的
async def create_file(file: bytes = File(...), fileb: UploadFile = File(...), token: str = Form(...)):
File(...), Form(...), 起初还以为 ... 只是真正意义上的省略号,使用时需传入适当的参数,后来发现 ... 居然是一个 Python 实实在在的内置对象。>>> ...
Ellipsis
>>> id(...)
4473082960
>>> id(...)
4473082960
>>> bool(...)
True
>>> type(...)
<class 'ellipsis'>
... 是一个单例的 Ellipsis 对象,它的 bool 值为 True。查看 FastAPI 的 Form 类
1def Form( # noqa: N802
2 default: Any,
3 *,
4 media_type: str = "application/x-www-form-urlencoded",
5 alias: Optional[str] = None,
6 title: Optional[str] = None,
7 description: Optional[str] = None,
8 gt: Optional[float] = None,
9 ge: Optional[float] = None,
10 lt: Optional[float] = None,
11 le: Optional[float] = None,
12 min_length: Optional[int] = None,
13 max_length: Optional[int] = None,
14 regex: Optional[str] = None,
15 example: Any = Undefined,
16 examples: Optional[Dict[str, Any]] = None,
17 **extra: Any,
18) -> Any:Form(...) 应该是把 ... 传给了第一个参数 default: Any. 验证一下1>>> def foo(a, *, b=0, c=1):
2... print(a, b, c)
3...
4...
5>>> foo(...)
6Ellipsis 0 1这样看
... 没什么特别的,纯粹就是一个 Python 常量。... 在 Type Hints 中的使用
不定长的 Tuple, 可用 Ellipsis 指定1from typing import Tuple
2
3def foo() -> Tuple[int, int]:
4 return (1, 2, 3)上面代码用
mypy test.py 校验会出报错test.py:4: error: Incompatible return value type (got "Tuple[int, int, int]", expected "Tuple[int, int]")但在函数中可因不用条件返回不同长度的 Tuple, 这时就得用
Found 1 error in 1 file (checked 1 source file)
..., 写成1from typing import Tuple
2
3def foo() -> Tuple[int, ...]: # 表示不定长,但类型全为 int
4 return (1, 2, 3)mypy test.py 检验通过。如果返回的值是
(1, 2, 'str') 也是不行的,错误是test.py:4: error: Incompatible return value type (got "Tuple[int, int, str]", expected "Tuple[int, ...]")Callable 类型提示时第一个参数必须是
Found 1 error in 1 file (checked 1 source file)
..., 假如写下面的代码1from typing import Callable
2
3def foo() -> Callable[int, int]:
4 return lambda x: 1自己设想 Callable 就一个 int 的参数,不过
mypy test.py 的说法是test.py:3: error: The first argument to Callable must be a list of types or "..."Callable[..., int] 的第一个位置上还必须为
Found 1 error in 1 file (checked 1 source file)
..., 想明确也不行,所以应该写成1from typing import Callable
2
3def foo() -> Callable[..., int]:
4 return lambda x: 1这时候你的回调函数写多少个参数都行,如
return lambda x, y: 1 也能通过 mypy 这一关。特殊函数参数 / 和 *
当单独的 / 或 * 出现在函数参数中,它们不是用来接收参数值,而是用以界定哪些参数只能按位置传递,哪些参数只能用关键传递,哪些既能用位置又能用关键字传递。比如对于一个常见的函数声明
def foo(a, b, c, d=0)我们的调用方式是随意的
foo(1, 2, 3, 4)Python 函数中没有默认值的参数要放前面,它们渴望通过位置对应来传递,如果第一个参数用了关键字,后面的参数都必须以同样的方式,否则顺序就会错乱。报的错误信息是
foo(1, 2, c=3, 4) # 这是错误的,一旦开始了以关键字传递参数,则后面的参数都必须指定关键字来传递
foo(b=2, a=1, c=3)
SyntaxError: positional argument follows keyword argument看 Python 官方对
/ 和 * 的解释,见 Special parameters1def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
2 ----------- ---------- ----------
3 | | |
4 | Positional or keyword |
5 | - Keyword only
6 -- Positional only简单的规则就是:
/ 前只能按位置传递 (Positional-Only), * 后只能按关键字传递 (Keyword-Only),/ 后 和 * 前(中间)的随意。因为 / 管前面,* 管后面,所以一个函数中同时有 / 和 * 时,/ 必须写成 * 前面。在 FastAPI 框架中声明函数常用
*, 它的一个约定是对于必需的无默认值的参数放在 * 前面,可用位置或关键字来传递,相应的有默认值的参数放在 * 后面作为可选参数,我们也可以借鉴这种用法。下面代码逐个验证
/和 * 的功能 1>>> def standard_arg(arg):
2... pass
3...
4>>> standard_arg(1)
5>>> standard_arg(arg=1)
6>>>
7>>> def pos_only_arg(arg, /):
8... pass
9...
10>>> pos_only_arg(1)
11>>> pos_only_arg(arg=1)
12Traceback (most recent call last):
13 File "<input>", line 1, in <module>
14 pos_only_arg(arg=1)
15TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'
16>>>
17>>> def kwd_only_arg(*, arg):
18... pass
19...
20>>> kwd_only_arg(arg=1)
21>>> kwd_only_arg(1)
22Traceback (most recent call last):
23 File "<input>", line 1, in <module>
24 kwd_only_arg(1)
25TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
26>>>
27>>> def combined_example(pos_only, /, standard, *, kwd_only):
28... print(pos_only, standard, kwd_only)
29...
30...
31>>> combined_example(1, 2, kwd_only=3)
321 2 3
33>>> combined_example(1, standard=2, kwd_only=3)
341 2 3
35>>> combined_example(post_only=1, standard=2, kwd_only=3)
36Traceback (most recent call last):
37 File "<input>", line 1, in <module>
38 combined_example(post_only=1, standard=2, kwd_only=3)
39TypeError: combined_example() got an unexpected keyword argument 'post_only'
40>>> combined_example(1, standard=2, 3)
41 File "<bpython-input-141>", line 1
42 combined_example(1, standard=2, 3)
43 ^
44SyntaxError: positional argument follows keyword argument*args, **kwargs 函数参数
首先args 和 kwargs 是两个参数命名的习俗,按自己的喜爱可以用任意的词,如 *aaa, **bbb,不过最好是按照大家的习惯来命名。* 和 ** 可用于拆解参数,如1>>> def foo(a, b, c):
2... print(a, b, c)
3...
4...
5>>> foo(*[2, 3, 1]) # 把列表拆了依次按位置对应参数
62 3 1
7>>> foo(**{"a":2, "b":3, "c": 1}) # 把字典拆了按关键字对应参数
82 3 1思考并复习上一节的内容:
- 如果定义 def foo(*, a, b, c) 是否能用 foo(*[2,3,1]) 来调用呢?
- 如果定义 def foo(a, b, c, /) 是否能用 foo(**{"a":2, "b":3, "c":1}) 来调用呢?
因此,基于
* 和 ** 分别拆解对应的列表和字典的行为,当它们被安放在函数参数名前面也是一样的用法。*args 相当于是可变参数, 在函数中对应一个列表,在 Java 中就是 Object... args1>>> def foo(*args):
2... print(type(args))
3... print(args)
4...
5...
6>>> foo(2,3,1)
7<class 'tuple'>
8(2 3 1)args 是一个列表,怎么去理解这一个调用过程呢?传入单个值的,在函数中变成了一个列表,看着想是
* 拆解的逆过程。可以这么理解,把 *args 整体看作一个参数,那么可以想像该参数 *args 是一个已拆解的列表,那么去掉 * 号的 args 就是列表本身。所以传参时是这么对应的单个值的类似的2,3,1=>*args, 而不是args
**kwargs 就是不定数量的按关键字传递的参数1>>> def foo(**kwargs):
2... print(type(kwargs))
3... print(kwargs)
4...
5...
6>>> foo(a=2, c=3, b=1)
7<class 'dict'>
8{'a': 2, 'c': 3, 'b': 1}同样的理解方式,把
**kwargs 整体当作一个参数,传参时对应关系就是a=2, c=3, b=1 => **kwargs, 已拆解的字典小结一下 *args 和 **kwargs 参数的使用,基于 *, ** 分别是列表和字典的拆解,得到下面的理解- 看到
*args参数,我们要传入一个被拆解的列表,如 2,3,1。已有列表需拆解后传入, 如 foo(*[2,3,1]) - 看到
**kwargs参数,我们要传入一个被拆解的字典,如 a=2,c=3,b=1。已有字典需拆解后传入, 如 foo(**{"a":2, "c":3, "b":1})
*args 和 **kwargs 任意一种方式都可以帮我创建无限行为的函数, 通中会结合二者来向一个未知函数传递所有的参数 1>>> def foo(fn, *args, **kwargs):
2... fn(*args, **kwargs)
3...
4...
5>>> def bar(a, b, *, c):
6... print(a, b, c)
7...
8...
9>>> foo(bar, 1, 3, c=4)
101 3 4再一个较典型的例子就是装饰器中,因为装饰器需要由外部的装饰器函数调用另一个被装饰的函数,而被修饰的函数的参数是不定的
1def my_decorator(func):
2 def wrapper(*args, **kwargs):
3 print(f"before calling {func.__name__}")
4 func(*args, **kwargs)
5 print(f"after calling {func.__name__}")
6
7 return wrapper
8
9
10@my_decorator
11def say_hello(firstname, lastname, **kwargs):
12 print(f"Hello {firstname} {lastname}!", kwargs)
13
14
15say_hello("Steve", "Jobs", company="Apple", country="USA")这是摘自我之前 熟悉和应用 Python 的装饰器 一文中的例子
链接:
永久链接 https://yanbin.blog/python-ellipsis-star-slash-args-kwargs/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。