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

119

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

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

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

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

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

Джон Малдер
источник
17
если кого-то интересует, почему это происходит, посетите effbot.org/zone/default-values.htm
Райан
То же самое происходит и с наборами, хотя вам нужен немного более сложный пример, чтобы он отображался как ошибка.
abeboparebop
Поскольку ссылки умирают, позвольте мне прямо указать, что это желаемое поведение. Переменные по умолчанию оцениваются при определении функции (что происходит при ее первом вызове), а НЕ при каждом вызове функции. Следовательно, если вы изменяете изменяемый аргумент по умолчанию, любой последующий вызов функции может использовать только измененный объект.
Moritz

Ответы:

152
def my_func(working_list=None):
    if working_list is None: 
        working_list = []

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

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

HenryR
источник
1
Лучше сказать: if working_list == None: или if working_list: ??
Джон Малдер,
2
Это предпочтительный способ сделать это в python, даже если мне это не нравится, потому что он уродлив. Я бы сказал, что лучшей практикой было бы «если рабочий_лист равен None».
e-satis
21
Предпочтительный способ в этом примере - сказать: if working_list is None. Вызывающий мог использовать пустой объект в виде списка с настраиваемым добавлением.
tzot
5
Мохит Ранка: имейте в виду, что not working_list имеет значение True, если его длина равна 0. Это приводит к несогласованному поведению: если функции получают список с каким-либо элементом в нем, его вызывающий объект обновляет свой список, а если список пуст, он не будут тронуты.
Винсент
1
@PatrickT Правильный инструмент зависит от случая - функция varargs сильно отличается от той, которая принимает (необязательный) аргумент списка. Ситуации, когда вам придется выбирать между ними, возникают реже, чем вы думаете. Varargs отлично подходит при изменении счетчика аргументов, но исправляется, когда код НАПИСАН. Как ваш пример. Если это переменная времени выполнения или вы хотите вызвать f()список, вам нужно будет вызвать, f(*l)что является грубым. Хуже того, реализация mate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])SUCK w / varargs. Намного лучше, если это так def mate(birds=[], bees=[]):.
FeRD,
26

Существующие ответы уже предоставили прямые решения, о которых просили. Однако, поскольку это очень распространенная ошибка для начинающих программистов 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
Женхуа
источник
13

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

if working_list is None: working_list = []

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

working_list = working_list or []

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

бендин
источник
10

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

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

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)
Бени Чернявский-Паскин
источник
3

Возможно, я не по теме, но помните, что если вы просто хотите передать переменное количество аргументов, питонический способ - передать кортеж *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}
Mapad
источник
0

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

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
drssdinblck
источник
-3

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

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

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

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

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

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

Ответ:

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

Питер Чен
источник