考虑使用默认参数的典型函数:
def f(accuracy=1e-3, nstep=10):
...
这是紧凑且易于理解的。但是如果我们有另一个函数g
会调用f
,我们想要将g
的一些参数传递给f
怎么办?这样做的一种自然方式是:
def g(accuracy=1e-3, nstep=10):
f(accuracy, nstep)
...
这种做法的问题在于可选参数的默认值会重复出现。通常在传播这样的默认参数时,需要在上部函数(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)
...
现在,调用函数不需要知道f
的默认值。但是f
界面现在有点麻烦,而且不太清楚。这是没有显式默认参数支持的语言中的典型方法,如fortran或javascript。但是如果一个人在python中以这种方式完成所有工作,那么就会丢掉大部分语言的默认参数支持。
有没有比这两个更好的方法?这样做的标准,pythonic方式是什么?
答案 0 :(得分:9)
定义全局常量:
ACCURACY = 1e-3
NSTEP = 10
def f(accuracy=ACCURACY, nstep=NSTEP):
...
def g(accuracy=ACCURACY, nstep=NSTEP):
f(accuracy, nstep)
如果f
和g
在不同的模块中定义,那么您也可以制作constants.py
模块:
ACCURACY = 1e-3
NSTEP = 10
然后使用:
定义f
from constants import ACCURACY, NSTEP
def f(accuracy=ACCURACY, nstep=NSTEP):
...
,同样适用于g
。
答案 1 :(得分:4)
我认为程序范式缩小了你对这个问题的看法。以下是我使用其他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()
您可以将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)()
但这也是一个棘手的方法 - 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)
然而,这排除了仅有关键字的参数,这些参数具有单独的__kwdefaults__
,并且可能会破坏use_defaults
装饰器后面的逻辑。
您也可以使用包装器在运行时添加参数,但这可能会降低性能。
答案 2 :(得分:3)
与@unutbu衔接:
如果您使用的是包结构:
mypackage
|
+- __init__.py
|
+- fmod.py
|
+- gmod.py
|
...
然后在__init__.py
中将你的常数设为@unutbu建议:
ACCURACY = 1e-3
NSTEP = 10
__all__ = ['ACCURACY', 'NSTEP']
然后在fmod.py
from mypackage import *
def f(accuracy=ACCURACY, nstep=NSTEP):
...
和gmod.py
以及任何其他模块导入常量。
from mypackage import *
def g(accuracy=ACCURACY, nstep=NSTEP):
f(accuracy, nstep)
...
或者,如果您不使用包,只需创建一个名为myconstants.py
的模块,并执行与__init__.py
完全相同的操作,但不是从mypackage
导入,而是从{myconstants
导入1}}。
这种风格的一个优点是,如果以后你想从文件中读取常量(或作为函数的参数),假设它存在,你可以将代码放在__init__.py
或myconstants.py
中那样做。
答案 3 :(得分:2)
我最喜欢的,kwargs!
def f(**kwargs):
kwargs.get('accuracy', 1e-3)
..
def g(**kwargs):
f(**kwargs)
当然,请随意使用上述常量。