我有一个调用math.isclose
的函数foo
:
import math
def foo(..., rtol=None, atol=None):
...
if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):
...
...
如果我没有将rtol
和atol
传递给foo
,上述操作将在math.isclose
中失败:
TypeError: must be real number, not NoneType
我不想将系统默认参数值放入我的代码中( 如果他们改变了方向?)
这是我到目前为止的想法:
import math
def foo(..., rtol=None, atol=None):
...
tols = {}
if rtol is not None:
tols["rel_tol"] = rtol
if atol is not None:
tols["abs_tol"] = atol
if math.isclose(x, y, **tols):
...
...
这看起来很长很傻,并且在每次调用时分配一个dict
foo
(它以递归方式调用,所以这 很重要)。
因此,告诉math.isclose
使用默认设置的最佳方法是什么
容忍度?
PS。有几个相关的问题。请注意,我不是想要了解math.isclose
的实际默认参数-我只想告诉它使用默认值,无论它们是什么。
答案 0 :(得分:11)
一种解决方法是可变参数分解:
def foo(..., **kwargs):
...
if math.isclose(x, y, **kwargs):
...
这将允许您将atol
和rtol
指定为主要函数foo
的关键字参数,然后将其不变地传递给math.isclose
。
但是,我也要说,传递给kwargs
的参数以某种方式修改函数的行为是很习惯的,而不是仅仅传递给被调用的子函数。因此,我建议改用一个参数命名,这样很明显它将被解压缩并原样传递给子函数:
def foo(..., isclose_kwargs={}):
...
if math.isclose(x, y, **isclose_kwargs):
...
您可以在matplotlib
中看到一个等效的模式(例如:plt.subplots
-subplot_kw
和gridspec_kw
,所有其他关键字参数都传递给Figure
构造函数分别为**fig_kw
和seaborn
(例如:FacetGrid
-subplot_kws
,gridspec_kws
)。
当存在可能需要传递关键字参数的多个子功能时,这一点尤其明显。否则,保留默认行为:
def foo(..., f1_kwargs={}, f2_kwargs={}, f3_kwargs={}):
...
f1(**f1_kwargs)
...
f2(**f2_kwargs)
...
f3(**f3_kwargs)
...
注意:
请注意default arguments are only instantiated once,因此您不应在函数中修改空的dicts
。如果需要,您应该改为使用None
作为默认参数,并在每次运行函数时实例化一个新的空dict
:
def foo(..., isclose_kwargs=None):
if isclose_kwargs is None:
isclose_kwargs = {}
...
if math.isclose(x, y, **isclose_kwargs):
...
我的偏好是避免这种情况,因为它比较简短,因此您不知道自己在做什么,并且一般而言,我不喜欢重新绑定变量。但是,这绝对是一个有效的习惯用法,它可以更安全。
答案 1 :(得分:11)
正确的解决方案是使用与math.isclose()
相同的默认值。无需对其进行硬编码,因为您可以使用inspect.signature()
function获取 current 默认值:
import inspect
import math
_isclose_params = inspect.signature(math.isclose).parameters
def foo(..., rtol=_isclose_params['rel_tol'].default, atol=_isclose_params['abs_tol'].default):
# ...
快速演示:
>>> import inspect
>>> import math
>>> params = inspect.signature(math.isclose).parameters
>>> params['rel_tol'].default
1e-09
>>> params['abs_tol'].default
0.0
之所以可行,是因为math.isclose()
使用Argument Clinic tool定义了其自变量:
[争论]诊所的最初动机是为CPython内置函数提供内省的“签名”。过去,如果您传入内置函数,自省查询功能将引发异常。有了Argument Clinic,这已经成为过去!
在幕后,math.isclose()
签名实际上存储为字符串:
>>> math.isclose.__text_signature__
'($module, /, a, b, *, rel_tol=1e-09, abs_tol=0.0)'
inspect
签名支持对此进行了解析,以为您提供实际值。
并非所有C定义的函数都使用Argument Clinic 还,代码库是根据具体情况进行转换的。 math.isclose()
是converted for Python 3.7.0。
您可以使用__doc__
字符串作为后备,因为在早期版本中,它也包含签名:
>>> import math
>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
>>> math.isclose.__doc__.splitlines()[0]
'isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0) -> bool'
所以通用的后备方式可能是:
import inspect
def func_defaults(f):
try:
params = inspect.signature(f).parameters
except ValueError:
# parse out the signature from the docstring
doc = f.__doc__
first = doc and doc.splitlines()[0]
if first is None or f.__name__ not in first or '(' not in first:
return {}
sig = inspect._signature_fromstr(inspect.Signature, math.isclose, first)
params = sig.parameters
return {
name: p.default for name, p in params.items()
if p.default is not inspect.Parameter.empty
}
我认为这只是权宜之计,仅支持较旧的Python 3.x发行版。该函数将生成一个以参数名称为关键字的字典:
>>> import sys
>>> import math
>>> sys.version_info
sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
>>> func_defaults(math.isclose)
{'rel_tol': 1e-09, 'abs_tol': 0.0}
请注意,复制Python默认值是非常低的风险;除非有错误,否则值不容易更改。因此,另一种选择是将3.5 / 3.6的默认默认值硬编码为后备,并使用3.7及更高版本中提供的签名:
try:
# Get defaults through introspection in newer releases
_isclose_params = inspect.signature(math.isclose).parameters
_isclose_rel_tol = _isclose_params['rel_tol'].default
_isclose_abs_tol = _isclose_params['abs_tol'].default
except ValueError:
# Python 3.5 / 3.6 known defaults
_isclose_rel_tol = 1e-09
_isclose_abs_tol = 0.0
但是请注意,您不支持未来,其他参数和默认值的风险更大。至少inspect.signature()
方法可以让您添加有关代码期望的参数数量的断言。
答案 2 :(得分:7)
使用函数的默认参数确实没有很多方法...您只有两个选择:
由于所有选项都不是很好,因此我将列出详尽的清单,以便您可以将它们全部进行比较。
**kwargs
传递参数使用**kwargs
定义您的方法,并将其传递给math.isclose
:
def foo(..., **kwargs):
...
if math.isclose(x, y, **kwargs):
缺点:
foo(1, 2, rtol=3)
不起作用)**kwargs
字典def foo(..., rtol=None, atol=None):
...
kwargs = {}
if rtol is not None:
kwargs["rel_tol"] = rtol
if atol is not None:
kwargs["abs_tol"] = atol
if math.isclose(x, y, **kwargs):
缺点:
def foo(..., rtol=1e-09, atol=0.0):
...
if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):
缺点:
您可以使用inspect
模块在运行时确定默认值:
import inspect, math
signature = inspect.signature(math.isclose)
DEFAULT_RTOL = signature.parameters['rel_tol'].default
DEFAULT_ATOL = signature.parameters['abs_tol'].default
def foo(..., rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL):
...
if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):
缺点:
inspect.signature
在内置函数或C中定义的其他函数上可能会失败答案 3 :(得分:3)
将递归委托给一个辅助函数,这样您只需构建一次dict
。
import math
def foo_helper(..., **kwargs):
...
if math.isclose(x, y, **kwargs):
...
...
def foo(..., rtol=None, atol=None):
tols = {}
if rtol is not None:
tols["rel_tol"] = rtol
if atol is not None:
tols["abs_tol"] = atol
return foo_helper(x, y, **tols)
或者,不要将公差传递给foo
,而传递已经包含所需公差的函数。
from functools import partial
# f can take a default value if there is a common set of tolerances
# to use.
def foo(x, y, f):
...
if f(x,y):
...
...
# Use the defaults
foo(x, y, f=math.isclose)
# Use some other tolerances
foo(x, y, f=partial(math.isclose, rtol=my_rtel))
foo(x, y, f=partial(math.isclose, atol=my_atol))
foo(x, y, f=partial(math.isclose, rtol=my_rtel, atol=my_atol))