Вопрос-Ответ

How can I avoid issues caused by Python's early-bound default parameters (e.g. mutable default arguments "remembering" old data)?

Как я могу избежать проблем, вызванных параметрами по умолчанию с ранней привязкой к Python (например, изменяемыми аргументами по умолчанию, "запоминающими" старые данные)?

Иногда кажется естественным иметь параметр по умолчанию, который представляет собой пустой список. Однако Python приводит к неожиданному поведению в этих ситуациях.

Для примера рассмотрим эту функцию:

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

При первом вызове по умолчанию будет работать, но последующие вызовы обновят существующий список (по одному "a" каждому вызову) и напечатают обновленную версию.

Как я могу исправить функцию так, чтобы, если она вызывается повторно без явного аргумента, каждый раз использовался новый пустой список?

Переведено автоматически
Ответ 1
def my_func(working_list=None):
if working_list is None:
working_list = []

# alternative:
# working_list = [] if working_list is None else working_list

working_list.append("a")
print(working_list)

В документах говорится, что вы должны использовать None значение по умолчанию и явно проверять его в теле функции.

Ответ 2

В других ответах уже были предоставлены прямые решения, как и просили, однако, поскольку это очень распространенная ошибка для начинающих программистов на Python, стоит добавить объяснение того, почему Python ведет себя таким образом, которое хорошо изложено в Руководстве автостопщиков по Python в разделе Изменяемые аргументы по умолчанию:


Аргументы по умолчанию в Python оцениваются один раз при определении функции, а не при каждом вызове функции (как, скажем, в Ruby). Это означает, что если вы используете изменяемый аргумент по умолчанию и изменяете его, вы будете изменять этот объект и для всех будущих вызовов функции.


Ответ 3

Не то чтобы это имело значение в данном случае, но вы можете использовать object identity для проверки отсутствия:

if working_list is None: working_list = []

Вы также могли бы воспользоваться тем, как в python определен логический оператор or:

working_list = working_list or []

Хотя это приведет к неожиданному поведению, если вызывающий объект предоставит вам пустой список (который считается ложным) в качестве working_list и ожидает, что ваша функция изменит список, который он ей предоставил.

Ответ 4

Если целью функции является изменение параметра, переданного как working_list, смотрите Ответ Генри (= Нет, проверьте, нет ли внутри).

Но если вы не собирались изменять аргумент, просто используйте его как отправную точку для списка, вы можете просто скопировать его:

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()) при его вызове. Таким образом, ваши соглашения о вызовах остаются очень чистыми.


Один шаблон, в котором часто выполняется изменение необязательного аргумента , - это скрытый аргумент "memo" в рекурсивных функциях:

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)
python