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

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

Если, например, у меня есть функция:

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

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

Итак, какой же питонный способ получить желаемое поведение (новый список при каждом вызове)?

14.12.2008 11:23:54
если кому-то интересно, почему это происходит, посмотрите effbot.org/zone/default-values.htm
Ryan Haining 16.07.2012 19:06:36
То же самое происходит с наборами, хотя вам нужен немного более сложный пример, чтобы он отображался как ошибка.
abeboparebop 19.11.2015 12:28:29
Когда ссылки умирают, позвольте мне прямо указать, что это желаемое поведение. Переменные по умолчанию оцениваются при определении функции (что происходит при первом ее вызове), а НЕ при каждом вызове функции. Следовательно, если вы изменяете изменяемый аргумент по умолчанию, любой последующий вызов функции может использовать только измененный объект.
Moritz 3.07.2018 16:50:15
7 ОТВЕТОВ
РЕШЕНИЕ
def my_func(working_list=None):
    if working_list is None: 
        working_list = []

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

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

135
11.03.2019 04:46:24
Лучше сказать: если working_list == Нет: или working_list: ??
John Mulder 14.12.2008 11:31:17
Это предпочтительный способ сделать это в Python, даже если мне это не нравится, так как это ужасно. Я бы сказал, что лучшей практикой будет «если working_list - None».
e-satis 14.12.2008 12:35:29
Предпочтительный способ в этом примере - сказать: если working_list - None. Вызывающий объект мог использовать пустой объект в виде списка с пользовательским добавлением.
tzot 14.12.2008 12:45:17
Мохит Ранка: имейте в виду, что not working_list имеет значение True, если его длина равна 0. Это приводит к противоречивому поведению: если функции получают список с некоторым элементом в нем, его вызывающая сторона будет обновлять свой список, и если список пуст, он не будет затронут
vincent 14.12.2008 13:21:16
Эта идиома помогает в 2 случаях: когда вы изменяете аргумент, и когда вы хотите, чтобы значение по умолчанию было определено в момент вызова вашей функции. Последнее еще важнее.
Beni Cherniavsky-Paskin 4.09.2011 21:40:39

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

if working_list is None: working_list = []

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

working_list = working_list or []

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

13
14.12.2008 11:43:57

Я мог бы быть не по теме, но помните, что если вы просто хотите передать переменное число аргументов, питонический способ - передать кортеж *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}
3
15.12.2008 08:39:03

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

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

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)
10
13.09.2018 08:32:44

Существующие ответы уже предоставили прямые решения в соответствии с просьбой. Однако, поскольку это очень распространенная ошибка для начинающих программистов на Python, стоит добавить объяснение, почему python ведет себя таким образом, которое хорошо представлено в « Руководстве автостопом по Python » как « Изменяемые аргументы по умолчанию »: http: // docs .python-guide.org / о / последняя / запись / подводные камни /

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

Пример кода для его реализации:

def foo(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to
19
6.12.2017 15:03:13

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

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
0
4.08.2017 10:06:15

Я взял класс расширения UCSC Python for programmer

Что верно для: def Fn (data = []):

а) хорошая идея, чтобы ваши списки данных начинались пустыми при каждом вызове.

б) хорошая идея, так что все вызовы функции, которые не предоставляют аргументов для вызова, получат пустой список в качестве данных.

в) разумная идея, если ваши данные представляют собой список строк.

г) плохая идея, потому что default [] будет накапливать данные, а default [] будет меняться при последующих вызовах.

Ответ:

г) плохая идея, потому что default [] будет накапливать данные, а default [] будет меняться при последующих вызовах.

-3
30.06.2019 09:31:24