pip install dynamic-default-args
这与其他黑客技巧有些相似,只是更加优雅。
其想法是创建一个容器类来存储动态默认参数,并使用内省来获取装饰函数的签名,然后为其生成专用包装器。
例如,对于此函数:
from dynamic_default_args import dynamic_default_args, named_default
@dynamic_default_args(format_doc=True)
def foo(a, b=named_default(name='b', value=5),
/,
c=named_default(name='c', value=object),
*d,
e=1e-3, f=named_default(name='f', value='will it work?'),
**g):
""" A function with dynamic default arguments.
Args:
a: Required Positional-only argument a.
b: Positional-only argument b. Dynamically defaults to {b}.
c: Positional-or-keyword argument c. Dynamically defaults to {c}.
*d: Varargs.
e: Keyword-only argument e. Defaults to 1e-3.
f: Keyword-only argument f. Dynamically defaults to {f}
**g: Varkeywords.
"""
print(f'Called with: a={a}, b={b}, c={c}, d={d}, e={e}, f={f}, g={g}')
正如您所知,Python有5种参数,根据它们相对于语法中的/
、*
和**
的位置进行分类:
def f(po0, ..., /, pok0, ..., *args, kw0, kw1, ..., **kwargs):
---------- -------- | -------------- |
| | | | |
| Positional- | | Varkeywords
| or-keyword | Keyword-only
Positional-only Varargs
我们生成一个字符串表达式
expr
,其中包含包装函数的定义,并根据其类型调用原始函数的参数遵循上述规则。它的内容应该类似于这样:
def wrapper(a, b=b_, c=c_, *d, e=e_, f=f_, **g):
return func(a,
b.value if isinstance(b, named_default) else b,
c.value if isinstance(c, named_default) else c,
*d,
e=e,
f=f.value if isinstance(f, named_default) else f,
**g)
接下来,使用包含默认参数b_、c_、e_、f_
的上下文字典,从foo
的签名中获取,将expr
编译,函数为func=foo
,并使用我们定义的类named_default
。
exec_locals = {}
exec(compile(expr, '<foo_wrapper>', 'exec'), context, exec_locals)
wrapper = functools.wraps(func)(exec_locals[wrapper_alias])
所有这些都在开始时执行(不是惰性初始化),因此我们可以在运行时限制一个额外的函数调用,并且对于每个函数,类型检查和属性访问开销的最小量(比调用另一个函数检索默认值要高效得多)。
容器的值以后可以被修改,函数的文档字符串也将自动重新格式化。
named_default('b').value += 10
named_default('f').value = 'it works'
help(foo)
动态修改foo.__defaults__
也可以完成任务并且更加高效。
了解更多:dynamic-default-args
baz
不为None
时,这种老式的方法避免了不必要的重新分配。 - chepner