如何告诉函数使用默认参数值?

sds*_*sds 29 python python-3.x

我有一个函数foo调用math.isclose

import math
def foo(..., rtol=None, atol=None):
    ...
    if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):
        ...
    ...
Run Code Online (Sandbox Code Playgroud)

上述失败的math.isclose,如果我不通过rtol,并atolfoo

TypeError: must be real number, not NoneType
Run Code Online (Sandbox Code Playgroud)

我不想将系统默认参数值放入我的代码中(如果以后改变的话会怎样?)

到目前为止,这是我想到的:

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):
        ...
    ...
Run Code Online (Sandbox Code Playgroud)

这看起来很长,很愚蠢,并且dict在每次调用时都 分配一个foo(递归调用自身,所以这重要)。

那么,告诉math.isclose使用默认公差的最佳方法是什么?

PS。有几个相关的问题。请注意,我并不想知道实际的默认参数math.isclose-所有我希望它告诉它使用任何他们都是默认的。

gmd*_*mds 11

一种解决方法是可变参数展开:

def foo(..., **kwargs):
    ...
    if math.isclose(x, y, **kwargs):
        ...
Run Code Online (Sandbox Code Playgroud)

这将允许你指定atolrtol作为关键字参数的主要功能foo,它会再传给不变math.isclose

但是,我还要说的是,传递参数以kwargs某种方式修改函数的行为,而不仅仅是传递给被调用的子函数,这是很习惯的做法。因此,我建议改用一个参数命名,这样很明显它将被解压缩并不变地传递给子函数:

def foo(..., isclose_kwargs={}):
    ...
    if math.isclose(x, y, **isclose_kwargs):
        ...
Run Code Online (Sandbox Code Playgroud)

您可以在中看到一个等效的模式matplotlib(例如:plt.subplots- subplot_kwgridspec_kw,所有其他关键字参数都作为传递给Figure构造函数**fig_kw)和seaborn(示例:FacetGrid- subplot_kwsgridspec_kws)。

当有这特别明显多发,你可能想通过关键字参数,但在其他方面保留默认行为子功能:

def foo(..., f1_kwargs={}, f2_kwargs={}, f3_kwargs={}):
    ...
    f1(**f1_kwargs)
    ...
    f2(**f2_kwargs)
    ...
    f3(**f3_kwargs)
    ...
Run Code Online (Sandbox Code Playgroud)

警告:

请注意,默认参数仅实例化一次,因此您不应dicts在函数中修改空值。如果需要,您应该改为使用None默认参数,并在dict每次运行该函数时实例化一个新的空值:

def foo(..., isclose_kwargs=None):
    if isclose_kwargs is None:
        isclose_kwargs = {}
    ...
    if math.isclose(x, y, **isclose_kwargs):
        ...
Run Code Online (Sandbox Code Playgroud)

我的偏好是避免这种情况,因为它比较简短,因此您不知道自己在做什么,而且一般来说我不喜欢重新绑定变量。但是,这绝对是一个有效的习惯用法,它可以更安全。


Mar*_*ers 11

正确的解决方案是使用与相同的默认设置math.isclose()。无需对它们进行硬编码,因为您可以使用函数获得当前的默认值:inspect.signature()

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):
    # ...
Run Code Online (Sandbox Code Playgroud)

快速演示:

>>> import inspect
>>> import math
>>> params = inspect.signature(math.isclose).parameters
>>> params['rel_tol'].default
1e-09
>>> params['abs_tol'].default
0.0
Run Code Online (Sandbox Code Playgroud)

之所以有效,是因为math.isclose()使用Argument Clinic工具定义了其参数:

Argument Clinic的最初动机是为CPython内置函数提供内省的“签名”。过去,如果您传入内置函数,自省查询功能将引发异常。有了Argument Clinic,这已成为过去!

在幕后,math.isclose()签名实际上存储为字符串:

>>> math.isclose.__text_signature__
'($module, /, a, b, *, rel_tol=1e-09, abs_tol=0.0)'
Run Code Online (Sandbox Code Playgroud)

inspect签名支持对此进行了解析,以为您提供实际值。

不是所有的C-定义的函数使用参数诊所然而,基本代码被转换上的情况下,逐案。math.isclose()转换为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'
Run Code Online (Sandbox Code Playgroud)

因此,通用的后备广告可能是:

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
    }
Run Code Online (Sandbox Code Playgroud)

我将其视为仅支持较旧的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}
Run Code Online (Sandbox Code Playgroud)

注意复制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
Run Code Online (Sandbox Code Playgroud)

但是请注意,不支持将来的附加参数和默认值的风险更大。至少该方法使您可以添加有关代码期望的参数数量的断言。inspect.signature()


Ara*_*Fey 7

使函数使用其默认参数的方法确实很少,您只有两种选择:

  1. 传递真实的默认值
  2. 根本不要传递论点

由于这些选项都不是很好的选择,因此我将列出一个详尽的清单,以便您可以将它们全部进行比较。

  • 使用**kwargs通过论证

    使用定义您的方法,**kwargs并将其传递给math.isclose

    def foo(..., **kwargs):
        ...
        if math.isclose(x, y, **kwargs):
    
    Run Code Online (Sandbox Code Playgroud)

    缺点:

    • 两个函数的参数名称必须匹配(例如,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):
    
    Run Code Online (Sandbox Code Playgroud)

    缺点:

    • 丑陋,难以编码,而且速度不快
  • 硬编码默认值

    def foo(..., rtol=1e-09, atol=0.0):
        ...
        if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):
    
    Run Code Online (Sandbox Code Playgroud)

    缺点:

    • 硬编码值
  • 使用自省功能查找默认值

    您可以使用该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):
    
    Run Code Online (Sandbox Code Playgroud)

    缺点: