juan_gandhi: (Default)
Juan-Carlos Gandhi ([personal profile] juan_gandhi) wrote2008-05-14 12:56 pm

dynamic vs static

Я не собираюсь вступать в эту нонешнюю дискуссию. Просто хочу заметить, что речь идёт о "гарвардской архитектуре" супротив "архитектуры фон Неймана".

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

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

Ну а так, наверное, стоит почитать, конечно: Егге устраивает разгром в столице статической типизации

[identity profile] faceted-jacinth.livejournal.com 2008-05-15 08:35 am (UTC)(link)
i = 1
def a():
    print i
    i = 2
a()

Where is your God now?
nine_k: A stream of colors expanding from brain (Default)

[personal profile] nine_k 2008-05-18 05:10 pm (UTC)(link)
Хотите ли вы сказать, что исполнение оператора def в этом примере привязывает значение i к тому объекту, который был в момент исполнения? Думаю, вряд ли вы так полагаете :) Каждый раз при исполнении функции a() происходит name lookup для i в окружающем scope (как я понимаю, инструкция LOAD_NAME делает именно это).

В наезде выше, разумеется, под "компиляцией" имеются в виду преобразования c разрешением ссылок (early binding; в этом смысле финальный этап компиляции доделывает линкер), как это происходит в, условно говоря, наследниках алгола. Тут я выразился неверно. Разумеется, компиляция собственно исполнимого кода в более простую форму (типа байткода) возможна и практикуется и в динамических языках.

[identity profile] faceted-jacinth.livejournal.com 2008-05-18 05:27 pm (UTC)(link)
Я хочу сказать, что
>>> def a():
...  print i
...  i = 2
...
>>> a()
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 2, in a
UnboundLocalError: local variable 'i' referenced before assignment

Если закомментить присваивание, то ничего подобного не происходит, естественно.

Это означает, что при определении функции питон аккуратно _компилирует_ её тело, строя табличку локальных переменных, направляя обращения в эту табличку и всё такое. Если бы питон был чисто интерпретируемым, у него, конечно, не было бы возможности кинуть эксепшен на строчке "print i" ДО ТОГО, как выполнилась строчка "i = 2", определяющая i как локальную переменную.
nine_k: A stream of colors expanding from brain (Default)

[personal profile] nine_k 2008-05-18 06:39 pm (UTC)(link)
Это очень здравый довод. Но это не совсем то :)

Это было введено в питоне 2.0.
"An attempt has been made to alleviate one of Python's warts, the often-confusing NameError exception when code refers to a local variable before the variable has been assigned a value. For example, the following code raises an exception on the print statement in both 1.5.2 and 2.0; in 1.5.2 a NameError exception is raised, while 2.0 raises a new UnboundLocalError exception."

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

Вовсе *не компилятор* ругается на эту функцию (как он поругался бы на синтаксическую ошибку). Exception возникает при *исполнении* готового объекта (что, надо сказать, иногда бывает неприятным сюрпризом). Это LOAD_FAST, вставленный компилятором, ругается, afaict. Но так как он умный и понимает, что не найти он мог только локальную переменную, ругается он при помощи более конкретного UnboundLocalError, а не просто NameError.

Тот факт, что компилятор способен прийти к выводу о локальности переменной, говорит о том, что некоторый небольшой (imho, вполне тривиальный) анализ корректного кода он таки выполняет. Мой point в том, что компиляция эта тривиальна — примерно такую компиляцию исполнял и интерпретатор бейсика на 8-битных машинах моего детства, который тоже ключевые слова в байт-коды превращал и скобочные выражения раскладывал во что-то более быстро вычислимое.

[identity profile] faceted-jacinth.livejournal.com 2008-05-18 07:01 pm (UTC)(link)
Это было в питоне с самого начала, в 2.0 они поменяли тип эксепшена.

Несомненно, питоновский код компилируется в некий (высокоуровневый) байт-код
---
U miss da point, bro. Байткод совершенно не высокоуровенен, да его может и вовсе не быть. Дело в спеках, которые ясно говорят, что локальной считается любая переменная, в которую было (точнее, может быть будет) выполнено присваивание, неважно где. Это, ещё раз повторю, на всякий случай, означает, что питоновский интерпретатор, когда ему скармливают очередную функцию, совершает действия, которые я называю компиляцией. Обработку кода без его исполнения. Довольно сложную обработку.

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

Напоминаю, весь пойнт моего первого коммента заключался в том, что компайл-тайм обработка (в частности, статическая типизация) это бенефит, это подъём над бейзлайном, она доставляет.

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

(в данном конкретном случае у него есть возможность, но она есть не всегда. Однако лично я могу сказать, что аналогичная шарповая проверка не раз спасала меня и я даже привык писать код так, чтобы она сработала, если я вдруг что-то забуду. Если у меня есть out переменная и развесистое дерево вычислений, то я никогда не присваиваю ей дефолтное значение в начале, нет, если вычисления удались, присваиваю результат и выхожу, если нет, присваиваю дефолтное значение и выхожу, это правильно, так надо.)
nine_k: A stream of colors expanding from brain (Default)

[personal profile] nine_k 2008-05-18 07:59 pm (UTC)(link)
Это да: в 2.0 они стали говорить именно про *локальность*, а не просто ошибку разрешения имён.

Байткод питона куда более "высокоуровнев", чем, скажем, у JVM млм CLR.

"If a name is bound in a block, it is a local variable of that block" — да-да. Но энфорсится это не совсем в момент компиляции, как видим. А проверяется компилятором тривиально. И именно тривиальность компилятора и мешает ему отсечь ошибку. (Ещё можно вспомнить, что связанные с этим определением проблемы решают в 3.0 введением слова nonlocal.)

