By value/by reference
От: Roman Odaisky Украина  
Дата: 03.04.13 10:08
Оценка: 21 (1)
Большинство современных языков, которые не называются 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)]]), но в этой задаче императивный подход был намного естественее — и, как оказалось, опаснее.

А как в вашем любимом языке отличается передача по ссылке от передачи по значению, как это позволяет избежать подобных проблем и какие новые проблемы создает?
До последнего не верил в пирамиду Лебедева.
Re: By value/by reference
От: SergH Россия  
Дата: 03.04.13 12:19
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Хотел создать список из семи пустых списков, а получил список из семи ссылок на один и тот же список.


Ну, на это я достаточно давно наткнулся.

RO> [f() for f in [(lambda: i) for i in range(10)]]


Аааа, вот это жесть! Минут пять тупил, пока не понял, в чём тут дело. Там ещё так потом можно:

>>> x = [lambda: i for i in range(10)]
>>> x[0]()
9
>>> i
9
>>> i = 10
>>> x[0]()
10


Спасибо, очень красивый пример. Но я что-то пока не придумал, как его исправить...
Делай что должно, и будь что будет
Re[2]: By value/by reference
От: SergH Россия  
Дата: 03.04.13 12:23
Оценка:
Здравствуйте, SergH, Вы писали:

SH>Спасибо, очень красивый пример. Но я что-то пока не придумал, как его исправить...


видимо так: [lambda x=i: x for i in range(10)]
не очень красиво менять прототип, но никто ведь не заметит
Делай что должно, и будь что будет
Re[3]: By value/by reference
От: Roman Odaisky Украина  
Дата: 03.04.13 15:36
Оценка:
Здравствуйте, SergH, Вы писали:

SH>видимо так: [lambda x=i: x for i in range(10)]


Можно даже lambda i=i: i, хотя непонятно, почему вообще этот хак со значениями по умолчанию работает. А на баг я наткнулся в реальном проекте (хотя и читал о нём раньше), когда генерировал коллбеки для событий в Twisted.

А тот факт, что переменная i становится видимой вне list comprehension, исправлен в Python 3.
До последнего не верил в пирамиду Лебедева.
Re[4]: By value/by reference
От: SergH Россия  
Дата: 03.04.13 15:50
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Можно даже lambda i=i: i, хотя непонятно, почему вообще этот хак со значениями по умолчанию работает.


Потому что происходит разрешение имени i. Числа немодифицируемые, а значит for в лист компрехеншн каждый раз порождает новую переменную с именем i. Точнее, он даже не порождает, он связывает имя i с очередным элементом списка, порождённого range. Т.е. проблема в прошлый раз была в том, что лямбда запоминала не пременную, а имя. А тут она запоминает именно переменную.

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

Логично, что когда-то имя должно разрешаться, иначе [i for i in range(10)] тоже бы не работало
Делай что должно, и будь что будет
Re: By value/by reference
От: maxkar  
Дата: 03.04.13 16:00
Оценка: +1 :)
Здравствуйте, Roman Odaisky, Вы писали:

RO>Большинство современных языков, которые не называются C++, по умолчанию передают параметры по ссылке, и даже простое присваивание создает новую ссылку на объект, а не копию.

Наоборот же! Большинство языков как раз передают параметры по-значению. Для C, Java, Javascript это вообще единственный способ передачи. Если кратко, то объекты никуда не передаются. И значением переменной является не "объект", а "ссылка на объект". Если же подробно, то вот и вот.
Re[2]: By value/by reference
От: Roman Odaisky Украина  
Дата: 03.04.13 16:44
Оценка: +1
Здравствуйте, maxkar, Вы писали:

RO>>Большинство современных языков, которые не называются C++, по умолчанию передают параметры по ссылке, и даже простое присваивание создает новую ссылку на объект, а не копию.

M>Наоборот же! Большинство языков как раз передают параметры по значению. Для C, Java, Javascript это вообще единственный способ передачи. Если кратко, то объекты никуда не передаются. И значением переменной является не "объект", а "ссылка на объект".

