Comments 78
Очень хорошо, всё основное в одно месте, хотя точнее это «которые допускают начинающие разработчики», ну и надо коллективным разумом вспомнить чего не хватает, например, про «общий» except.
Практически все эти глюки избегаются тестированием и использованием анализаторов типа pylint и flake8.
Если честно, то всё это со стороны других языков (может, кроме JS) выглядит как ошибки в проектировании или реализации языка. Ну, то есть, поведение крайне неочевидно. Причём неочевидно в очень простых местах (типа мутабельных значений по умолчанию в аргументах). Если язык в самых базовых моментах требует от тебя понимания своей работы изнутри — что-то с этим языком сильно не так.
Имхо, конечно. И разумеется это не перечеркивает некоторые плюсы питона.
Вот уже точно не ошибки в реализации, когда понимаешь как это реализовано, понимаешь почему это так.
«Мутабельные значения по умолчанию в аргументах» — это ни разу не простое место и используются они редко.

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

Надо сказать, что Python2 к Python3 всё равно поменялся несовместимым образом, так что возможность "со второго раза сделать правильно" была, но не использована.
Пример с генератором лямбд — он самый неочевидный для меня. В лямбде, значит, i связывается по ссылке. А в выражении for i in range(5) — по значению?! Почему тогда [[i] for i in range(3)] возвращает [[0], [1], [2]], а не [[2], [2], [2]]?

А, хотя в лямбде же лексическое связывание, а не по ссылке и не по значению.

А ещё с одной стороны — вопрос семантики и того, что в питоне тело цикла не создаёт новую область видимости.
В Julia:


function create_multipliers_1(n::Integer)
    let m=[]
        let itr=0:n-1
            _next = iterate(itr)
            while _next ≢ nothing
                i = _next[1]
                push!(m, x->i*x)
                _next = iterate(itr, _next[2])
            end
        end
        m
    end
end

function create_multipliers_2(n::Integer)
    let m=[]
        let itr=0:n-1, i=nothing
            _next = iterate(itr)
            while _next ≢ nothing
                i = _next[1]
                push!(m, x->i*x)
                _next = iterate(itr, _next[2])
            end
        end
        m
    end
end

foreach(m->print(m(2), " "), create_multipliers_1(5)) # 0 2 4 6 8
foreach(m->print(m(2), " "), create_multipliers_2(5)) # 8 8 8 8 8

Выражение for i in itr в Julia семантически эквивалентно тому, как развёрнуто в первой функции.

А ещё с одной стороны — вопрос семантики и того, что в питоне тело цикла не создаёт новую область видимости.

Возможно из-за того, что у цикла может быть секция else. Могу ошибаться.

Тут, можно сказать, есть более глубокая проблема (или не совсем проблема, это кто как видит): в питоне вообще единственный способ создать вложенную область видимости — это написать функцию. Никаких там let, фигурных скобочек или чего-то ещё. Честно — понятия не имею, создаёт это проблемы питонистам или нет. В Julia удобнее, когда каждый блок создаёт локальную область видимости, но больше с точки зрения оптимизации кода компилятором.

До меня дошло, что не так с лямбдами. Проблема больше в том, что Гвидо давным-давно не упёрся и не сказал "не бывать тут лямбдам". В итоге они есть, но со списковыми включениями неправильно работают.
Суть: включения позиционируются как замена map. Но [<expr> for x in collection] не соответствует семантически map(lambda x: <expr>, collection), когда <expr> — это лямбда! Включение захватит x лексически, а map — по значению.

Потому что в Py3:
>> type(range(3))
<class 'range'>


А при итерировании этот класс возвращает значение.

