【问题标题】:Is it ok to use module constants as default function arguments in Python?可以在 Python 中使用模块常量作为默认函数参数吗?
【发布时间】:2017-12-30 01:25:20
【问题描述】:

基本上是这样的:

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)

这些是等价的还是我应该更喜欢其中一个?

【问题讨论】:

  • 你能举例说明你在哪里看到第二种模式吗?
  • @BrenBarn 从我的脑海中,不,但我已经经常看到它,将它作为一种方法来记忆。
  • 第二个模式在默认值是可变的时候更有用,比如[]。对于不可变类型(例如数字),您应该可以直接使用它们
  • 第二个例子不允许0超时
  • @mxgx:正如 Paul Panzer 的评论所说,这种模式在不同的情况下更常见,这就是我问的原因。我很好奇的是,您是否特别看到有人使用 default-None-then-if-inside-function together 并将模块级常量作为默认值,或者您是否已经刚刚分别看到了这两件事。

标签: python function function-parameter


【解决方案1】:

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

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

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

。 . .那么您仍然在多个调用中重用相同的可变值。 (当然,将可变值定义为“常量”可能无论如何都不是一个好主意。)这就是为什么我在 cmets 中询问您是否见过有人专门用模块常量做这种事情。

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

【讨论】:

  • 感谢您的解释,在我看来实际上存在两个细微差别:第一个是您在上一段中描述的那个,即 none-then-if 模式给用户为重复调用该函数覆盖模块常量的选项。另一个区别是第一个模式允许用户在我自己的默认值和底层请求函数的默认值之间进行选择。通过在第一种情况下传递 None 将获得默认的 request.get() 超时,用户不应该知道,但可以知道。
  • 我不知道任何实际的用例,但它有助于理解正在发生的事情。
  • @mxgx:是的,确实如此。
【解决方案2】:

更新:我强烈推荐阅读this post about instance and class attributes,它包含了使用这两种属性的最佳实践,以及当一种属性优于另一种时。

正如您所提到的,第二种模式可能出现在模块中,其中使用关键字self 将常量定义为实例属性(您可以阅读更多关于属性therethere)例如:

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 问题由mgilson 以更聪明的方式解决。它建议创建sentinels:

这里的常用习语是将默认值设置为某个哨兵值 (没有一个是典型的,尽管some have suggested Ellipsis 对此 目的),然后您可以检查。

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

您可能还会看到用于哨兵的 object() 实例。

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

后一个版本的好处是您可以将 None 传递给您的 功能但有一些缺点(参见下面@larsmans 的 cmets)。如果 您不会预见需要将 None 作为有意义的参数传递给 你的方法,我会提倡使用它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-25
    • 2021-09-09
    • 2018-01-21
    • 2015-05-19
    • 2019-11-16
    • 2010-11-16
    • 2011-04-01
    相关资源
    最近更新 更多