Несомненно, статический анализ полезен! Беда в том, что он не бесплатен. Статическая типизация решает многие проблемы — и создаёт некоторые проблемы. Динамическая — то же самое, одно лечим, другим расплачиваемся. Например, писанием бесконечных вариантов одной функции с разными типами параметров и практически идентичным телом. Или type cast-ами в некоторых тонких местах, где можно проглядеть ошибку типизпции и влететь в неё в runtime (чего, казалось бы, "не может быть").

В C#, кстати, вносят много полезных фишек, позволяющих, с одной стороны, легче использовать некоторые динамические подходы, с другой — не писать лишних слов.

[identity profile] cmm.livejournal.com 2008-05-19 12:26 pm (UTC)(link)
Несомненно, статический анализ полезен! Беда в том, что он не бесплатен. Статическая типизация решает многие проблемы — и создаёт некоторые проблемы. Динамическая — то же самое, одно лечим, другим расплачиваемся.

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

требование явного объявления переменных перед использованием, к примеру (не типов, а имён!), не превращает язык из "динамического" в "статический", но весьма улучшает как компиляцию, так и диагностику.
nine_k: A stream of colors expanding from brain (Default)

[personal profile] nine_k 2008-05-19 02:14 pm (UTC)(link)
Совершенно согласен. Статический анализ не сводится к статическому контролю типов.

[identity profile] faceted-jacinth.livejournal.com 2008-05-18 05:29 pm (UTC)(link)
Ой, там ещё вначале
>>> i = 1

[identity profile] faceted-jacinth.livejournal.com 2008-05-18 06:03 pm (UTC)(link)
Кстати, раз у нас тут начался вечер чудных питонооткрытий, там ещё есть забавные моменты. Например:
a.py
import b
print "a"

b.py
import a
print "b"
В качестве самостоятельного упражнения оставляю исполнение "python b.py" и "import b" из интерпретатора (да, оба способа работают, но печатают разное).
nine_k: A stream of colors expanding from brain (Default)

[personal profile] nine_k 2008-05-18 06:49 pm (UTC)(link)
Не вижу, что в этом удивительного. Всё вполне предсказуемо и не противоречит идее, что import есть оператор, исполнимый в runtime. Более того, код типа
try:
  import x.y.z as X
except ImportError:
  import a.b.c as X
тоже вполне себе широко используется для обеспечения version compatibility.

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

[identity profile] faceted-jacinth.livejournal.com 2008-05-18 07:08 pm (UTC)(link)
Вы опять наступаете на те же грабли. Если я показываю кусок кода и говорю "Зацените", это означает, что, скорее всего, нужно его скормить интерпретатору. Ну, если мы оба вроде понимаем язык, то есть когда я показываю код, я имею в виду, что его исполнение даст результат, отличный от ожидаемого. От того, который вы получили, исполнив код на своём внутримозговом интерпретаторе питона. Потому что я как раз утверждаю, что ваш внутренний интерпретатор неправильно работает.

Ну вот и тут. Реально получаемый результат весьма забавен и заставляет задуматься о том, что же всё-таки кэшируется и как это всё работает.
nine_k: A stream of colors expanding from brain (Default)

[personal profile] nine_k 2008-05-18 07:43 pm (UTC)(link)
Почтеннейший, не надо думать обо мне так плохо. Разумеется, я скормил интерпретатору предложенный вам кусок, и только затем взялся писать объяснения.

Не вижу, где получается результат, отличный от ожидаемого.
$ python
Python 2.4.5 (#2, Apr 17 2008, 13:00:52) 
[GCC 4.2.3 (Debian 4.2.3-3)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import b
a
b
>>> _

Мы видим, как сначала исполнился импорт модуля a, идущий первой строчкой в модуле b. Он (a) занялся импортом модуля b. Но так как мы прямо вот сейчас уже заняты импортом модуля b, повторно (рекурсивно) его импортировать не надо. Осталось напечатать "a". На этом импорт модуля a завершился, и продолжился импорт (т.е исполнение) модуля b; исполнилась его вторая строчка, напечатавшая "b".

Не вижу, что тут неинтуитивного.

Второй пример разобрать? :) Или что я упускаю?

[identity profile] faceted-jacinth.livejournal.com 2008-05-18 07:46 pm (UTC)(link)
да, именно второй. Очень интересно послушать, потому что я сам этого не понимаю.
nine_k: A stream of colors expanding from brain (Default)

[personal profile] nine_k 2008-05-18 08:34 pm (UTC)(link)
Всегда пожалуйста.

$ python b.py
b
a
b
$ _
Картина та же самая. Будем писать имя файла и строку с комментарием.
b.py:1 — import a, поехали исполнять a.py и записали себе что импортируем a.
a.py:1 — import b, поехали исполнять b.py, ведь мы ни разу не пробовали его импортировать.
b.py:1 — import a, но у нас записано, что мы уже исполняем импорт a, потому второй раз не надо, едем дальше
b.py:2 — print "b", напечатали на экране первое "b".
Тут кончился b.py, который мы исполняли по просьбе import b из a.py, едем по нему дальше.
a.py:2 — print "a", напечатали "a".
Тут кончился a.py, который мы звали по просьбе import a, продолжился b.py, который мы звали из командной строки.
b.py:2 — print "b", напечатали вторую "b".
Тут кончился самый внешний b.py, сказочке конец, интерпретатор вышел.

Как-то так я себе это представляю. Может быть, я где-то серьёзно ошибаюсь, я ещё пороюсь в этом (тем более, надо по работе), но по крайней мере мне моя версия кажется правдоподобной :)