Почему синтаксис "изменяемый аргумент по умолчанию изменчив" так ужасен, спрашивает новичка Python - PullRequest
28 голосов
/ 14 апреля 2010

Теперь следите за моей серией "вопросов о новичках в Python" и основаны на другом вопросе .

Prerogative

Перейдите к http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables и прокрутите вниз до «Значения параметров по умолчанию». Там вы можете найти следующее:

def bad_append(new_item, a_list=[]):
    a_list.append(new_item)
    return a_list

def good_append(new_item, a_list=None):
    if a_list is None:
        a_list = []
    a_list.append(new_item)
    return a_list

На этом же самом примере есть даже «Важное предупреждение» на python.org , хотя на самом деле это не значит, что оно «лучше».

Один из способов выразить это

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

редактирование:

Еще один способ выразиться

Я не спрашиваю почему или как это происходит (спасибо Марку за ссылку).

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

Я думаю, что лучшим способом было бы, вероятно, сделать что-то в самом def, в котором аргумент name был бы присоединен к «локальному» или «новому» внутри def, изменяемого объекта. Что-то вроде:

def better_append(new_item, a_list=immutable([])):
    a_list.append(new_item)
    return a_list

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

Ответы [ 8 ]

11 голосов
/ 14 апреля 2010

Это называется «изменяемой ловушкой по умолчанию». Смотри: http://www.ferg.org/projects/python_gotchas.html#contents_item_6

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

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

Это:

>>> my_list = []
>>> my_list.append(1)

Яснее и проще для чтения, чем:

>>> my_list = my_append(1)

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

6 голосов
/ 14 апреля 2010

Аргументы по умолчанию оцениваются во время выполнения оператора def, что, вероятно, является наиболее разумным подходом: часто это то, что нужно. Если бы это было не так, это могло бы привести к сбивающим с толку результатам, когда среда немного изменилась.

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

4 голосов
/ 14 апреля 2010

Чрезвычайно специфический вариант использования функции, которая позволяет опционально передавать список для изменения, но генерирует новый список, если вы специально не передаете его, определенно не стоит синтаксиса специального случая. Серьезно, если вы выполняете несколько вызовов этой функции, зачем вам нужен специальный случай первого вызова в серии (путем передачи только одного аргумента), чтобы отличить его от любого другого (для которого потребуются два аргумента) чтобы иметь возможность продолжать обогащать существующий список) ?! Например, рассмотрим что-то вроде (при условии, конечно, что betterappend сделал что-то полезное, потому что в текущем примере было бы безумно называть это вместо прямого .append! -):

def thecaller(n):
  if fee(0):
    newlist = betterappend(foo())
  else:
    newlist = betterappend(fie())
  for x in range(1, n):
    if fee(x):
      betterappend(foo(), newlist)
    else:
      betterappend(fie(), newlist)

это просто безумие, и должно очевидно быть вместо

def thecaller(n):
  newlist = []
  for x in range(n):
    if fee(x):
      betterappend(foo(), newlist)
    else:
      betterappend(fie(), newlist)

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

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

3 голосов
/ 15 апреля 2010

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

Пример, который вы приводите: некорректен . Он изменяет список, который вы передаете, как побочный эффект. Если вы хотели, чтобы функция работала, не имеет смысла иметь аргумент по умолчанию. Также не имеет смысла возвращать обновленный список. Без аргумента по умолчанию проблема исчезнет.

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

def better_append(new_item, a_list=[]): 
    new_list = list(a_list)
    new_list.append(new_item) 
    return new_list 

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

def generator_append(new_item, a_list=[]):
    for x in a_list:
        yield x
    yield new_item

Я думаю, вы ошибочно полагаете, что Python по-разному обрабатывает изменяемые и неизменяемые аргументы по умолчанию; это просто неправда. Скорее неизменность аргумента заставляет вас незаметно изменить код, чтобы автоматически делать правильные вещи. Возьмите пример и примените его к строке, а не к списку:

def string_append(new_item, a_string=''):
    a_string = a_string + new_item
    return a_string

Этот код не изменяет переданную строку - он не может этого сделать, потому что строки неизменяемы. Он создает новую строку и назначает a_string для этой новой строки. Аргумент по умолчанию можно использовать снова и снова, потому что он не меняется, вы сделали его копию в начале.

2 голосов
/ 14 апреля 2010

Что если вы говорите не о списках, а об AwesomeSets, классе, который вы только что определили? Вы хотите определить ".local" в каждый класс ?

class Foo(object):
    def get(self):
        return Foo()
    local = property(get)

может сработать, но очень быстро состарится, очень скоро. Довольно скоро паттерн «если a есть None: a = CorrectObject ()» становится второй натурой, и вы не найдете его уродливым - вы увидите, что он освещает.

Проблема не в синтаксисе, а в семантике - значения параметров по умолчанию оцениваются во время определения функции, а не во время выполнения функции.

1 голос
/ 14 апреля 2010

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

Таким образом, у вас есть два разных метода для передачи параметров, обеспечивающих различное поведение. И это хорошо, я бы не стал это менять.

0 голосов
/ 14 апреля 2010

Это лучше, чем good_append (), IMO:

def ok_append(new_item, a_list=None):
    return a_list.append(new_item) if a_list else [ new_item ]

Вы также можете быть очень осторожны и проверить, что a_list был списком ...

0 голосов
/ 14 апреля 2010

Я думаю, вы путаете элегантный синтаксис с синтаксическим сахаром. Синтаксис python четко связывает оба подхода, просто случается так, что правильный подход выглядит менее элегантным (с точки зрения синтаксиса), чем неправильный подход. Но поскольку неправильный подход, хорошо ... неправильный, его элегантность не имеет значения. Что касается того, почему что-то, что вы демонстрируете в better_append, не реализовано, я бы предположил, что There should be one-- and preferably only one --obvious way to do it. превосходит незначительные преимущества в элегантности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...