Это всё терминология, а на деле если есть class X { int value } и void f(X x) { x.value += 42; }, то в C эта функция не будет иметь видимого эффекта, а в Java будет.
До последнего не верил в пирамиду Лебедева.
Re: By value/by reference
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 03.04.13 17:45
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>А как в вашем любимом языке отличается передача по ссылке от передачи по значению, как это позволяет избежать подобных проблем и какие новые проблемы создает?


Хм... Так много любимых языков? Ну тогда:


6.2 Режимы передачи параметров

Стандарт предусматривает четыре режима передачи параметров для подпрограмм:


Все эти режимы не имеют непосредственных аналогов в других языках программирования. Необходимо также отметить следующее:

Лимитированные приватные типы (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 разрешается использовать и в процедурах, и в функциях.

При этом необходимо обратить внимание на то, что функции, использующие этот режим для передачи параметров, способны изменять состояние объектов на которые такие параметры ссылаются. То есть, такие функции могут обладать побочными эффектами.
Re[2]: By value/by reference
От: Кодёнок  
Дата: 03.04.13 18:11
Оценка:
Здравствуйте, SergH, Вы писали:

SH>Аааа, вот это жесть! Минут пять тупил, пока не понял, в чём тут дело. Там ещё так потом можно:


>>>> x = [lambda: i for i in range(10)]

>>>> x[0]()
SH>9
>>>> i = 10
>>>> x[0]()
SH>10

SH>Спасибо, очень красивый пример. Но я что-то пока не придумал, как его исправить...


Эта проблема исправлена в Python 3.
Re[3]: By value/by reference
От: Roman Odaisky Украина  
Дата: 03.04.13 18:29
Оценка:
Здравствуйте, Кодёнок, Вы писали:

Кё>Эта проблема исправлена в Python 3.


python3 -c 'print([lambda: i for i in range(42)][0]())' не соглашается с тобой. Там исправлена только утечка i наружу.
До последнего не верил в пирамиду Лебедева.
Re[4]: By value/by reference
От: Кодёнок  
Дата: 03.04.13 19:00
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

Кё>>Эта проблема исправлена в Python 3.


RO>python3 -c 'print([lambda: i for i in range(42)][0]())' не соглашается с тобой. Там исправлена только утечка i наружу.


Я как раз и говорил про проблемы со скопами (см. то что я процитировал).
Re: By value/by reference
От: Ziaw Россия  
Дата: 04.04.13 03:08
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>А как в вашем любимом языке отличается передача по ссылке от передачи по значению, как это позволяет избежать подобных проблем и какие новые проблемы создает?


В руби та же засада

irb(main):034:0> a = [[]] * 7
=> [[], [], [], [], [], [], []]
irb(main):035:0> a[0] << 1
=> [1]
irb(main):036:0> a
=> [[1], [1], [1], [1], [1], [1], [1]]


Но там хотя бы нет такого глюка с параметром по умолчанию:

python
>>> def x(arr = []):
...   arr.append(1)
...   print arr
...
>>> x()
[1]
>>> x()
[1, 1]
>>> x()
[1, 1, 1]


ruby
irb(main):003:0> def x arr=[]; arr << 1; puts arr; end
=> nil
irb(main):004:0> x
1
=> nil
irb(main):005:0> x
1
=> nil
irb(main):006:0> x
1


coffeescript
x = (arr = []) ->
  arr.push(1)
  console.log(arr)

x()
[1]
x()
[1]
x()
[1]


Вывод — иммутабельность и функциональный стиль ftw.
Re[2]: By value/by reference
От: icWasya  
Дата: 04.04.13 10:24
Оценка:
Здравствуйте, Mystic, Вы писали:

M>6.2.3 Режим "out"


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


M>Режим "out" разрешается использовать только в процедурах.


Это копипаста? и как правильно?
Re[3]: By value/by reference
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 04.04.13 10:49
Оценка:
Здравствуйте, icWasya, Вы писали:

W>Здравствуйте, Mystic, Вы писали:


M>>6.2.3 Режим "out"


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


M>>Режим "out" разрешается использовать только в процедурах.


W>Это копипаста? и как правильно?


Это я потер лишнее. Для Ada83 параметр доступен для чтения и для записи. Начиная с Ada95 (2005 и 2012) параметр не доступен для чтения.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.