Про лямбду сами выше ответили :)
«Простое место» не в плане того, как оно работает на самом деле, а как оно преподносится сферическом новичку в вакууме. То есть если взять гипотетический курс «Python для вашего маркетолога», то там, когда дойдут до функций, расскажут про чудесные позиционные аргументы, keyword-аргументы и покажут пример мегаудобной фичи со значениями по умолчанию. И всё выглядит красиво и понятно, но потом тебе говорят, что если поставить дефолтным значением список, то всё будет плохо. А если не список, а множество (круглые скобочки вместо квадратных), то всё будет ок. А потом начинают жесть про мутабельность. А сверху присыпят тем, что передавая что угодно в функцию ты никогда не можешь быть уверен, что это что-то останется без изменений. И это чуть ли не в самом начале освоения языка, который все называют очень простым и понятным, что даже гуманитарии из маркетинга осваивают его.
А если не список, а множество (круглые скобочки вместо квадратных), то всё будет ок.

Это не так, множество изменяемо, поэтому будет ровно та же самая проблема.

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

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

Вот только в том комментарии написано "множество", а не "кортеж" или "tuple", а слово "набор" в таком контексте я вообще слышу впервые.

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

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

Для меня, как для человека с математическим образованием, всё это выглядит, мягко скажем, дико… но вот почему-то имеено так устроен этот мир… я просто принял это к сведению, так как понять — почему он именно так устроен, я не в состоянии…
Эм, я питоном не пользуюсь, но вот если это тело функции то разве поведение кода не очевидно?
>>> def foo(bar=[]):        # bar - это необязательный аргумент 
                            # и по умолчанию равен пустому списку.
...    bar.append("baz")    # эта строка может стать проблемой...
...    return bar


Смотрим в гугль: «append» (англ.) — «присоединять» (ну или тут python list).
Ну вроде логчно, было бы там что-то типа define, set, initialize и тп, тогда да.
Тут скорее не ошибка, а не желание новичка хотя бы мануал почитать.

Нет, ошибка не очевидна, т.к. bar является аргументом метода, а ведёт себя как локальная статическая переменная.

В документации все это описано, значит не ошибка, а проблема грамотности разработчика.

Всё же от языков программирования в 2019-м году ожидают большую «человечность» и отсутствие необходимости штудировать документацию, чтобы не совершать ошибок в таких базовых вещах. Особенно для языка, который на каждом шагу называется самым простым и понятным в освоении, что его даже детям предлагают в школах изучать.
Если будет язык программирования более человечным, то не избежит логических проблем, а это приведёт к тому, что он не будет логически цельным. А ещё хуже, это может привести к ухудшению производительности программ в угоду нежеланию людей читать документацию.

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

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

Принцип наименьшего удивления никто не отменял.
Просто в большинстве других языков сделано наоборот.

Если сделано по другому, это не значит что ошибка.

Принцип наименьшего удивления никто не отменял.

Это относится к психологии и никак не к языкам программирования.
Это относится к психологии и никак не к языкам программирования.
Образованность она такая…
The principle of least astonishment (POLA), also called the principle of least surprise (alternatively a «law» or «rule»)[1][2] applies to user interface and software design.[3]

[1]The Art of Unix Programming.
Линк
Так и написано, что применим для пользовательского интерфейса и к софтверному дизайну. Это не затрагивает того как это будет использовать компьютер. Т.е. это больше относится к психологии человека, чем к работе машины.
Открою вам секрет: языки программирования — они вообще больше про человека, чем про машину. Машине ничего, кроме машинных кодов, не нужно…
А как, по твоему переводится и понимается, software design?

Не благодари, заработал.
У вас какое-то устойчивое недопонимание темы. Процессору по барабану какой software design. Он оперирует кодом, а не языком программирования, хотя и использует интерпретаторы и компиляторы чтобы привести код человека в свой понятный. Человеку нужен software design чтобы не запутаться в хитросплетении вызовов, методов и классов.

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

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


def foo(bar = None):
    bar = [] if bar is None else bar
    ...

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


Даже в Common Lisp, который был стандартизован в годы, когда питон только-только был представлен публике, выражение для значения по умолчанию вычисляется при каждом вызове. И это прописано в стандарте.


