Каков питонический способ избежать параметров по умолчанию, которые являются пустыми списками?
-
21-08-2019 - |
Вопрос
Иногда кажется естественным иметь параметр по умолчанию, который представляет собой пустой список.И все же Python выдает неожиданное поведение в таких ситуациях.
Если, например, у меня есть функция:
def my_func(working_list = []):
working_list.append("a")
print(working_list)
При первом вызове он будет работать по умолчанию, но последующие вызовы обновят существующий список (с одним "а" при каждом вызове) и выведут обновленную версию.
Итак, каков питонический способ получить желаемое поведение (свежий список при каждом вызове)?
Решение
def my_func(working_list=None):
if working_list is None:
working_list = []
working_list.append("a")
print(working_list)
Документы скажите, что вы должны использовать None
по умолчанию и явно проверьте это в теле функции.
Другие советы
Существующие ответы уже предоставили прямые решения, как и требовалось.Однако, поскольку это очень распространенная ошибка для начинающих программистов на Python, стоит добавить объяснение, почему python ведет себя таким образом, которое хорошо изложено в "руководство для автостопщиков по Python" как "Изменяемые аргументы по умолчанию": http://docs.python-guide.org/en/latest/writing/gotchas/
Цитата:"Аргументы Python по умолчанию вычисляются один раз при определении функции, а не при каждом вызове функции (как это происходит, скажем, в Ruby).Это означает, что если вы используете изменяемый аргумент по умолчанию и изменяете его, вы также измените этот объект для всех будущих вызовов функции"
Пример кода для его реализации:
def foo(element, to=None):
if to is None:
to = []
to.append(element)
return to
Не то чтобы это имело значение в данном случае, но вы можете использовать идентификатор объекта для проверки отсутствия:
if working_list is None: working_list = []
Вы также могли бы воспользоваться тем, как логический оператор or определен в python:
working_list = working_list or []
Хотя это будет вести себя неожиданно, если вызывающий объект предоставит вам пустой список (который считается ложным) в качестве working_list и ожидает, что ваша функция изменит список, который он ей предоставил.
Если целью функции является изменять параметр передается как 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)
Возможно, я отклонился от темы, но помните, что если вы просто хотите передать переменное количество аргументов, питонический способ - передать кортеж *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}
Там уже были даны хорошие и правильные ответы.Я просто хотел дать другой синтаксис для написания того, что вы хотите сделать, который я нахожу более красивым, когда вы, например, хотите создать класс с пустыми списками по умолчанию:
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 []
В этом фрагменте используется синтаксис оператора if 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
Что верно для:определение Fn(data = []):
a) это хорошая идея, чтобы ваши списки данных начинались пустыми при каждом вызове.
b) это хорошая идея, чтобы все вызовы функции, которые не предоставляют никаких аргументов при вызове, получали пустой список в качестве данных.
c) является разумной идеей, если ваши данные представляют собой список строк.
d) это плохая идея, потому что значение по умолчанию [] будет накапливать данные, а значение по умолчанию [] будет меняться при последующих вызовах.
Ответ:
d) это плохая идея, потому что значение по умолчанию [] будет накапливать данные, а значение по умолчанию [] будет меняться при последующих вызовах.