Почему установка значения параметра по умолчанию делает эту функцию закрытой? - PullRequest
3 голосов
/ 04 ноября 2010

Я пишу приложение, в котором теги можно связать, и необходимо извлечь всю цепочку связанных тегов. Самостоятельная ссылка не допускается. Запуск следующего кода приводит к очень странным результатам:

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: []

Итак, я как-то случайно создал замыкание. Насколько я вижу, известные должны были выйти из области видимости и исчезнуть после завершения вызова функции.

Если я изменю определение chain_tags (), чтобы не устанавливать значение по умолчанию, проблема исчезнет:

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

Почему это?

Ответы [ 2 ]

9 голосов
/ 04 ноября 2010

Это распространенная ошибка в Python:

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

known=[] не означает, что если известное не поддерживается, сделайте его пустым списком;на самом деле он связывает известный «анонимный» список.Каждый раз, когда этот список по умолчанию известен, он становится одним и тем же списком.

Типичный шаблон для выполнения того, что вы намеревались здесь:

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

, который правильно инициализирует known дляпустой список, если он не предоставлен.

3 голосов
/ 04 ноября 2010

Может быть, в качестве дополнительного объяснения, что происходит: параметры по умолчанию просто сохраняются в самом объекте функции (т.е. tag_chain.func_defaults в 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'],)

Изменение аргументов по умолчанию похоже на изменение переменных класса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...