8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

如何避免由 Python 早期绑定的默认参数引起的问题(例如,可变默认参数“记住”旧数据)?

B''H Bi'ezras -- Boruch Hashem 2月前

94 0

有时,使用一个空列表作为默认参数似乎很自然。然而,Python 在这些情况下会产生意想不到的行为。例如,考虑这个函数:def my_func(

有时,使用一个空列表作为默认参数似乎很自然。然而, Python 在这些情况下会产生意想不到的行为。 .

例如,考虑这个函数:

def my_func(working_list=[]):
    working_list.append("a")
    print(working_list)

第一次调用时,默认设置会起作用,但之后的调用将更新现有列表( "a" 每次调用一个)并打印更新后的版本。

我如何修复该函数,以便在没有明确参数的情况下重复调用它时,每次都使用一个新的空列表?

帖子版权声明 1、本帖标题:如何避免由 Python 早期绑定的默认参数引起的问题(例如,可变默认参数“记住”旧数据)?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由B''H Bi'ezras -- Boruch Hashem在本站《object》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 回顾

    Python 会提前评估参数/参数的默认值 ;它们是“早期绑定的”。这可能会以几种不同的方式导致问题。例如:

    >>> import datetime, time
    >>> def what_time_is_it(dt=datetime.datetime.now()): # chosen ahead of time!
    ...     return f'It is now {dt.strftime("%H:%M:%S")}.'
    ... 
    >>> 
    >>> first = what_time_is_it()
    >>> time.sleep(10) # Even if time elapses...
    >>> what_time_is_it() == first # the reported time is the same!
    True
    

    然而,问题最常见的表现方式是当函数的参数是 可变的 (例如, a list )时,并在函数的代码中发生变异。 当发生这种情况时,更改将被“记住” ,从而在后续调用中“看到”:

    >>> def append_one_and_return(a_list=[]):
    ...     a_list.append(1)
    ...     return a_list
    ... 
    >>> 
    >>> append_one_and_return()
    [1]
    >>> append_one_and_return()
    [1, 1]
    >>> append_one_and_return()
    [1, 1, 1]
    

    因为 a_list 是提前创建的,所以每次调用使用默认值的函数时都将使用 相同的列表对象 ,该对象在每次调用时都会被修改,并附加另一个 1 值。

    这是一个 有意识的设计决策 可以在某些情况下加以利用 - 尽管通常有更好的方法来解决其他问题。(考虑 using functools.cache or functools.lru_cache 进行记忆,并 functools.partial 绑定函数参数。)

    这也意味着 实例的方法不能使用实例的属性作为默认值 :在确定默认值时, self is not in scope, and the instance does not exist anyway

    >>> class Example:
    ...     def function(self, arg=self):
    ...         pass
    ... 
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 2, in Example
    NameError: name 'self' is not defined
    

    (该类 Example 还不 存在,并且名称 Example 也不范围内;因此, 即使我们不关心可变性问题,类属性在这里

    解决方案

    用作 None 标记值

    标准的、普遍被认为是惯用的方法是 use None as the default value, and explicitly check 该值并在函数的逻辑中替换它。因此:

    >>> def append_one_and_return_fixed(a_list=None):
    ...     if a_list is None:
    ...         a_list = []
    ...     a_list.append(1)
    ...     return a_list
    ... 
    >>> append_one_and_return_fixed([2]) # it works consistently with an argument
    [2, 1]
    >>> append_one_and_return_fixed([2])
    [2, 1]
    >>> append_one_and_return_fixed() # and also without an argument
    [1]
    >>> append_one_and_return_fixed()
    [1]
    

    这种 有效,是因为 代码 a_list = [] 在调用函数时 运行(如果需要) ,而不是提前运行——因此,它每次都会创建一个新的空列表。因此,这种方法也可以解决这个 datetime.now() 问题。这确实意味着函数不能将 None 值用于 其他 目的;然而,这不应该在普通代码中造成问题。

    简单地避免可变的默认值

    命令查询分离 的原则,不需要修改参数来实现函数的逻辑, 那么最好 不要这样做 .

    By this argument, append_one_and_return is poorly designed to begin with :由于其目的是 显示 输入的某些修改版本,因此它实际上不应该 修改 调用者的变量,而应该只创建一个 新对象 。这允许使用不可变对象(例如元组)作为默认值。因此:

    def with_appended_one(a_sequence=()):
        return [*a_sequence, 1]
    

    这样,即使明确提供了输入,也可以避免修改输入:

    >>> x = [1]
    >>> with_appended_one(x)
    [1, 1]
    >>> x # not modified!
    [1]
    

    它无需参数就可以正常工作,即使反复使用也是如此:

    >>> with_appended_one()
    [1]
    >>> with_appended_one()
    [1]
    

    并且它还获得了一些灵活性:

    >>> with_appended_one('example') # a string is a sequence of its characters.
    ['e', 'x', 'a', 'm', 'p', 'l', 'e', 1]
    

    PEP 671

    PEP 671 提议为 Python 引入新语法,允许显式后期绑定参数的默认值。建议的语法是:

    def append_and_show_future(a_list=>None): # note => instead of =
        a_list.append(1)
        print(a_list)
    

    然而,虽然该 PEP 草案提议在 Python 3.12 中引入该功能,但这并没有实现,而且 目前还没有这样的语法可用 最近关于这个想法的讨论,但它似乎不太可能在不久的将来得到 Python 的支持。

返回
作者最近主题: