Який пітонічний спосіб уникнути параметрів за замовчуванням, які є порожніми списками?


117

Іноді здається, що параметр за замовчуванням є порожнім списком. І все ж Python надає несподівану поведінку в цих ситуаціях .

Наприклад, у мене є функція:

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

Перший раз, коли він називається за замовчуванням, спрацює, але дзвінки після цього оновлять існуючий список (з одним "a" кожним викликом) та надрукують оновлену версію.

Отже, який пітонічний спосіб отримати поведінку, яку я бажаю (свіжий список на кожному дзвінку)?


17
якщо когось цікавить, чому це відбувається, перегляньте effbot.org/zone/default-values.htm
Ryan Haining

Така ж поведінка трапляється і для наборів, хоча вам потрібен трохи складніший приклад, щоб відобразитися як помилка.
abeboparebop

Коли посилання вмирають, дозвольте я прямо зазначити, що це бажана поведінка. Змінні за замовчуванням оцінюються при визначенні функції (що відбувається під час першого виклику), а НЕ щоразу, коли функція викликається. Отже, якщо ви мутуєте змінний аргумент за замовчуванням, будь-який наступний виклик функції може використовувати лише мутований об'єкт.
Моріц

Відповіді:


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

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

Документи говорять, що ви повинні використовувати Noneяк за замовчуванням і чітко перевірити його в тілі функції.


1
Чи краще сказати: якщо working_list == Немає: або якщо working_list: ??
Джон Малдер

2
Це кращий спосіб зробити це в python, навіть якщо мені це не подобається, оскільки це некрасиво. Я б сказав, що найкращою практикою було б "якщо working_list - ні".
e-satis

21
Кращий спосіб у цьому прикладі - сказати: якщо working_list - Ні. Абонент, можливо, використовував порожній список, схожий на список, зі спеціальним додатком.
tzot

5
Mohit Ranka: будьте обережні, що не working_list - це True, якщо його довжина дорівнює 0. Це призводить до непослідовної поведінки: якщо функції отримують список із деяким елементом у ньому, його абонент буде оновити свій список, і якщо список буде порожнім, він не буде торкатися
Вінсент

1
@PatrickT Правильний інструмент залежить від випадку - функція 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 / остання / письма / 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 []

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


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}

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

-3

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

Що стосується: def Fn (data = []):

а) - це гарна ідея, щоб ваші списки даних починалися порожніми при кожному дзвінку.

б) є ​​хорошою ідеєю, тому що всі виклики функції, які не надають жодних аргументів під час виклику, отримають порожній список як дані.

в) - розумна ідея, якщо ваші дані - це список рядків.

г) - погана ідея, оскільки за замовчуванням [] буде накопичуватися дані, а за замовчуванням [] змінюватиметься з наступними дзвінками.

Відповідь:

г) - погана ідея, оскільки за замовчуванням [] буде накопичуватися дані, а за замовчуванням [] змінюватиметься з наступними дзвінками.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.