为什么设置默认参数值会使这个函数成为闭包?

3

我正在编写一个应用程序,其中标签是可链接的,并且需要检索所有链接标签的完整链。不允许自引用。运行以下代码会得到一些非常奇怪的结果:

class Tag(object):
  def __init__(self, name):
    self.name = name
    self.links = []

  def __repr__(self):
    return "<Tag {0}>".format(self.name)

  def link(self, tag):
    self.links.append(tag)


def tag_chain(tag, known=[]):
  chain = []
  if tag not in known:
    known.append(tag)
  print "Known: {0}".format(known)

  for link in tag.links:
    if link in known:
      continue
    else:
      known.append(link)
    chain.append(link)
    chain.extend(tag_chain(link, known))
  return chain

a = Tag("a")
b = Tag("b")
c = Tag("c")
a.link(b)
b.link(c)
c.link(a)

o = tag_chain(a)
print "Result:", o
print "------------------"
o = tag_chain(a)
print "Result:", o

结果:

Known: [<Tag a>]
Known: [<Tag a>, <Tag b>]
Known: [<Tag a>, <Tag b>, <Tag c>]
Result: [<Tag b>, <Tag c>]
------------------
Known: [<Tag a>, <Tag b>, <Tag c>]
Result: []

所以,不知怎么的,我无意中创建了一个闭包。据我所见,known应该在函数调用完成后已经超出作用域并消失了。

如果我改变chain_tags()函数的定义,不设置默认值,问题就解决了:

...
def tag_chain(tag, known):
...
o = tag_chain(a, [])
print "Result:", o
print "------------------"
o = tag_chain(a, [])
print "Result:", o

为什么会这样呢?
2个回答

9

这是Python中常见的一个错误:

def tag_chain(tag, known=[]):
  # ...

known=[]并不意味着如果未提供known,则将其设置为空列表;实际上,它将known绑定到一个“匿名”列表。每次known默认为该列表时,它都是相同的列表。

在这里实现你想要的目标的典型模式是:

def tag_chain(tag, known=None):
    if known is None:
        known = []
    # ...

如果未提供,则正确初始化“known”为空列表的代码如下: known = []

感谢清晰的答案。我习惯了Python比这更明确。如果有人知道这个决定背后的理由,我会非常感兴趣看到它。 - sh-beta

3
也许需要额外解释一下发生了什么:默认参数简单地存储在函数对象本身上(即Py2中的),并在需要时用于扩展参数。
>>> def x(a=['here']):
...     a.append(a[-1]*2)
...
>>> x
<function x at 0x0053DB70>
>>> x.func_defaults
(['here'],)

在这个例子中,你可以观察默认列表在那里增长:
>>> x()
>>> x.func_defaults
(['here', 'herehere'],)
>>> x()
>>> x.func_defaults
(['here', 'herehere', 'herehereherehere'],)

修改默认参数有点像改变类变量。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接