Большинство современных языков, которые не называются C++, по умолчанию передают параметры по ссылке, и даже простое присваивание создает новую ссылку на объект, а не копию. Я было думал, что хорошо знаком с этими тонкостями, но всё же наткнулся на вот такие грабли:
#!python
schedule_by_day = [[]] * 7 # а ведь совсем не то же самое, что [[] for _ in range(7)]for ...:
schedule_by_day[d].append(...)
Хотел создать список из семи пустых списков, а получил список из семи ссылок на один и тот же список.
Обычно стремлюсь писать в ФП-подобном иммутабельном стиле, тогда такого обычно не случается (и то не гарантия, см. известный баг: [f() for f in [(lambda: i) for i in range(10)]]), но в этой задаче императивный подход был намного естественее — и, как оказалось, опаснее.
А как в вашем любимом языке отличается передача по ссылке от передачи по значению, как это позволяет избежать подобных проблем и какие новые проблемы создает?
Здравствуйте, SergH, Вы писали:
SH>видимо так: [lambda x=i: x for i in range(10)]
Можно даже lambda i=i: i, хотя непонятно, почему вообще этот хак со значениями по умолчанию работает. А на баг я наткнулся в реальном проекте (хотя и читал о нём раньше), когда генерировал коллбеки для событий в Twisted.
А тот факт, что переменная i становится видимой вне list comprehension, исправлен в Python 3.
Здравствуйте, Roman Odaisky, Вы писали:
RO>Можно даже lambda i=i: i, хотя непонятно, почему вообще этот хак со значениями по умолчанию работает.
Потому что происходит разрешение имени i. Числа немодифицируемые, а значит for в лист компрехеншн каждый раз порождает новую переменную с именем i. Точнее, он даже не порождает, он связывает имя i с очередным элементом списка, порождённого range. Т.е. проблема в прошлый раз была в том, что лямбда запоминала не пременную, а имя. А тут она запоминает именно переменную.
Насколько я понимаю, разрешение имени тут происходит потому что это часть кода исполняется. Тело лямбды не исполняется, оно типа компилируется и запоминается, поэтому там имена разрешаются позже.
Логично, что когда-то имя должно разрешаться, иначе [i for i in range(10)] тоже бы не работало
Здравствуйте, Roman Odaisky, Вы писали:
RO>Большинство современных языков, которые не называются C++, по умолчанию передают параметры по ссылке, и даже простое присваивание создает новую ссылку на объект, а не копию.
Наоборот же! Большинство языков как раз передают параметры по-значению. Для C, Java, Javascript это вообще единственный способ передачи. Если кратко, то объекты никуда не передаются. И значением переменной является не "объект", а "ссылка на объект". Если же подробно, то вот и вот.
Здравствуйте, maxkar, Вы писали:
RO>>Большинство современных языков, которые не называются C++, по умолчанию передают параметры по ссылке, и даже простое присваивание создает новую ссылку на объект, а не копию. M>Наоборот же! Большинство языков как раз передают параметры по значению. Для C, Java, Javascript это вообще единственный способ передачи. Если кратко, то объекты никуда не передаются. И значением переменной является не "объект", а "ссылка на объект".
Это всё терминология, а на деле если есть class X { int value } и void f(X x) { x.value += 42; }, то в C эта функция не будет иметь видимого эффекта, а в Java будет.
Здравствуйте, Roman Odaisky, Вы писали:
RO>А как в вашем любимом языке отличается передача по ссылке от передачи по значению, как это позволяет избежать подобных проблем и какие новые проблемы создает?
Хм... Так много любимых языков? Ну тогда:
6.2 Режимы передачи параметров
Стандарт предусматривает четыре режима передачи параметров для подпрограмм:
in
in out
out
access
Все эти режимы не имеют непосредственных аналогов в других языках программирования. Необходимо также отметить следующее:
по-умолчанию, для передачи параметров подпрограммы, всегда устанавливается режим — "in" !!!
Для "in" / "out" скалярных значений используется механизм передачи параметров по копированию-"in" (copy-in), по копированию-"out" (copy-out). Стандарт специфицирует, что любые другие типы могут быть переданы по copy-in/copy-out, или по ссылке.
Лимитированные приватные типы (limited private types) передаются по ссылке, для предотвращения проблем нарушения приватности.
6.2.1 Режим "in"
Параметры передаваемые в этом режиме подобны параметрам передаваемым по значению в языке Паскаль, и обычным параметрам языка Си, с тем исключением, что им не могут присваиваться значения внутри подпрограммы.
Это значит, что при входе в подпрограмму, формальный параметр инициализируется значением фактического параметра, при этом, внутри подпрограммы, он является константой и разрешает только чтение значения ассоциированного фактического параметра.
Режим "in" разрешается использовать и в процедурах, и в функциях.
6.2.2 Режим "in out"
Этот режим непосредственно соответствует параметрам передаваемым по ссылке (подобно var-параметрам языка Паскаль).
Таким образом, при входе в подпрограмму, формальный параметр инициализируется значением фактического параметра. Внутри подпрограммы, формальный параметр, использующий этот режим, может быть использован как в левой, так и в правой части инструкций присваивания (другими словами: формальный параметр доступен как для чтения, так и для записи). При этом, если формальному параметру внутри подпрограммы произведено присваивание нового значения, то после выхода из подпрограммы значение фактического параметра заменяется на новое значение формального параметра.
Режим "in out" разрешается использовать только в процедурах.
6.2.3 Режим "out"
В этом режиме, при входе в подпрограмму, формальный параметр не инициализируется (!!!) значением фактического параметра. Согласно стандарта, внутри подпрограммы, формальный параметр, использующий этот режим, может быть использован как в левой, так и в правой части инструкций присваивания (другими словами: формальный параметр доступен как для чтения, так и для записи). Согласно стандарта, внутри подпрограммы, такой формальный параметр может быть использован только в левой части инструкций присваивания (другими словами: доступен только для записи). При этом, после выхода из подпрограммы, значение фактического параметра заменяется на значение формального параметра.
Режим "out" разрешается использовать только в процедурах.
6.2.4 Режим access
Поскольку значения ссылочного типа (указатели) часто используются в качестве параметров передаваемых подпрограммам, Ада предусматривает режим передачи параметров access, который специально предназначен для передачи параметров ссылочного типа.
При использовании режима access, фактический параметр, который предоставляется при вызове подпрограммы, — это любое значение ссылочного типа, которое ссылается (указывает) на объект соответствующего типа. При входе в подпрограмму, формальный параметр инициализируется значением фактического параметра, при этом, Ада производит автоматическую проверку того, что значение параметра не равно null. В случае когда значение параметра равно null генерируется исключительная ситуация Constraint_Error. Внутри подпрограммы, формальный параметр, использующий режим access, является константой ссылочного типа и ему нельзя присваивать новое значение, поэтому такие формальные параметры несколько подобны формальным параметрам, использующим режим "in". Однако, поскольку параметр является значением ссылочного типа (указателем), то подпрограмма может изменить содержимое объекта на который данный параметр ссылается (указывает). Кроме того, внутри подпрограммы такой параметр принадлежит анонимному ссылочному типу, и поскольку у нас нет возможности определить имя этого ссылочного типа, то мы не можем описать ни одного дополнительного объекта этого типа. Любая попытка конвертирования значения такого параметра в значение именованого ссылочного типа будет проверяться на соответствие правилам области действия для ссылочных типов. При обнаружении нарушения этих правил генерируется исключительная ситуация Programm_Error.
Режим access разрешается использовать и в процедурах, и в функциях.
При этом необходимо обратить внимание на то, что функции, использующие этот режим для передачи параметров, способны изменять состояние объектов на которые такие параметры ссылаются. То есть, такие функции могут обладать побочными эффектами.
Здравствуйте, SergH, Вы писали:
SH>Аааа, вот это жесть! Минут пять тупил, пока не понял, в чём тут дело. Там ещё так потом можно:
>>>> x = [lambda: i for i in range(10)] >>>> x[0]() SH>9 >>>> i = 10 >>>> x[0]() SH>10
SH>Спасибо, очень красивый пример. Но я что-то пока не придумал, как его исправить...
Здравствуйте, Roman Odaisky, Вы писали:
Кё>>Эта проблема исправлена в Python 3.
RO>python3 -c 'print([lambda: i for i in range(42)][0]())' не соглашается с тобой. Там исправлена только утечка i наружу.
Я как раз и говорил про проблемы со скопами (см. то что я процитировал).
Здравствуйте, Roman Odaisky, Вы писали:
RO>А как в вашем любимом языке отличается передача по ссылке от передачи по значению, как это позволяет избежать подобных проблем и какие новые проблемы создает?
Здравствуйте, Mystic, Вы писали:
M>6.2.3 Режим "out"
M>В этом режиме, при входе в подпрограмму, формальный параметр не инициализируется (!!!) значением фактического параметра. Согласно стандарта, внутри подпрограммы, формальный параметр, использующий этот режим, может быть использован как в левой, так и в правой части инструкций присваивания (другими словами: формальный параметр доступен как для чтения, так и для записи).Согласно стандарта, внутри подпрограммы, такой формальный параметр может быть использован только в левой части инструкций присваивания (другими словами: доступен только для записи). При этом, после выхода из подпрограммы, значение фактического параметра заменяется на значение формального параметра.
M>Режим "out" разрешается использовать только в процедурах.
Здравствуйте, icWasya, Вы писали:
W>Здравствуйте, Mystic, Вы писали:
M>>6.2.3 Режим "out"
M>>В этом режиме, при входе в подпрограмму, формальный параметр не инициализируется (!!!) значением фактического параметра. Согласно стандарта, внутри подпрограммы, формальный параметр, использующий этот режим, может быть использован как в левой, так и в правой части инструкций присваивания (другими словами: формальный параметр доступен как для чтения, так и для записи).Согласно стандарта, внутри подпрограммы, такой формальный параметр может быть использован только в левой части инструкций присваивания (другими словами: доступен только для записи). При этом, после выхода из подпрограммы, значение фактического параметра заменяется на значение формального параметра.
M>>Режим "out" разрешается использовать только в процедурах.
W>Это копипаста? и как правильно?
Это я потер лишнее. Для Ada83 параметр доступен для чтения и для записи. Начиная с Ada95 (2005 и 2012) параметр не доступен для чтения.