例如,考虑以下函数:
def my_func(working_list=[]):
working_list.append("a")
print(working_list)
第一次调用时,缺省值会起作用,但之后的调用将更新现有列表(每个调用均添加一个
"a"
),并打印更新版本。我该如何修复此函数,以便在没有显式参数的情况下重复调用时,每次都使用新的空列表?
def my_func(working_list=[]):
working_list.append("a")
print(working_list)
"a"
),并打印更新版本。f()
,那么你必须调用f(*l)
,这很糟糕。更糟糕的是,使用可变参数实现mate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])
会很糟糕。如果是def mate(birds=[], bees=[]):
就好多了。 - FeRD其他答案已经提供了直接的解决方案,但由于这是新 Python 程序员非常容易犯的错误,值得补充一下为什么 Python 会以这种方式行事的解释。在 The Hitchhikers Guide to Python 中,可变默认参数 很好地总结了这个问题:
Python 的默认参数在函数定义时只计算一次(不像 Ruby 等语言每次调用函数都会重新计算)。这意味着如果你使用一个可变的默认参数并且改变它,你已经改变了该对象的状态,并且所有未来对该函数的调用都将使用修改后的值。
虽然在这种情况下并不重要,但你可以使用对象身份来测试是否为 None:
if working_list is None: working_list = []
你也可以利用 Python 中布尔运算符 or 的定义:
working_list = working_list or []
如果调用者给你一个空列表(在Python中,空列表被视为false),并期望你的函数修改他所提供的列表,则此函数的行为将是意外的。
0
和1
,或True
和False
时,行为却出人意料。 - Nico Schlömerworking_list
参数传递的列表,则请参考HenryR的答案(=None,内部检查是否为None)。def myFunc(starting_list = []):
starting_list = list(starting_list)
starting_list.append("a")
print starting_list
(或者在这个简单的例子中只需使用print starting_list + ["a"]
,但我想那只是一个玩具示例)
通常情况下,在Python中修改参数不是好的编程习惯。唯一完全期望改变对象的函数是对象的方法。甚至更少见的是修改可选参数 - 仅在某些调用中发生的副作用是否真的是最好的接口?
如果您出于C语言的“输出参数”的习惯而这样做,则完全没有必要 - 您始终可以将多个值作为元组返回。
如果您这样做是为了有效地构建长列表而不建立中间列表,请考虑将其编写为生成器,并在调用时使用result_list.extend(myFunc())
。这样您的调用约定仍然非常清晰。
一个经常使用可变可选参数的模式是递归函数中的隐藏“备忘录”参数:
def depth_first_walk_graph(graph, node, _visited=None):
if _visited is None:
_visited = set() # create memo once in top-level call
if node in _visited:
return
_visited.add(node)
for neighbour in graph[node]:
depth_first_walk_graph(graph, neighbour, _visited)
()
)可能也是有意义的。根据情况,这可能需要对代码进行轻微更改(但在此处不需要;list(())
可以很好地创建一个新的空列表)。 - Karl Knechtel可能我有些跑题了,但是请记住如果你只想传递可变数量的参数,Pythonic的方式是传递一个元组*args
或者字典**kargs
。这些是可选的并且比语法myFunc([1, 2, 3])
更好。
如果你想传递一个元组:
def myFunc(arg1, *args):
print args
w = []
w += args
print w
>>>myFunc(1, 2, 3, 4, 5, 6, 7)
(2, 3, 4, 5, 6, 7)
[2, 3, 4, 5, 6, 7]
如果您想传递一个字典:
def myFunc(arg1, **kargs):
print kargs
>>>myFunc(1, option1=2, option2=3)
{'option2' : 2, 'option1' : 3}
Python会提前计算参数的默认值,它们是“早绑定”的。这可能会导致一些问题,例如:
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
>>> 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
值。
这是一个有意识的设计决策,可以在某些情况下利用 - 尽管通常有更好的方法来解决那些其他问题。 (考虑使用functools.cache
或functools.lru_cache
进行记忆化,并使用functools.partial绑定函数参数)。
这也意味着实例的方法不能使用实例属性作为默认值:在确定默认值的时间,self
不在作用域内,而且实例也不存在:
>>> 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
作为哨兵值None
作为默认值,并明确检查该值并在函数的程序逻辑中替换它。因此:>>> 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 值; 但是,在普通代码中,这不应该成为问题。
简单避免可变默认值
如果没有必要修改参数以实现函数逻辑,由于命令查询分离原则,最好就不要那样做。
因此,append_one_and_return
的设计本来就很差:由于其目的是显示输入的某个修改版本,它不应该同时实际修改调用者的变量,而是应该为显示目的创建一个新对象。这允许使用不可变对象(例如元组)作为默认值。因此:
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]
可能在3.12版本中出现的新语法
计划在Python 3.12中引入新的语法,详见PEP 671,该语法将允许显式请求默认值的延迟绑定而不是早期绑定。该语法很可能如下所示:
def append_and_show_future(a_list=>None): # note => instead of =
a_list.append(1)
print(a_list)
然而,截至本文撰写时,该提案尚未正式接受并且不在即将到来的更改列表上。
def my_funct(params, lst = []):
liste = lst.copy()
. .
引用自https://docs.python.org/3/reference/compound_stmts.html#function-definitions
当函数定义被执行时,默认参数值从左到右进行计算。这意味着表达式在函数定义时只计算一次,并且相同的“预先计算”值用于每个调用。当默认参数是可变对象(例如列表或字典)时,理解这一点尤为重要:如果函数修改了对象(例如通过向列表添加项),则默认值实际上被修改了。这通常不是预期的结果。解决方法是将None用作默认值,并在函数体中显式测试它,例如:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
已经有很好且正确的答案了。我只是想提供另一种语法来编写您想要做的事情,当您例如想要创建一个带有默认空列表的类时,我发现这更加优美:
class Node(object):
def __init__(self, _id, val, parents=None, children=None):
self.id = _id
self.val = val
self.parents = parents if parents is not None else []
self.children = children if children is not None else []
def myFunc(working_list=None):
working_list = [] if working_list is None else working_list
working_list.append("a")
print working_list
我参加了UCSC延伸课程的 Python for programmer
下列哪项陈述是正确的:def Fn(data = []):
a) 这样做是个好主意,因为每次调用时数据列表都会为空。
b) 这样做是个好主意,因为所有没有提供参数的函数调用都将使用空列表作为数据。
c) 只要你的数据是一个字符串列表,这么做就是合理的。
d) 这是个坏主意,因为默认值[]将累积数据并且默认值[]会随后续调用而改变。
答案:
d) 这是个坏主意,因为默认值[]将累积数据并且默认值[]会随后续调用而改变。