2. Почему это может быть полезно?
Часто у программиста, использующего в работе Python, возникает
один из следующих вопросов:
какой из двух способов решения проблемы потребляет меньше
памяти?
Беглый обзор “внутренностей” Python 2
3. Почему это может быть полезно?
Часто у программиста, использующего в работе Python, возникает
один из следующих вопросов:
какой из двух способов решения проблемы потребляет меньше
памяти?
какой из двух способов работает быстрее?
Беглый обзор “внутренностей” Python 2
4. Почему это может быть полезно?
Часто у программиста, использующего в работе Python, возникает
один из следующих вопросов:
какой из двух способов решения проблемы потребляет меньше
памяти?
какой из двух способов работает быстрее?
как поведет себя определенная конструкция при изменении
runtime среды?
Беглый обзор “внутренностей” Python 2
5. Почему это может быть полезно?
Решаются они обычно одним из следующих способов:
можно спросить у коллег
Беглый обзор “внутренностей” Python 3
6. Почему это может быть полезно?
Решаются они обычно одним из следующих способов:
можно спросить у коллег
можно найти ответ на StackOverflow
Беглый обзор “внутренностей” Python 3
7. Почему это может быть полезно?
Решаются они обычно одним из следующих способов:
можно спросить у коллег
можно найти ответ на StackOverflow
можно замерить самому
Беглый обзор “внутренностей” Python 3
8. Почему это может быть полезно?
Однако понимание того, как работает интерпретатор на самых
нижних уровнях, позволяет обрести интуитивное понимание
нюансов работы многих конструкций языка.
Беглый обзор “внутренностей” Python 4
9. Почему это может быть полезно?
Однако понимание того, как работает интерпретатор на самых
нижних уровнях, позволяет обрести интуитивное понимание
нюансов работы многих конструкций языка.
Кроме того, в случае с Python абсолютное большинство
программистов обладает достаточной квалификацией, чтобы без
посредников найти ответ на свой вопрос в исходниках
интерпретатора.
Беглый обзор “внутренностей” Python 4
10. Почему это может быть полезно?
Знакомство с “внутренностями” полезно также потому, что Python
является open source проектом - кто знает, быть может именно вы
решите одну из наболевших проблем? ;)
Беглый обзор “внутренностей” Python 5
11. Почему это может быть полезно?
Знакомство с “внутренностями” полезно также потому, что Python
является open source проектом - кто знает, быть может именно вы
решите одну из наболевших проблем? ;)
Кратким изучением наиболее характерных особенностей
интерпретатора мы сейчас и займемся.
Беглый обзор “внутренностей” Python 5
13. CPython
Основная реализация Python на сегодняшний день
Написана на C (не С++), кроссплатформенна и довольно легко
переносима на отличные от официально поддерживаемых
платформы
Беглый обзор “внутренностей” Python 6
14. CPython
Основная реализация Python на сегодняшний день
Написана на C (не С++), кроссплатформенна и довольно легко
переносима на отличные от официально поддерживаемых
платформы
Код простой и понятный
Беглый обзор “внутренностей” Python 6
15. CPython
Основная реализация Python на сегодняшний день
Написана на C (не С++), кроссплатформенна и довольно легко
переносима на отличные от официально поддерживаемых
платформы
Код простой и понятный
Нет, серьезно, простой и понятный, даже для людей, не
интересующихся разработкой языков программирования ;)
Беглый обзор “внутренностей” Python 6
16. CPython
Есть еще PyPy, IronPython, Jython, Boo (хотя это не совсем
Python)
Беглый обзор “внутренностей” Python 7
17. CPython
Есть еще PyPy, IronPython, Jython, Boo (хотя это не совсем
Python)
Они по своему интересны, но с CPython “внутри” у них мало
общего
Беглый обзор “внутренностей” Python 7
18. CPython
Есть еще PyPy, IronPython, Jython, Boo (хотя это не совсем
Python)
Они по своему интересны, но с CPython “внутри” у них мало
общего
Поэтому хотя они и могут быть очень полезны на практике,
рассматривать их мы не будем
Беглый обзор “внутренностей” Python 7
20. Все - объект
В Python все является объектом
Беглый обзор “внутренностей” Python 9
21. Все - объект
В Python все является объектом
Ну то есть вообще все - от чисел до стакфреймов
Беглый обзор “внутренностей” Python 9
22. Все - объект
В Python все является объектом
Ну то есть вообще все - от чисел до стакфреймов
На C-уровне это выражено типом PyObject *
Беглый обзор “внутренностей” Python 9
23. Все - объект
В Python все является объектом
Ну то есть вообще все - от чисел до стакфреймов
На C-уровне это выражено типом PyObject *
Любой PyObject имеет стандартный заголовок:
#define PyObject_HEAD
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
Беглый обзор “внутренностей” Python 9
24. Все - объект
В Python все является объектом
Ну то есть вообще все - от чисел до стакфреймов
На C-уровне это выражено типом PyObject *
Любой PyObject имеет стандартный заголовок:
#define PyObject_HEAD
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
Поэтому в 64-битном Python число не может быть меньше 24
байт. Deal with it.
Беглый обзор “внутренностей” Python 9
25. PyObject *
#define PyObject_HEAD
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
ob_refcnt - reference counter
ob_type - type object, определяющий поведение объекта и
значение полей struct’а, идущих после заголовка (например,
PyStringObject, PyIntObject)
PyObject * - указатель. Поэтому все значения в Python передаются
по ссылке. No exceptions.
Беглый обзор “внутренностей” Python 10
28. None
None - особый объект
Он один на каждый инстанс интерпретатора. Совсем один.
Беглый обзор “внутренностей” Python 12
29. None
None - особый объект
Он один на каждый инстанс интерпретатора. Совсем один.
Поэтому для него is и == всегда эквивалентны.
Беглый обзор “внутренностей” Python 12
30. None
None - особый объект
Он один на каждый инстанс интерпретатора. Совсем один.
Поэтому для него is и == всегда эквивалентны.
Вот так вот делать не стоит:
if x == None:
это медленно, бессмысленно и вообще плохой тон.
Беглый обзор “внутренностей” Python 12
31. int
int - тип “малых” целых чисел
Беглый обзор “внутренностей” Python 13
32. int
int - тип “малых” целых чисел
В Python целые - неизменяемый тип
Беглый обзор “внутренностей” Python 13
33. int
int - тип “малых” целых чисел
В Python целые - неизменяемый тип
Проверим как на них работает is:
>>> int("100") is int("100")
True
>>> int("1000") is int("1000")
False
Беглый обзор “внутренностей” Python 13
34. int
int - тип “малых” целых чисел
В Python целые - неизменяемый тип
Проверим как на них работает is:
>>> int("100") is int("100")
True
>>> int("1000") is int("1000")
False
Как это объяснить? Оказывается, интерпретатор “кеширует”
объекты int со значениями от -5 до 256, для других значений
создаются самостоятельные объекты.
Беглый обзор “внутренностей” Python 13
35. int
int - тип “малых” целых чисел
В Python целые - неизменяемый тип
Проверим как на них работает is:
>>> int("100") is int("100")
True
>>> int("1000") is int("1000")
False
Как это объяснить? Оказывается, интерпретатор “кеширует”
объекты int со значениями от -5 до 256, для других значений
создаются самостоятельные объекты.
Поэтому список intов размером до байта будет иметь overhead в 8
байт на элемент (указатель), а больших intов - до 32 байт на
элемент (указатель + объект).
Беглый обзор “внутренностей” Python 13
36. int
Но загадки на этом не заканчиваются:
>>> (1000 is 1000, 1000+0 is 1000+0)
(True, False)
Беглый обзор “внутренностей” Python 14
37. int
Но загадки на этом не заканчиваются:
>>> (1000 is 1000, 1000+0 is 1000+0)
(True, False)
Почему так - немного дальше.
Беглый обзор “внутренностей” Python 14
38. string
Строки, как и целые, в Python неизменяемы
Беглый обзор “внутренностей” Python 15
39. string
Строки, как и целые, в Python неизменяемы
Однако sharing объектов их затрагивает куда сильнее
Беглый обзор “внутренностей” Python 15
40. string
Строки, как и целые, в Python неизменяемы
Однако sharing объектов их затрагивает куда сильнее
Интерпретатор поддерживает dict так называемых interned
строк, для каждой из которых гарантированно существует ровно
один объект
Беглый обзор “внутренностей” Python 15
41. string
Строки, как и целые, в Python неизменяемы
Однако sharing объектов их затрагивает куда сильнее
Интерпретатор поддерживает dict так называемых interned
строк, для каждой из которых гарантированно существует ровно
один объект
Все идентификаторы (имена переменных, модулей, методов),
автоматически туда помещаются
Беглый обзор “внутренностей” Python 15
42. string
Строки, как и целые, в Python неизменяемы
Однако sharing объектов их затрагивает куда сильнее
Интерпретатор поддерживает dict так называемых interned
строк, для каждой из которых гарантированно существует ровно
один объект
Все идентификаторы (имена переменных, модулей, методов),
автоматически туда помещаются
Оператор == для interned строк выраждается в is (сравнение
указателей)
Беглый обзор “внутренностей” Python 15
43. string
Строки, как и целые, в Python неизменяемы
Однако sharing объектов их затрагивает куда сильнее
Интерпретатор поддерживает dict так называемых interned
строк, для каждой из которых гарантированно существует ровно
один объект
Все идентификаторы (имена переменных, модулей, методов),
автоматически туда помещаются
Оператор == для interned строк выраждается в is (сравнение
указателей)
Помимо этого, по аналогии с int со значениями [−5, 256]
разделяются объекты пустой и всех возможных однобуквенных
строк
Беглый обзор “внутренностей” Python 15
48. Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Его сначала нужно “разжевать” до очень простых операций,
работающих с адресами и регистрами
Беглый обзор “внутренностей” Python 18
49. Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Его сначала нужно “разжевать” до очень простых операций,
работающих с адресами и регистрами
Код на Python также не годится для непосредственного
исполнения
Беглый обзор “внутренностей” Python 18
50. Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Его сначала нужно “разжевать” до очень простых операций,
работающих с адресами и регистрами
Код на Python также не годится для непосредственного
исполнения
Поэтому скрипт при загрузке транслируется в байткод
виртуальной машины
Беглый обзор “внутренностей” Python 18
51. Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Его сначала нужно “разжевать” до очень простых операций,
работающих с адресами и регистрами
Код на Python также не годится для непосредственного
исполнения
Поэтому скрипт при загрузке транслируется в байткод
виртуальной машины
Виртуальная машина (VM) - это подпрограмма интерпретатора,
испоняющая байткод.
Беглый обзор “внутренностей” Python 18
52. Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Его сначала нужно “разжевать” до очень простых операций,
работающих с адресами и регистрами
Код на Python также не годится для непосредственного
исполнения
Поэтому скрипт при загрузке транслируется в байткод
виртуальной машины
Виртуальная машина (VM) - это подпрограмма интерпретатора,
испоняющая байткод.
Но кроме отсутствия “железного” воплощения, концептуальных
различий с процессором нет.
Беглый обзор “внутренностей” Python 18
54. Байткод
Команды CPython VM кодируются одним байтом
Каждая из них может иметь опциональный 16-битный аргумент
Беглый обзор “внутренностей” Python 19
55. Байткод
Команды CPython VM кодируются одним байтом
Каждая из них может иметь опциональный 16-битный аргумент
Концептуально VM является стековой машиной (уместны
аналогии с обратной польской записью и языком Forth) -
значения кладутся на value stack (не путать с call stack), операции
их снимают с вершины и кладут результат обратно.
Беглый обзор “внутренностей” Python 19
56. Байткод
Команды CPython VM кодируются одним байтом
Каждая из них может иметь опциональный 16-битный аргумент
Концептуально VM является стековой машиной (уместны
аналогии с обратной польской записью и языком Forth) -
значения кладутся на value stack (не путать с call stack), операции
их снимают с вершины и кладут результат обратно.
При помощи модуля dis можно посмотреть на байткод “живых”
функций
Беглый обзор “внутренностей” Python 19
61. Code object
Как мы видим, байткод это не просто строка байт, так как
16-битных целых недостаточно, чтобы кодировать любые
Python-объекты
Беглый обзор “внутренностей” Python 22
62. Code object
Как мы видим, байткод это не просто строка байт, так как
16-битных целых недостаточно, чтобы кодировать любые
Python-объекты
Однако их достаточно, чтобы кодировать смещения. Например, в
список констант co_consts
Беглый обзор “внутренностей” Python 22
63. Code object
Как мы видим, байткод это не просто строка байт, так как
16-битных целых недостаточно, чтобы кодировать любые
Python-объекты
Однако их достаточно, чтобы кодировать смещения. Например, в
список констант co_consts
Наряду с другой метаинформацией (количество локальных
переменных, количество параметров, глубина стека) байткод
формирует code object
Беглый обзор “внутренностей” Python 22
64. Code object
Как мы видим, байткод это не просто строка байт, так как
16-битных целых недостаточно, чтобы кодировать любые
Python-объекты
Однако их достаточно, чтобы кодировать смещения. Например, в
список констант co_consts
Наряду с другой метаинформацией (количество локальных
переменных, количество параметров, глубина стека) байткод
формирует code object
Именно code object являет собой единицу существования
компилированного кода в Python
Беглый обзор “внутренностей” Python 22
65. Code object
Как мы видим, байткод это не просто строка байт, так как
16-битных целых недостаточно, чтобы кодировать любые
Python-объекты
Однако их достаточно, чтобы кодировать смещения. Например, в
список констант co_consts
Наряду с другой метаинформацией (количество локальных
переменных, количество параметров, глубина стека) байткод
формирует code object
Именно code object являет собой единицу существования
компилированного кода в Python
Они же сериализуются в .pyc файлы
Беглый обзор “внутренностей” Python 22
67. Code object
code object являются неизменяемыми
В “нормальном” коде они строятся единожды - при загрузке
модуля
Беглый обзор “внутренностей” Python 23
68. Code object
code object являются неизменяемыми
В “нормальном” коде они строятся единожды - при загрузке
модуля
Это такой же объект как и все:
def f():
def g(): pass
return g
2 0 LOAD_CONST 1 (<code object f ...>)
3 MAKE_FUNCTION 0
6 STORE_FAST 0 (f)
3 9 LOAD_GLOBAL 0 (g)
12 RETURN_VALUE
Беглый обзор “внутренностей” Python 23
70. Компиляция
Процесс преобразования исходного кода модуля в набор
code object называется компиляцией
Однако компилятор у Python очень рудиментарный - фактически
1-в-1 отображение конструкций в байткод
Беглый обзор “внутренностей” Python 24
71. Компиляция
Процесс преобразования исходного кода модуля в набор
code object называется компиляцией
Однако компилятор у Python очень рудиментарный - фактически
1-в-1 отображение конструкций в байткод
Оптимизаций уровня байткода почти нет
Беглый обзор “внутренностей” Python 24
72. Компиляция
Процесс преобразования исходного кода модуля в набор
code object называется компиляцией
Однако компилятор у Python очень рудиментарный - фактически
1-в-1 отображение конструкций в байткод
Оптимизаций уровня байткода почти нет
uncompyle, open source декомпилятор Python, способен
восстановить исходный код по .pyc файлу почти всегда, потеряв
при этом разве что комментарии
Беглый обзор “внутренностей” Python 24
73. Компиляция
Процесс преобразования исходного кода модуля в набор
code object называется компиляцией
Однако компилятор у Python очень рудиментарный - фактически
1-в-1 отображение конструкций в байткод
Оптимизаций уровня байткода почти нет
uncompyle, open source декомпилятор Python, способен
восстановить исходный код по .pyc файлу почти всегда, потеряв
при этом разве что комментарии
Так как компилятор неоптимизирующий, ему почти всегда можно
помочь (если надо)
Беглый обзор “внутренностей” Python 24
74. Компиляция
def f():
l = []
for i in xrange(10000):
l.append(i)
>>> timeit.timeit("""....""")
0.09371685981750488
>>> dis.dis(f) (... оставлено только тело цикла ...)
4 25 LOAD_FAST 0 (l)
28 LOAD_ATTR 1 (append)
31 LOAD_FAST 1 (i)
34 CALL_FUNCTION 1
Беглый обзор “внутренностей” Python 25
75. Loop hoisting
LOAD_ATTR по идее выполняется небыстро - это поиск строки с
именем метода в дикте (хеш-таблице). Строка interned, но все
равно это долго.
Беглый обзор “внутренностей” Python 26
76. Loop hoisting
LOAD_ATTR по идее выполняется небыстро - это поиск строки с
именем метода в дикте (хеш-таблице). Строка interned, но все
равно это долго.
Логично не выполнять эту операцию в теле цикла, а сделать ее
единожды до начала выполнения.
Беглый обзор “внутренностей” Python 26
77. Loop hoisting
LOAD_ATTR по идее выполняется небыстро - это поиск строки с
именем метода в дикте (хеш-таблице). Строка interned, но все
равно это долго.
Логично не выполнять эту операцию в теле цикла, а сделать ее
единожды до начала выполнения.
Эта оптимизация известна как loop hoisting, но рудиментарный
компилятор Python ее не делает.
Беглый обзор “внутренностей” Python 26
78. Loop hoisting
LOAD_ATTR по идее выполняется небыстро - это поиск строки с
именем метода в дикте (хеш-таблице). Строка interned, но все
равно это долго.
Логично не выполнять эту операцию в теле цикла, а сделать ее
единожды до начала выполнения.
Эта оптимизация известна как loop hoisting, но рудиментарный
компилятор Python ее не делает.
Поможем ему!
Беглый обзор “внутренностей” Python 26
79. Loop hoisting
def f():
l = []
la = l.append
for i in xrange(10000):
la(i)
>>> timeit.timeit("""....""")
0.08451047520987543
>>> dis.dis(f) (... оставлено только тело цикла ...)
5 34 LOAD_FAST 1 (la)
37 LOAD_FAST 2 (i)
40 CALL_FUNCTION 1
До оптимизации было 0.09371685981750488. Разница - 10%.
Беглый обзор “внутренностей” Python 27
80. LIST_APPEND
Добавление к списку - частая операция, и потому в CPython VM
есть особый опкод LIST_APPEND
Беглый обзор “внутренностей” Python 28
81. LIST_APPEND
Добавление к списку - частая операция, и потому в CPython VM
есть особый опкод LIST_APPEND
Как показывает изучение исходников компилятора, используется
этот опкод только для компиляции list comprehensions:
def f():
return [x for x in xrange(10000)]
>>> timeit.timeit("""....""")
0.08152854398842842
>>> dis.dis(f) (... оставлено только тело цикла ...)
16 STORE_FAST 0 (x)
19 LOAD_FAST 0 (x)
22 LIST_APPEND 2
Беглый обзор “внутренностей” Python 28
83. Дикты и строки
Многие знакомые с семантикой Python люди шутят, что Гвидо ван
Россум написал dict (открытую хеш-таблицу) и string
(неизменяемые строки), после чего решил больше ничего не
писать
Беглый обзор “внутренностей” Python 30
84. Дикты и строки
Многие знакомые с семантикой Python люди шутят, что Гвидо ван
Россум написал dict (открытую хеш-таблицу) и string
(неизменяемые строки), после чего решил больше ничего не
писать
Действительно, объекты, модули и неймспейсы - все это обычные
дикты cо строковыми ключами
Беглый обзор “внутренностей” Python 30
85. Дикты и строки
Многие знакомые с семантикой Python люди шутят, что Гвидо ван
Россум написал dict (открытую хеш-таблицу) и string
(неизменяемые строки), после чего решил больше ничего не
писать
Действительно, объекты, модули и неймспейсы - все это обычные
дикты cо строковыми ключами
Глобальное пространство имен модуля, следовательно, тоже дикт,
а обращение к переменной - это lookup в дикте.
Беглый обзор “внутренностей” Python 30
86. Дикты и строки
Многие знакомые с семантикой Python люди шутят, что Гвидо ван
Россум написал dict (открытую хеш-таблицу) и string
(неизменяемые строки), после чего решил больше ничего не
писать
Действительно, объекты, модули и неймспейсы - все это обычные
дикты cо строковыми ключами
Глобальное пространство имен модуля, следовательно, тоже дикт,
а обращение к переменной - это lookup в дикте.
Всегда ли это так?
Беглый обзор “внутренностей” Python 30
87. Дикты и строки
def f():
a = 3
return a + b
2 0 LOAD_CONST 1 (1)
3 STORE_FAST 0 (a)
3 6 LOAD_FAST 0 (a)
9 LOAD_GLOBAL 0 (b)
12 BINARY_ADD
13 RETURN_VALUE
Беглый обзор “внутренностей” Python 31
88. LOAD_FAST и LOAD_GLOBAL
3 6 LOAD_FAST 0 (a)
9 LOAD_GLOBAL 0 (b)
Для имен a и b компилятор использовал разные опкоды. Почему?
Беглый обзор “внутренностей” Python 32
89. LOAD_FAST и LOAD_GLOBAL
3 6 LOAD_FAST 0 (a)
9 LOAD_GLOBAL 0 (b)
Для имен a и b компилятор использовал разные опкоды. Почему?
Оказывается, на этапе компиляции в code object собираются все
имена локальных переменных (то есть присваиваемые в этом
блоке кода и не помеченные явно при помощи global), и
заносятся в список co_locals.
Беглый обзор “внутренностей” Python 32
90. LOAD_FAST и LOAD_GLOBAL
3 6 LOAD_FAST 0 (a)
9 LOAD_GLOBAL 0 (b)
Для имен a и b компилятор использовал разные опкоды. Почему?
Оказывается, на этапе компиляции в code object собираются все
имена локальных переменных (то есть присваиваемые в этом
блоке кода и не помеченные явно при помощи global), и
заносятся в список co_locals.
Впоследствии для каждого фрейма стека создается массив
локальных переменных, и обращение к ним происходит по их
индексу в co_locals. Этот индекс неизменен и “зашит” в байткод.
Беглый обзор “внутренностей” Python 32
91. LOAD_FAST и LOAD_GLOBAL
LOAD_FAST, как нетрудно догадаться, просто берет значение по
индексу из массива
Беглый обзор “внутренностей” Python 33
92. LOAD_FAST и LOAD_GLOBAL
LOAD_FAST, как нетрудно догадаться, просто берет значение по
индексу из массива
LOAD_GLOBAL же вынужден брать строку с именем из co_consts,
после чего искать по этому ключу в дикте неймспейса
Беглый обзор “внутренностей” Python 33
93. LOAD_FAST и LOAD_GLOBAL
LOAD_FAST, как нетрудно догадаться, просто берет значение по
индексу из массива
LOAD_GLOBAL же вынужден брать строку с именем из co_consts,
после чего искать по этому ключу в дикте неймспейса
Обе операции выполняются с ожидаемой стоимостью O(1), но у
массива константа явно лучше
Беглый обзор “внутренностей” Python 33
94. LOAD_FAST и LOAD_GLOBAL
LOAD_FAST, как нетрудно догадаться, просто берет значение по
индексу из массива
LOAD_GLOBAL же вынужден брать строку с именем из co_consts,
после чего искать по этому ключу в дикте неймспейса
Обе операции выполняются с ожидаемой стоимостью O(1), но у
массива константа явно лучше
Поэтому закешировать что-то в локальную переменную не самая
плохая идея в плане производительности
Беглый обзор “внутренностей” Python 33
95. Lexical scoping
Глобальный и локальный неймспейсы достаточно очевидны сами
по себе
Беглый обзор “внутренностей” Python 34
96. Lexical scoping
Глобальный и локальный неймспейсы достаточно очевидны сами
по себе
Однако Python позволяет делать вот так:
def f():
a = 1
def g(): return a
return g
>>> t = f()
>>> t()
1
Беглый обзор “внутренностей” Python 34
97. Lexical scoping
Глобальный и локальный неймспейсы достаточно очевидны сами
по себе
Однако Python позволяет делать вот так:
def f():
a = 1
def g(): return a
return g
>>> t = f()
>>> t()
1
Как возвращенной функции удается вернуть значение из
разрушенного фрейма?
Беглый обзор “внутренностей” Python 34
98. Lexical scoping
Оказывается, такая ситуация детектируется при компиляции и
решается при помощи создания cell object (в функциональных
языках подобные объекты называют замыканиями или closures):
2 0 LOAD_CONST 1 (1)
3 STORE_DEREF 0 (a)
3 6 LOAD_CLOSURE 0 (a)
9 BUILD_TUPLE 1
12 LOAD_CONST 2 (<code object ...>)
15 MAKE_CLOSURE 0
18 STORE_FAST 0 (g)
4 21 LOAD_FAST 0 (g)
24 RETURN_VALUE
Беглый обзор “внутренностей” Python 35
99. Lexical scoping
Оказывается, такая ситуация детектируется при компиляции и
решается при помощи создания cell object (в функциональных
языках подобные объекты называют замыканиями или closures):
2 0 LOAD_CONST 1 (1)
3 STORE_DEREF 0 (a)
3 6 LOAD_CLOSURE 0 (a)
9 BUILD_TUPLE 1
12 LOAD_CONST 2 (<code object ...>)
15 MAKE_CLOSURE 0
18 STORE_FAST 0 (g)
4 21 LOAD_FAST 0 (g)
24 RETURN_VALUE
cell object - это код функции вместе со значениями “внешних”
(свободных, free) переменных
Беглый обзор “внутренностей” Python 35
100. Lexical scoping
cell object по времени жизни не привязан к фрейму, в котором
он создан, и собирается сборщиком мусора только тогда, когда
станет недостижим.
Беглый обзор “внутренностей” Python 36
101. Lexical scoping
cell object по времени жизни не привязан к фрейму, в котором
он создан, и собирается сборщиком мусора только тогда, когда
станет недостижим.
Это “взрослая” реализация лексической области видимости
Беглый обзор “внутренностей” Python 36
102. Lexical scoping
cell object по времени жизни не привязан к фрейму, в котором
он создан, и собирается сборщиком мусора только тогда, когда
станет недостижим.
Это “взрослая” реализация лексической области видимости
Однако, к сожалению, она неполна:
def f():
a = 1
if True: a = 2; print a
print a
этот код выведет 2 2. Аналогичный код на C++, или, скажем,
Lua, выдаст 2 1
Беглый обзор “внутренностей” Python 36
103. Lexical scoping
cell object по времени жизни не привязан к фрейму, в котором
он создан, и собирается сборщиком мусора только тогда, когда
станет недостижим.
Это “взрослая” реализация лексической области видимости
Однако, к сожалению, она неполна:
def f():
a = 1
if True: a = 2; print a
print a
этот код выведет 2 2. Аналогичный код на C++, или, скажем,
Lua, выдаст 2 1
Это источник трудноуловимых багов
Беглый обзор “внутренностей” Python 36
104. Выстрелить в ногу!
Что выведет это код?
l = [lambda: x for x in "abcdefg"]
for r in l: print r(),
Беглый обзор “внутренностей” Python 37
105. Выстрелить в ногу!
Что выведет это код?
l = [lambda: x for x in "abcdefg"]
for r in l: print r(),
Наверное a b c d e f g?
Беглый обзор “внутренностей” Python 37
106. Выстрелить в ногу!
Что выведет это код?
l = [lambda: x for x in "abcdefg"]
for r in l: print r(),
Наверное a b c d e f g?
Реальность жестока.
Беглый обзор “внутренностей” Python 37
107. Выстрелить в ногу!
Что выведет это код?
l = [lambda: x for x in "abcdefg"]
for r in l: print r(),
Наверное a b c d e f g?
Реальность жестока.
Правильный ответ - g g g g g g g.
Беглый обзор “внутренностей” Python 37
108. Lexical scoping
К сожалению, различие поведения областей видимости на макро-
и микроуровнях настолько глубоко зашито в архитектуре языка,
что поправить его практически нереально
Беглый обзор “внутренностей” Python 38
109. Lexical scoping
К сожалению, различие поведения областей видимости на макро-
и микроуровнях настолько глубоко зашито в архитектуре языка,
что поправить его практически нереально
Даже если это сделать - это с большой вероятностью приведет к
“ломанию” имеющегося кода
Беглый обзор “внутренностей” Python 38
110. Lexical scoping
К сожалению, различие поведения областей видимости на макро-
и микроуровнях настолько глубоко зашито в архитектуре языка,
что поправить его практически нереально
Даже если это сделать - это с большой вероятностью приведет к
“ломанию” имеющегося кода
Поэтому про данную особенность надо просто помнить, и не
сильно увлекаться функциональным программированием на
Python ;)
Беглый обзор “внутренностей” Python 38
112. Заключение
К сожалению, регламент данного доклада не дает слишком
разогнаться. За бортом остаются многие интересные темы:
Беглый обзор “внутренностей” Python 40
113. Заключение
К сожалению, регламент данного доклада не дает слишком
разогнаться. За бортом остаются многие интересные темы:
Global Interpreter Lock (GIL), или почему программы на Python на
нескольких ядрах работают медленнее, чем на одном
Беглый обзор “внутренностей” Python 40
114. Заключение
К сожалению, регламент данного доклада не дает слишком
разогнаться. За бортом остаются многие интересные темы:
Global Interpreter Lock (GIL), или почему программы на Python на
нескольких ядрах работают медленнее, чем на одном
frame object и их список, или как greenlet умудряется
эмулировать кооперативную многозадачность “срезанием” стека
Беглый обзор “внутренностей” Python 40
115. Заключение
К сожалению, регламент данного доклада не дает слишком
разогнаться. За бортом остаются многие интересные темы:
Global Interpreter Lock (GIL), или почему программы на Python на
нескольких ядрах работают медленнее, чем на одном
frame object и их список, или как greenlet умудряется
эмулировать кооперативную многозадачность “срезанием” стека
ООП в Python, или почему у object нельзя выставить атрибут, а
у унаследованного от него пустого класса - можно
Беглый обзор “внутренностей” Python 40
116. Заключение
К сожалению, регламент данного доклада не дает слишком
разогнаться. За бортом остаются многие интересные темы:
Global Interpreter Lock (GIL), или почему программы на Python на
нескольких ядрах работают медленнее, чем на одном
frame object и их список, или как greenlet умудряется
эмулировать кооперативную многозадачность “срезанием” стека
ООП в Python, или почему у object нельзя выставить атрибут, а
у унаследованного от него пустого класса - можно
Генераторы, или как “шаманство” с фреймами позволяет
создавать видимость их (генераторов) наличия, но с серьезными
ограничениями
Беглый обзор “внутренностей” Python 40
118. Заключение
Однако большой проблемы нет - это не сакральное знание
Все, что я вам сегодня рассказал, я не прочитал в интернете и на
StackOverflow - я прочитал это в исходных кодах интерпретатора
Беглый обзор “внутренностей” Python 41
119. Заключение
Однако большой проблемы нет - это не сакральное знание
Все, что я вам сегодня рассказал, я не прочитал в интернете и на
StackOverflow - я прочитал это в исходных кодах интерпретатора
Они действительно неплохо структурированы и их довольно
просто читать, даже людям без соответствующего бэкграунда
Беглый обзор “внутренностей” Python 41
120. Заключение
Однако большой проблемы нет - это не сакральное знание
Все, что я вам сегодня рассказал, я не прочитал в интернете и на
StackOverflow - я прочитал это в исходных кодах интерпретатора
Они действительно неплохо структурированы и их довольно
просто читать, даже людям без соответствующего бэкграунда
Мне это помогло глубже понять как работает тот код, который я
пишу, что порой оказывалось очень полезным при профайлинге
критичных мест
Беглый обзор “внутренностей” Python 41
121. Заключение
Однако большой проблемы нет - это не сакральное знание
Все, что я вам сегодня рассказал, я не прочитал в интернете и на
StackOverflow - я прочитал это в исходных кодах интерпретатора
Они действительно неплохо структурированы и их довольно
просто читать, даже людям без соответствующего бэкграунда
Мне это помогло глубже понять как работает тот код, который я
пишу, что порой оказывалось очень полезным при профайлинге
критичных мест
Это как раз тот случай, когда вместо того, чтобы лезть на
StackOverflow, полезнее и интереснее разобраться самому. Так
что дерзайте!
Беглый обзор “внутренностей” Python 41