CL-USER> (defun foo (&optional (bar (make-array 0 :fill-pointer t)))
           (vector-push-extend 'baz bar)
           bar)
FOO
CL-USER> (foo)
#(BAZ)
CL-USER> (foo)
#(BAZ)
Вот уж воистину, просто и элегантно. А главное, добавляет в питон уникальную идиому, которой нет больше ни в одном языке.

А что языки программирования должны походить друг на друга? И приходить к пониманию даже для человеку ни разу не читающего руководство по языку?

Именно с мутабельными аргументами по умолчанию — можно было их вообще запретить, когда обратную совместимость ломали. Благо неизменяемых типов не так уж много, легко можно проверить.
Нет, я не против, когда в языке оставляют возможность выстрелить себе в ногу, потому что иногда надо. Но тут слишком близко к поверхности, и описание этого в руководстве не отменяет того, что это не очень хорошее решение в плане дизайна языка.
Хотя, спору нет, таких вот скользких мест на поверхности гораздо меньше, чем в Си, например, и в целом написать корректно работающую (пускай и медленную) программу не в пример проще.

Благо неизменяемых типов не так уж много, легко можно проверить.
Собственно код для этой проверки уже существует, так как мутабельные типы не могут использоваться в качестве ключей.
Нет, я не против, когда в языке оставляют возможность выстрелить себе в ногу, потому что иногда надо. Но тут слишком близко к поверхности, и описание этого в руководстве не отменяет того, что это не очень хорошее решение в плане дизайна языка.

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

Дизайн языка это компромисс в многих местах. Еще почему сделали единичную инициализацию аргументов по умолчанию, это из-за такого мощного свойства языка Python как развертываний args и kwargs в аргументах функций. Если бы при каждом вызове аргументы инициализировались, то про развертывание аргументов можно было бы забыть.
Не подстраивается ли? А по мне так как раз наоборот — последние лет 10-15 каждый новый язык пытается быть проще для входа и с как можно более плавной кривой обучения. Особенно если язык общего назначения и для общего же значения и используется. Читать спецификацию языка, чтобы писать без ошибок несложные программы — это очень странно и сейчас точно ни один из новых языков не предлагает себя изучать так.
Возможно вы имеете дело с языками для обучения или узкоспециализированными. Возьмите язык широкого применения такой как Rust или Go и попробуйте на нем что-то внятное написать. Я думаю, сразу измените свое мнение.

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

Сейчас в IT лезет много народа, который хочет только получать деньги, при этом люди совершенно не пытается стать профессионалами хотя бы в том языке на котором они пишут.
Я уже не первый год слышу речи на собеседовании подобные вашей. Джунторы поголовно не читают спецификации, из мидлов половина читала.
А вам не кажется что уже этот факт является достаточным для того, чтобы сказать что «современный мир разработки именно что подстраивается под этих людей» — иначе как бы они смогли работать хотя бы джунами или, тем более, дорасти до миддлов?

Естественно ни один из нечитающих вряд ли пройдет собеседование.
Ну если вашей компании важнее, чтобы человек вызубрил описание языка (а будет ли он при этом уметь что-то ещё — уже не так важно), то это ведь ваш выбор.

Того факта, что огромное количество компаний (Facebook, Google, Microsoft и прочие монстры… о мелких стартапах я уж и не говорю) этого не требут — достаточен для того, чтобы сказать подо что подстраивается «современный мир разработки».
А вам не кажется что уже этот факт является достаточным для того, чтобы сказать что «современный мир разработки именно что подстраивается под этих людей» — иначе как бы они смогли работать хотя бы джунами или, тем более, дорасти до миддлов?

В моей памяти с десяток примеров когда джун за пару лет превращался в сеньора, а через год уходил в Яндекс, IBM и т.д. А секрет из просто: отличное знание спецификации языка, паттерны, алгоритмы по Кнуту, а для вишенки от
приличный профиль на сайте типа HackerRank. И что странно, ни один из этих людей не жаловался на язык, что он работает как-то не так или что в нем ошибки.

Ну если вашей компании важнее, чтобы человек вызубрил описание языка (а будет ли он при этом уметь что-то ещё — уже не так важно), то это ведь ваш выбор.

Да, это мой выбор, так как я провожу собеседования соискателей. И я не хочу тратить время, чтобы потом вбивать людям терминологию которая принята в языке. Где я работаю, не такие богатые как Google и Microsoft, поэтому тратить деньги на проходимцев никто не будет. Нам нужны люди решающие задачи и проблемы, могущие легко объяснить суть коллеге, а не те кто себя практиковал на нестандартном мышлении.
Вау! Сколько пафоса-то. Я вам всё-таки советую задуматься над тем, почему вы — «не такие богатые как Google и Microsoft» и как это связана с тем, что вы идёте поперёк того, что делает индустрия.

Google и Microsoft, как бы, не всегда такими богатыми были, напоминаю. И если они смогли заработать достаточно денег для того, чтобы «тратить деньги на проходимцев» — а у вас их до сих пор недостаточно, то, может быть, проблема не в «проходимцах».
В моей памяти с десяток примеров когда джун за пару лет превращался в сеньора, а через год уходил в Яндекс, IBM и т.д.
Да, это мой выбор, так как я провожу собеседования соискателей
И то и то — липа. Время расставит нубов по местам, либо они научатся
Возьмите язык широкого применения такой как Rust или Go
А что ты знаешь про Го? Он создавался как язык с низким порогом вхождения. И на этом преуспел (в т.ч.)
Возможно вы имеете дело с языками для обучения или узкоспециализированными. Возьмите язык широкого применения такой как Rust или Go и попробуйте на нем что-то внятное написать. Я думаю, сразу измените свое мнение.

Я брал языки широкого применения от хаскеля до идриса (до того мой основной язык — C++), а сейчас вот ковыряю Coq (уж где инопланетность-то, особенно после языков типа агды или того же идриса). Так вот, таких сюрпризов, как в питоне, нет.


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

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

Но большинство людей не хотят и не любят разбираться вообще ни в чём! Они стремятся к получению набора готовых рецептов — ну и последующему выбору из них. Как эти рецепты связаны между собой — они даже не хотят задумываться.

И для вот такого подхода — Python неплохо подходит. Ну а те, кому хочется или нужно таки понимать — могут разобраться с реализацией… И у них тоже будет всё хорошо.

По той же причине «взлетел» Git, кстати. Им тоже невозможно пользоваться, не имея некоторого представления о том, как он устроен «внутри» (хотя там есть забавное читерство: есть некоторое «правдоподобное объяснение» о том, как Git устроен внутри и без него ничего понять невозможно, но оно уже давным-давно не описывает «реальный» Git… точное же описание того, что у Git'а внутри бывает нужно очень-очень редко).

Можно пояснить, что именно вы понимаете под "развёртыванием аргументов", и почему это конфликтует с инициализацией аргументов при вызове?


А то я как бы с Julia прихожу посмотреть, там есть и splat, т.е. f(x, rest...) принимает произвольное число аргументов, и инициализация аргументов по умолчанию при каждом вызове. Однократная инициализация, кстати, тоже ограничивает — выражение для значения по умолчанию не может использовать значения предыдущих аргументов, вроде f(x, y=sin(x)) = x + y. Точнее, может, но только если на момент объявления функции x будет в глобальной области видимости, и результат будет немного не тот...


Но, конечно, настоящий разработчик™ знает весь стандарт назубок и таких ошибок допускать не будет, поэтому всё нормально.

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

def test(*args, foo=[1]):
    print(args, foo)

Т.е. сколько аргументов не передавай, foo всегда будет проинициализированна, а это тоже самое что вот такой код:
def test(*args):
    foo=[1]
    print(args, foo)

Т.е. какая-то не нужная бессмыслица. Поэтому сейчас выдается SyntaxError.

Ну я не знаю, Julia c этим как-то живёт, без ошибок синтаксиса и без эквивалентности первой и второй функции:


julia> test(args...; foo=[1]) = println(args, " ", foo)
test (generic function with 1 method)

julia> test()
() [1]

julia> test(42)
(42,) [1]

julia> test(42, foo=42)
(42,) 42

Но там позиционные аргументы строго отделены от именованных (; в списке параметров). Но и в питоне можно понимать первую запись как то, что все аргументы после *args могут быть только именованными — вполне логичная семантика, как по мне.
Но фиг с ним, собственно, нельзя так нельзя. Почему нельзя для значения по умолчанию проверить тип и точно так же сказать "нельзя", если он оказался мутабельным? Ведь даже в документации признаётся, что это, скорее всего, не то, что программист хотел сказать. Чтобы, если уж хочется стрелять себе в ногу — это нужно было явно задекларировать:


_default_bar_in_foo = []

def foo(bar=None):
    bar = _default_bar_in_foo if bar is None else bar
    bar.append("baz")
    return bar

И, как я писал, — конечно, сейчас уже поздно это менять. Но Python3 всё равно ломал обратную совместимость, чёрт подери! Можно было тогда и сделать.

Не минусуйте пожалуйста, я просто не очень хорошо разбираюсь.
На сколько я понимаю bar=[] создается во время импорта модуля с этой функцией и соотвественном выделение памяти под bar происходит 1 раз, нужно ли для «условной очевидности» выделять память каждый раз при вызове foo с дефолтными аргументами?

Да в Питоне, вроде бы, выделение памяти не считается за криминал. Сборщик мусора же чистит эту память, если она больше не используется.

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

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

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

Пришлось бы тогда при каждом вызове вызывать декоратор,
Зачем?

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

Народ во сидит здесь и удивляется, что это ошибка, поверхностно судя и не вникая в суть почему это так работает.
А зачем «вникать в суть» неудобного и небезопасного поведения? Ну вот возьмите Ситроен с опцией торможения пассажиром. Почему вместо того, чтобы «вникнуть в суть того, как это работает» его отозвали? Ну потому что неудобно и небезопасно, блин.

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

Почему LISP полвека назад или C++ четверть века назад это делали ожидаемым и естественным способом, а Python — не может?

Ну Ok, совместимость, к этому уже все привыкли… могу понять — достаточно веская причина.

Но вот рассказы про декораторы и прочее — оставьте для людей, не умеющих в программирование. Люди, понимающие как это всё устроено как раз легко могут вам предложить несколько вариантов, которые могли бы проблему решить. Вопрос тут не во внутреннем устройстве а как раз исключительно «в человеческом факторе»: неясно — стоит ли исправление этой проблемы на третьем десятке жизни языка возможного хаоса или нет. Всё. Никакиз других веских причин для этого нет.
Почему?

Потому что декоратор изменяет функцию один раз.
Зачем?

Чтобы проинициализировать аргументы.

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

Вы прочитайте про передачу аргументов через * или **, возможно тогда поймете почему так, а не иначе. Странное желание видеть все языки одинаковыми. И пожалуйста, не начинайте демагогию, если вы совершенно не разбираетесь в Python.
Вы прочитайте про передачу аргументов через * или **, возможно тогда поймете почему так, а не иначе.
Какое имеет отношение передача аргументов через * и ** к их инициализации?

Странное желание видеть все языки одинаковыми.
Не одинаковыми. Логичными. То что одинаково выглядящие выражения «a = []», записанные в заголовке функции и в её теле работают по разному — ну вот ни разу не логично. И некрасиво. Да, можно пытаться это объяснять соображениями эффективности… (хотя «эффективность» и «питон» в одном предложении уже «делают мне смешно»), но вот никаких неразрешимых проблем с передачей параметров или декораторами отказ от этой «фичи» не вызывает.

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

Да, действительно, Python так устроен, что он вначале вычисляет все аргументы, а потом только передаёт этот список в декораторы и делает другие манипуляции. Ок, пусть будет так. Однако… метод «откладывания» вычислений всем, как бы, хорошо известен: лямбда. Храните в этом списке не готовые аргументы, а лямбды, которые эти аргументы вычислят в нужный момент — и всё. Вот совсем всё: декораторы вам больше не нужно вызывать 100500 раз, и никаких трюков с * или ** вам тоже не нужно. Эффективности не хватает? Ну, введите «псевдолямбду» в реализации, одна проверка одного бита для языка, который и так, при вызове функций, выполняет сотни машинных инструкций — это немного.

Ну или просто запретите модифицируемые типы данных использовать в качестве аргументов по умолчанию — запрещены же они в качестве ключей? Для этого вообще ничего менять не нужно.

На крайний случай хотя бы честно напишите в документации «по историческим причинам есть такая бяка» (собственно сейчас так и сделано).

Только не нужно обосновывать свои недоработки «глубинными материями» — глупо выглядит.

В Лиспе тоже есть, конечно, некоторые заморочки:


CL-USER> (defun foo (&optional (bar '(spam)))
           (nconc bar '(spam))
           bar)
; WARNING:
;   Destructive function NCONC called on constant data: (SPAM)
;   See also:
;     The ANSI Standard, Special Operator QUOTE
;     The ANSI Standard, Section 3.2.2.3
; 
; compilation unit finished
;   caught 1 WARNING condition
FOO
CL-USER> (foo)
(SPAM SPAM)
CL-USER> (foo)
; бесконечный цикл

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


CL-USER> (defun oof (&optional (bar (list 'spam)))
           (nconc bar (list 'spam))
           bar)
OOF
CL-USER> (oof)
(SPAM SPAM)
CL-USER> (oof)
(SPAM SPAM)

Если оставить вызов в аргументе функции, но оставить литерал в теле, то тоже будут весёлые побочные эффекты:


CL-USER> (defun ofo (&optional (bar (list 'spam)))
           (nconc bar '(spam))
           bar)
OOF
CL-USER> (ofo)
(SPAM SPAM)
CL-USER> (ofo)
(SPAM SPAM) ; всё хорошо? как бы не так!
CL-USER> (let ((spam (ofo)))
           (setf (nth 1 spam) 'ham)
           spam)
(SPAM HAM)
CL-USER> (ofo)
(SPAM HAM)

Ещё, говорят, реализация вольна внутри одного определения функции два одинаковых литерала по одной ссылке связывать, но в SBCL я такого не видел.

Тут, кстати Раст упоминался…

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

Ну и как бы оценить, типичный подход, подход в Питоне и подход в Расте.
Да за что я должен вас благодарить? По моему, вы своим хамским поведением поставили себя вне дискуссии.
Топ 10 ошибок, упоминается Python 2 — и ни слова про UnicodeDecodeError? Ну-ну :)
Решение этой распространенной проблемы с Python будет таким:
def create_multipliers():
return [lambda x, i=i : i * x for i in range(5)]

Фи, второй параметр добавлять. Правильное решение — это


def create_multipliers():
     return [(lambda k: lambda x: k * x)(i) for i in range(5)]
С чего это линтер костылём называть? Каждый человек устаёт, отвлекается, ошибается и ошибки пропускает и это неотключаемый биологический факт.
И то, что в данный конкретный момент каждый конкретный ты-я-они-все помнят про вот такое и вот эдакое поведение — вовсе не значит, что оно будет помнится и завтра, когда случатся магнитная буря, телефонный спам от банков, пробки по дороге и перемена настроения маркетинга «теперь нам нужно вот так».
Костыль для компилятора, а не для человека =)
Который подпирает недоработки в компиляторе и в дизайне языка.
>>> def foo(bar=None):
… if bar is None: # or if not bar:
… bar = []
… bar.append(«baz»)
… return bar


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

Потому что в Питоне происходит не присваивание, а связывание, а типы данных бывают изменяемые и неизменяемые. "Значением" изменяемого типа является ссылка на данные, а неизменяемого — сами данные.
Расшифровка определения функции:


  • При создании: Определить функцию foo от одного аргумента bar; вычислить выражение справа от bar= (вычисляется в None) и, если при вызове bar не дан явным аргументом, связать bar с получившимся объектом.
  • При вызове: если bar связано с объектом None, создать пустой список и связать bar с этим списком.

Кстати, # or if not bar — неверный комментарий, т.к. not [] вычисляется в True — т.е. если вместо if bar is None будет if not bar — то при вызове foo() на пустом списке "baz" добавится в новый список, а не в уже имеющийся.

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

Поэтому в нем нет переменных, а есть индентификаторы. Хотя многие по пивычке называют их переменными, хотя это неправильно. На собеседованиях на сеньора это спрашивают, чтобы понять владеет ли собеседуемый терминологией Python.
На собеседованиях на сеньора это спрашивают, чтобы понять владеет ли собеседуемый терминологией Python.
А не подскажите — на собеседованиях в какую именно компанию такие вещи спрашивать принято?

Ну просто чтобы чтобы не тратить время на общение с ней: последнее чего я хотел бы на работе — так это общаться с людьми, которые, вместо того, чтобы обсудлать эффктивность решения или его расширяемость — будут придираться к терминам и заниматься другими видами bikeshedding'а.
Судя по тому в каких хабах вы состоите на Хабре, вас не особо интересует разработка и языки программирование. Не льстите себе, вряд ли вас возьмут в девелоперскую компанию. Будьте хотя бы честным с самим собой!

Вот никогда не могу пройти мимо диагностики по юзерпику, так что можно мне тоже диагноз, пожалуйста?

Смешно. Если твой код вот прямо сейчас инсталлирован на миллиард или два компьютеров, но вы не подписаны на «правильные» хабы на Хабре и у вас нет профиля на HackerRank — то вы не разработчик?

Ну тогда понятно почему от вас, по вашим же словам, через два-три года люди уходят ибо вы не можете обеспечить им достойную зарплату… продолжайте в том же духе!
Я больше скажу… судить разраба по haсker-rank это удел душевнобольных, учитывая то, что там сидит 90% индусов и столько-же задротов, тупо зазубривших матчасть.
>>> numbers[:] = [n for n in numbers if not odd(n)]  # ahh, the beauty of it all


Зачем здесь срез [:]. Почему бы в данном примере не сделать просто numbers =?

Потому что здесь происходит замена данных, а не присваивание (связывание). Рассмотрим пример с обычным присваиванием:


numbers = [1, 2, 3]
other_number = numbers

numbers = [4, 5, 6]
# В данной ситуации, значение `other_numbers` не меняется, т.к. эта переменная продолжает ссылаться на область в памяти, к которой она была привязана изначально
print(other_numbers)
>>> [1, 2, 3]

Теперь с [:] = [...]:


numbers = [1, 2, 3]
other_number = numbers

numbers[:] = [4, 5, 6]
# Значение `other_numbers` меняется, т.к. обе переменные ссылаются на одну и ту же область памяти 
print(other_numbers)
>>> [4, 5, 6]
Это понятно. Но ведь в исходном примере нет other_numbers и его результат нисколько не изменится если убрать "[:]". Поэтому и вопрос был: зачем срез в данном конкретном примере?
возможно чтобы показать именно изменение списка, а не просто assignment с новым списком.
Only those users with full accounts are able to leave comments. Log in, please.