在 Python 中可以使用模块常量作为默认函数参数吗?

Is it ok to use module constants as default function arguments in Python?

基本上是这样的:

DEFAULT_TIMEOUT = 10
# or even: from my_settings import DEFAULT_TIMEOUT

def get_google(timeout=DEFAULT_TIMEOUT):
    return requests.get('google.com', timeout=timeout)

我假设只要常量真的保持不变,它就可以正常工作。然而我有时会看到这样的模式:

DEFAULT_TIMEOUT = 10

def get_google(timeout=None):
    if timeout is None:
        timeout = DEFAULT_TIMEOUT
    return requests.get('google.com', timeout=timeout)

这些是等效的还是我应该更喜欢一个?

使用 "constant" 作为默认值没有问题。正如您所说,只要 "constant" 确实不变,就没有关系。唯一的问题是您必须确保在函数之前定义常量,但通常人们将所有常量放在文件的顶部,所以这不是问题。

当所需的默认值是可变值(例如列表)时,您描述的第二种模式很常见。你经常看到这样的事情:

def foo(x=None):
    if x is None:
        x = []

而不是 def foo(x=[])。您可以找到很多关于此的问题,但本质上是因为如果您不这样做,可变默认值将在对该函数的多次调用中持续存在,这通常是不可取的。

但是,将此模式用于可变模块级常量并不能解决该问题。如果你有:

SOME_CONSTANT = []

def foo(x=None):
    if x is None:
        x = SOME_CONSTANT

。 . .那么您仍然在多个调用中重复使用相同的可变值。 (当然,无论如何,将可变值定义为 "constant" 可能不是一个好主意。)这就是为什么我在评论中问你是否看到有人专门用模块常量做这种事情。

如果模块级默认值实际上不是常量,而是打算由其他代码更改的值,那么也会使用此 None-then-if 模式。如果您执行 def foo(x=DEFAULT_TIMEOUT),则 x 的默认值是您 定义 函数时 DEFAULT_TIMEOUT 的值。但是,如果您使用 None-then-if 模式,则默认值将是您 调用 函数时 DEFAULT_TIMEOUT 的值。一些库定义的模块级值并不是常量,而是可以在执行过程中更改的配置值。这允许用户执行类似 set DEFAULT_TIMEOUT = 20 的操作来更改所有后续调用的默认超时,而不必每次都传递 timeout=20。在这种情况下,您可能希望在函数内部进行 if 检查,以确保每次调用都使用 DEFAULT_TIMEOUT.

的 "current" 值

更新: 我强烈推荐阅读 this post about instance and class attributes,它包括使用两种类型属性的最佳实践,以及何时优先使用另一种属性。

如您所述,第二种模式可以出现在模块中,其中关键字 self is used, to define constant as instance attribute (you can read more about attributes there and there) 例如:

class Module:
    def __init__(self):
        self.DEFAULT_TIMEOUT = 10

    def get_google(timeout=self.DEFAULT_TIMEOUT):
        return requests.get('google.com', timeout=timeout)

会产生错误:NameError: name 'self' is not defined

class Module:
    def __init__(self):
        self.DEFAULT_TIMEOUT = 10

    def get_google(timeout=None):
        if timeout is None:
            timeout = self.DEFAULT_TIMEOUT
        return requests.get('google.com', timeout=timeout)

另一个question problem is solved by mgilson in more clever way. It suggests creating sentinels:

The common idiom here is to set the default to some sentinel value (None is typical, although some have suggested Ellipsis for this purpose) which you can then check.

class Example(object): #inherit from object.  It's just a good idea.  
    def __init__(self, data = None):
        self.data = self.default_data() if data is None else data

    def default_data(self):  #probably need `self` here, unless this is a @staticmethod ...
        # ....
        return something

You might also see an instance of object() used for the sentinel.

SENTINEL = object()
class Example(object):
    def __init__(self, data = SENTINEL):
        self.data = self.default_data() if data is SENTINEL else data

This latter version has the benefit that you can pass None to your function but has a few downsides (see comments by @larsmans below). If you don't forsee the need to pass None as a meaningful argument to your methods, I would advocate using that.