在Python中避免默认参数中的代码重复

ama*_*rea 18 python default argument-passing

考虑具有默认参数的典型函数:

def f(accuracy=1e-3, nstep=10):
    ...
Run Code Online (Sandbox Code Playgroud)

这很紧凑,易于理解.但是,如果我们有另一个功能g,将来电f,我们要传递的一些参数gf?这样做的一种自然方式是:

def g(accuracy=1e-3, nstep=10):
    f(accuracy, nstep)
    ...
Run Code Online (Sandbox Code Playgroud)

这种做法的问题在于可选参数的默认值会重复出现.通常在传播这样的默认参数时,需要在上部函数(g)中使用与下部函数(f)中相同的默认值,因此任何时候默认更改都f需要通过调用它的所有函数并更新默认值他们将传播给他们的任何论据f.

另一种方法是使用占位符参数,并在函数内填写其值:

def f(accuracy=None, nstep=None):
    if accuracy is None: accuracy = 1e-3
    if nstep is None: nstep=10
    ...
def g(accuracy=None, nstep=None):
    f(accuracy, nstep)
    ...
Run Code Online (Sandbox Code Playgroud)

现在调用函数不需要知道f默认值是什么.但是f界面现在有点麻烦,而且不太清楚.这是没有显式默认参数支持的语言中的典型方法,如fortran或javascript.但是如果一个人在python中以这种方式完成所有事情,那么就会丢掉大部分语言的默认参数支持.

有没有比这两个更好的方法?这样做的标准,pythonic方式是什么?

unu*_*tbu 9

定义全局常量:

ACCURACY = 1e-3
NSTEP = 10

def f(accuracy=ACCURACY, nstep=NSTEP):
    ...

def g(accuracy=ACCURACY, nstep=NSTEP):
    f(accuracy, nstep)
Run Code Online (Sandbox Code Playgroud)

如果f并且g在不同的模块中定义,那么您也可以创建一个constants.py模块:

ACCURACY = 1e-3
NSTEP = 10
Run Code Online (Sandbox Code Playgroud)

然后定义f:

from constants import ACCURACY, NSTEP
def f(accuracy=ACCURACY, nstep=NSTEP):
    ...
Run Code Online (Sandbox Code Playgroud)

并且类似地g.


mya*_*aut 5

我认为程序范式将您的视野缩小到该问题。这是我使用其他Python功能发现的一些解决方案。

面向对象编程

您正在调用f()并且g()具有相同的参数子集-这很好地暗示了这些参数表示相同的实体。为什么不使其成为一个对象?

class FG:
    def __init__(self, accuracy=1e-3, nstep=10):
        self.accuracy = accuracy
        self.nstep = nstep

    def f(self):
        print ('f', self.accuracy, self.nstep)

    def g(self):
        self.f()
        print ('g', self.accuracy, self.nstep)

FG().f()
FG(1e-5).g()
FG(nstep=20).g()
Run Code Online (Sandbox Code Playgroud)

功能编程

您可以将其转换f()为高阶函数-即如下所示:

from functools import partial

def g(accuracy, nstep):
    print ('g', accuracy, nstep)

def f(accuracy=1e-3, nstep=10):
    g(accuracy, nstep)
    print ('f', accuracy, nstep)

def fg(func, accuracy=1e-3, nstep=10):
    return partial(func, accuracy=accuracy, nstep=nstep)

fg(g)()
fg(f, 2e-5)()
fg(f, nstep=32)()
Run Code Online (Sandbox Code Playgroud)

但是,这也是一个棘手的做法- f()g()呼吁在这里交换。可能有更好的方法可以做到这一点-即带有回调的管道,但对于FP :(

动态与内省

这是一种更为复杂的方法,需要深入研究CPython内部,但是既然CPython允许这样做,为什么不使用它呢?

这是一个装饰器,用于通过__defaults__成员更新默认值:

class use_defaults:
    def __init__(self, deflt_func):
        self.deflt_func = deflt_func

    def __call__(self, func):
        defltargs = dict(zip(getargspec(self.deflt_func).args, 
                            getargspec(self.deflt_func).defaults))

        defaults = (list(func.__defaults__) 
                    if func.__defaults__ is not None 
                    else [])

        func_args = reversed(getargspec(func).args[:-len(defaults)])

        for func_arg in func_args:
            if func_arg not in defltargs:
                # Default arguments doesn't allow gaps, ignore rest
                break
            defaults.insert(0, defltargs[func_arg])

        # Update list of default arguments
        func.__defaults__ = tuple(defaults)

        return func

def f(accuracy=1e-3, nstep=10, b = 'bbb'):
    print ('f', accuracy, nstep, b)

@use_defaults(f)
def g(first, accuracy, nstep, a = 'aaa'):
    f(accuracy, nstep)
    print ('g', first, accuracy, nstep, a)

g(True)
g(False, 2e-5)
g(True, nstep=32)
Run Code Online (Sandbox Code Playgroud)

但是,这排除了具有关键字的仅关键字参数__kwdefaults__,并且可能use_defaults会使装饰器后面的逻辑破灭。

您也可以使用包装器在运行时中添加参数,但这可能会降低性能。