Беглый обзор “внутренностей” Python
Никита Лесников
Почему это может быть полезно?
Часто у программиста, использующего в работе Python, возникает
один из следующих вопросов:
какой из двух способов решения проблемы потребляет меньше
памяти?
Беглый обзор “внутренностей” Python 2
Почему это может быть полезно?
Часто у программиста, использующего в работе Python, возникает
один из следующих вопросов:
какой из двух способов решения проблемы потребляет меньше
памяти?
какой из двух способов работает быстрее?
Беглый обзор “внутренностей” Python 2
Почему это может быть полезно?
Часто у программиста, использующего в работе Python, возникает
один из следующих вопросов:
какой из двух способов решения проблемы потребляет меньше
памяти?
какой из двух способов работает быстрее?
как поведет себя определенная конструкция при изменении
runtime среды?
Беглый обзор “внутренностей” Python 2
Почему это может быть полезно?
Решаются они обычно одним из следующих способов:
можно спросить у коллег
Беглый обзор “внутренностей” Python 3
Почему это может быть полезно?
Решаются они обычно одним из следующих способов:
можно спросить у коллег
можно найти ответ на StackOverflow
Беглый обзор “внутренностей” Python 3
Почему это может быть полезно?
Решаются они обычно одним из следующих способов:
можно спросить у коллег
можно найти ответ на StackOverflow
можно замерить самому
Беглый обзор “внутренностей” Python 3
Почему это может быть полезно?
Однако понимание того, как работает интерпретатор на самых
нижних уровнях, позволяет обрести интуитивное понимание
нюансов работы многих конструкций языка.
Беглый обзор “внутренностей” Python 4
Почему это может быть полезно?
Однако понимание того, как работает интерпретатор на самых
нижних уровнях, позволяет обрести интуитивное понимание
нюансов работы многих конструкций языка.
Кроме того, в случае с Python абсолютное большинство
программистов обладает достаточной квалификацией, чтобы без
посредников найти ответ на свой вопрос в исходниках
интерпретатора.
Беглый обзор “внутренностей” Python 4
Почему это может быть полезно?
Знакомство с “внутренностями” полезно также потому, что Python
является open source проектом - кто знает, быть может именно вы
решите одну из наболевших проблем? ;)
Беглый обзор “внутренностей” Python 5
Почему это может быть полезно?
Знакомство с “внутренностями” полезно также потому, что Python
является open source проектом - кто знает, быть может именно вы
решите одну из наболевших проблем? ;)
Кратким изучением наиболее характерных особенностей
интерпретатора мы сейчас и займемся.
Беглый обзор “внутренностей” Python 5
CPython
Основная реализация Python на сегодняшний день
Беглый обзор “внутренностей” Python 6
CPython
Основная реализация Python на сегодняшний день
Написана на C (не С++), кроссплатформенна и довольно легко
переносима на отличные от официально поддерживаемых
платформы
Беглый обзор “внутренностей” Python 6
CPython
Основная реализация Python на сегодняшний день
Написана на C (не С++), кроссплатформенна и довольно легко
переносима на отличные от официально поддерживаемых
платформы
Код простой и понятный
Беглый обзор “внутренностей” Python 6
CPython
Основная реализация Python на сегодняшний день
Написана на C (не С++), кроссплатформенна и довольно легко
переносима на отличные от официально поддерживаемых
платформы
Код простой и понятный
Нет, серьезно, простой и понятный, даже для людей, не
интересующихся разработкой языков программирования ;)
Беглый обзор “внутренностей” Python 6
CPython
Есть еще PyPy, IronPython, Jython, Boo (хотя это не совсем
Python)
Беглый обзор “внутренностей” Python 7
CPython
Есть еще PyPy, IronPython, Jython, Boo (хотя это не совсем
Python)
Они по своему интересны, но с CPython “внутри” у них мало
общего
Беглый обзор “внутренностей” Python 7
CPython
Есть еще PyPy, IronPython, Jython, Boo (хотя это не совсем
Python)
Они по своему интересны, но с CPython “внутри” у них мало
общего
Поэтому хотя они и могут быть очень полезны на практике,
рассматривать их мы не будем
Беглый обзор “внутренностей” Python 7
Все - объект.
Беглый обзор “внутренностей” Python 8
Все - объект
В Python все является объектом
Беглый обзор “внутренностей” Python 9
Все - объект
В Python все является объектом
Ну то есть вообще все - от чисел до стакфреймов
Беглый обзор “внутренностей” Python 9
Все - объект
В Python все является объектом
Ну то есть вообще все - от чисел до стакфреймов
На C-уровне это выражено типом PyObject *
Беглый обзор “внутренностей” Python 9
Все - объект
В Python все является объектом
Ну то есть вообще все - от чисел до стакфреймов
На C-уровне это выражено типом PyObject *
Любой PyObject имеет стандартный заголовок:
#define PyObject_HEAD 
Py_ssize_t ob_refcnt; 
struct _typeobject *ob_type;
Беглый обзор “внутренностей” Python 9
Все - объект
В Python все является объектом
Ну то есть вообще все - от чисел до стакфреймов
На C-уровне это выражено типом PyObject *
Любой PyObject имеет стандартный заголовок:
#define PyObject_HEAD 
Py_ssize_t ob_refcnt; 
struct _typeobject *ob_type;
Поэтому в 64-битном Python число не может быть меньше 24
байт. Deal with it.
Беглый обзор “внутренностей” Python 9
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
Ссылки
PyIntTypeObject
PyIntObject
ob_refcnt = 3
ob_type
ob_ival = 42
a b c
>>> a = b = c = 42
Все три имени ссылаются на один
объект
Этот факт можно установить при
помощи is
В общем случае == и is не
эквивалентны
Беглый обзор “внутренностей” Python 11
None
None - особый объект
Беглый обзор “внутренностей” Python 12
None
None - особый объект
Он один на каждый инстанс интерпретатора. Совсем один.
Беглый обзор “внутренностей” Python 12
None
None - особый объект
Он один на каждый инстанс интерпретатора. Совсем один.
Поэтому для него is и == всегда эквивалентны.
Беглый обзор “внутренностей” Python 12
None
None - особый объект
Он один на каждый инстанс интерпретатора. Совсем один.
Поэтому для него is и == всегда эквивалентны.
Вот так вот делать не стоит:
if x == None:
это медленно, бессмысленно и вообще плохой тон.
Беглый обзор “внутренностей” Python 12
int
int - тип “малых” целых чисел
Беглый обзор “внутренностей” Python 13
int
int - тип “малых” целых чисел
В Python целые - неизменяемый тип
Беглый обзор “внутренностей” Python 13
int
int - тип “малых” целых чисел
В Python целые - неизменяемый тип
Проверим как на них работает is:
>>> int("100") is int("100")
True
>>> int("1000") is int("1000")
False
Беглый обзор “внутренностей” Python 13
int
int - тип “малых” целых чисел
В Python целые - неизменяемый тип
Проверим как на них работает is:
>>> int("100") is int("100")
True
>>> int("1000") is int("1000")
False
Как это объяснить? Оказывается, интерпретатор “кеширует”
объекты int со значениями от -5 до 256, для других значений
создаются самостоятельные объекты.
Беглый обзор “внутренностей” Python 13
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
int
Но загадки на этом не заканчиваются:
>>> (1000 is 1000, 1000+0 is 1000+0)
(True, False)
Беглый обзор “внутренностей” Python 14
int
Но загадки на этом не заканчиваются:
>>> (1000 is 1000, 1000+0 is 1000+0)
(True, False)
Почему так - немного дальше.
Беглый обзор “внутренностей” Python 14
string
Строки, как и целые, в Python неизменяемы
Беглый обзор “внутренностей” Python 15
string
Строки, как и целые, в Python неизменяемы
Однако sharing объектов их затрагивает куда сильнее
Беглый обзор “внутренностей” Python 15
string
Строки, как и целые, в Python неизменяемы
Однако sharing объектов их затрагивает куда сильнее
Интерпретатор поддерживает dict так называемых interned
строк, для каждой из которых гарантированно существует ровно
один объект
Беглый обзор “внутренностей” Python 15
string
Строки, как и целые, в Python неизменяемы
Однако sharing объектов их затрагивает куда сильнее
Интерпретатор поддерживает dict так называемых interned
строк, для каждой из которых гарантированно существует ровно
один объект
Все идентификаторы (имена переменных, модулей, методов),
автоматически туда помещаются
Беглый обзор “внутренностей” Python 15
string
Строки, как и целые, в Python неизменяемы
Однако sharing объектов их затрагивает куда сильнее
Интерпретатор поддерживает dict так называемых interned
строк, для каждой из которых гарантированно существует ровно
один объект
Все идентификаторы (имена переменных, модулей, методов),
автоматически туда помещаются
Оператор == для interned строк выраждается в is (сравнение
указателей)
Беглый обзор “внутренностей” Python 15
string
Строки, как и целые, в Python неизменяемы
Однако sharing объектов их затрагивает куда сильнее
Интерпретатор поддерживает dict так называемых interned
строк, для каждой из которых гарантированно существует ровно
один объект
Все идентификаторы (имена переменных, модулей, методов),
автоматически туда помещаются
Оператор == для interned строк выраждается в is (сравнение
указателей)
Помимо этого, по аналогии с int со значениями [−5, 256]
разделяются объекты пустой и всех возможных однобуквенных
строк
Беглый обзор “внутренностей” Python 15
Краткий вывод
Не используйте is для чего-то кроме None
Беглый обзор “внутренностей” Python 16
Краткий вывод
Не используйте is для чего-то кроме None
Unless you know what you’re doing
Беглый обзор “внутренностей” Python 16
Байткод
Беглый обзор “внутренностей” Python 17
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Беглый обзор “внутренностей” Python 18
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Его сначала нужно “разжевать” до очень простых операций,
работающих с адресами и регистрами
Беглый обзор “внутренностей” Python 18
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Его сначала нужно “разжевать” до очень простых операций,
работающих с адресами и регистрами
Код на Python также не годится для непосредственного
исполнения
Беглый обзор “внутренностей” Python 18
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Его сначала нужно “разжевать” до очень простых операций,
работающих с адресами и регистрами
Код на Python также не годится для непосредственного
исполнения
Поэтому скрипт при загрузке транслируется в байткод
виртуальной машины
Беглый обзор “внутренностей” Python 18
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Его сначала нужно “разжевать” до очень простых операций,
работающих с адресами и регистрами
Код на Python также не годится для непосредственного
исполнения
Поэтому скрипт при загрузке транслируется в байткод
виртуальной машины
Виртуальная машина (VM) - это подпрограмма интерпретатора,
испоняющая байткод.
Беглый обзор “внутренностей” Python 18
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Его сначала нужно “разжевать” до очень простых операций,
работающих с адресами и регистрами
Код на Python также не годится для непосредственного
исполнения
Поэтому скрипт при загрузке транслируется в байткод
виртуальной машины
Виртуальная машина (VM) - это подпрограмма интерпретатора,
испоняющая байткод.
Но кроме отсутствия “железного” воплощения, концептуальных
различий с процессором нет.
Беглый обзор “внутренностей” Python 18
Байткод
Команды CPython VM кодируются одним байтом
Беглый обзор “внутренностей” Python 19
Байткод
Команды CPython VM кодируются одним байтом
Каждая из них может иметь опциональный 16-битный аргумент
Беглый обзор “внутренностей” Python 19
Байткод
Команды CPython VM кодируются одним байтом
Каждая из них может иметь опциональный 16-битный аргумент
Концептуально VM является стековой машиной (уместны
аналогии с обратной польской записью и языком Forth) -
значения кладутся на value stack (не путать с call stack), операции
их снимают с вершины и кладут результат обратно.
Беглый обзор “внутренностей” Python 19
Байткод
Команды CPython VM кодируются одним байтом
Каждая из них может иметь опциональный 16-битный аргумент
Концептуально VM является стековой машиной (уместны
аналогии с обратной польской записью и языком Forth) -
значения кладутся на value stack (не путать с call stack), операции
их снимают с вершины и кладут результат обратно.
При помощи модуля dis можно посмотреть на байткод “живых”
функций
Беглый обзор “внутренностей” Python 19
def f():
a = 1
b = 2
c = 3
return a + b * c
2 0 LOAD_CONST 1 (1)
3 STORE_FAST 0 (a)
3 6 LOAD_CONST 2 (2)
9 STORE_FAST 1 (b)
4 12 LOAD_CONST 3 (3)
15 STORE_FAST 2 (c)
5 18 LOAD_FAST 0 (a)
21 LOAD_FAST 1 (b)
24 LOAD_FAST 2 (c)
27 BINARY_MULTIPLY
28 BINARY_ADD
29 RETURN_VALUE
Беглый обзор “внутренностей” Python 20
Константы
def f(): return (1,"abc", 3.0)
Беглый обзор “внутренностей” Python 21
Константы
def f(): return (1,"abc", 3.0)
>>> dis.dis(f)
2 0 LOAD_CONST 4 ((1, ’abc’, 3.0))
3 RETURN_VALUE
Беглый обзор “внутренностей” Python 21
Константы
def f(): return (1,"abc", 3.0)
>>> dis.dis(f)
2 0 LOAD_CONST 4 ((1, ’abc’, 3.0))
3 RETURN_VALUE
>>> f.__code__.co_consts
(None, 1, ’abc’, 3.0, (1, ’abc’, 3.0))
Беглый обзор “внутренностей” Python 21
Code object
Как мы видим, байткод это не просто строка байт, так как
16-битных целых недостаточно, чтобы кодировать любые
Python-объекты
Беглый обзор “внутренностей” Python 22
Code object
Как мы видим, байткод это не просто строка байт, так как
16-битных целых недостаточно, чтобы кодировать любые
Python-объекты
Однако их достаточно, чтобы кодировать смещения. Например, в
список констант co_consts
Беглый обзор “внутренностей” Python 22
Code object
Как мы видим, байткод это не просто строка байт, так как
16-битных целых недостаточно, чтобы кодировать любые
Python-объекты
Однако их достаточно, чтобы кодировать смещения. Например, в
список констант co_consts
Наряду с другой метаинформацией (количество локальных
переменных, количество параметров, глубина стека) байткод
формирует code object
Беглый обзор “внутренностей” Python 22
Code object
Как мы видим, байткод это не просто строка байт, так как
16-битных целых недостаточно, чтобы кодировать любые
Python-объекты
Однако их достаточно, чтобы кодировать смещения. Например, в
список констант co_consts
Наряду с другой метаинформацией (количество локальных
переменных, количество параметров, глубина стека) байткод
формирует code object
Именно code object являет собой единицу существования
компилированного кода в Python
Беглый обзор “внутренностей” Python 22
Code object
Как мы видим, байткод это не просто строка байт, так как
16-битных целых недостаточно, чтобы кодировать любые
Python-объекты
Однако их достаточно, чтобы кодировать смещения. Например, в
список констант co_consts
Наряду с другой метаинформацией (количество локальных
переменных, количество параметров, глубина стека) байткод
формирует code object
Именно code object являет собой единицу существования
компилированного кода в Python
Они же сериализуются в .pyc файлы
Беглый обзор “внутренностей” Python 22
Code object
code object являются неизменяемыми
Беглый обзор “внутренностей” Python 23
Code object
code object являются неизменяемыми
В “нормальном” коде они строятся единожды - при загрузке
модуля
Беглый обзор “внутренностей” Python 23
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
Компиляция
Процесс преобразования исходного кода модуля в набор
code object называется компиляцией
Беглый обзор “внутренностей” Python 24
Компиляция
Процесс преобразования исходного кода модуля в набор
code object называется компиляцией
Однако компилятор у Python очень рудиментарный - фактически
1-в-1 отображение конструкций в байткод
Беглый обзор “внутренностей” Python 24
Компиляция
Процесс преобразования исходного кода модуля в набор
code object называется компиляцией
Однако компилятор у Python очень рудиментарный - фактически
1-в-1 отображение конструкций в байткод
Оптимизаций уровня байткода почти нет
Беглый обзор “внутренностей” Python 24
Компиляция
Процесс преобразования исходного кода модуля в набор
code object называется компиляцией
Однако компилятор у Python очень рудиментарный - фактически
1-в-1 отображение конструкций в байткод
Оптимизаций уровня байткода почти нет
uncompyle, open source декомпилятор Python, способен
восстановить исходный код по .pyc файлу почти всегда, потеряв
при этом разве что комментарии
Беглый обзор “внутренностей” Python 24
Компиляция
Процесс преобразования исходного кода модуля в набор
code object называется компиляцией
Однако компилятор у Python очень рудиментарный - фактически
1-в-1 отображение конструкций в байткод
Оптимизаций уровня байткода почти нет
uncompyle, open source декомпилятор Python, способен
восстановить исходный код по .pyc файлу почти всегда, потеряв
при этом разве что комментарии
Так как компилятор неоптимизирующий, ему почти всегда можно
помочь (если надо)
Беглый обзор “внутренностей” Python 24
Компиляция
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
Loop hoisting
LOAD_ATTR по идее выполняется небыстро - это поиск строки с
именем метода в дикте (хеш-таблице). Строка interned, но все
равно это долго.
Беглый обзор “внутренностей” Python 26
Loop hoisting
LOAD_ATTR по идее выполняется небыстро - это поиск строки с
именем метода в дикте (хеш-таблице). Строка interned, но все
равно это долго.
Логично не выполнять эту операцию в теле цикла, а сделать ее
единожды до начала выполнения.
Беглый обзор “внутренностей” Python 26
Loop hoisting
LOAD_ATTR по идее выполняется небыстро - это поиск строки с
именем метода в дикте (хеш-таблице). Строка interned, но все
равно это долго.
Логично не выполнять эту операцию в теле цикла, а сделать ее
единожды до начала выполнения.
Эта оптимизация известна как loop hoisting, но рудиментарный
компилятор Python ее не делает.
Беглый обзор “внутренностей” Python 26
Loop hoisting
LOAD_ATTR по идее выполняется небыстро - это поиск строки с
именем метода в дикте (хеш-таблице). Строка interned, но все
равно это долго.
Логично не выполнять эту операцию в теле цикла, а сделать ее
единожды до начала выполнения.
Эта оптимизация известна как loop hoisting, но рудиментарный
компилятор Python ее не делает.
Поможем ему!
Беглый обзор “внутренностей” Python 26
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
LIST_APPEND
Добавление к списку - частая операция, и потому в CPython VM
есть особый опкод LIST_APPEND
Беглый обзор “внутренностей” Python 28
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
Неймспейсы
Беглый обзор “внутренностей” Python 29
Дикты и строки
Многие знакомые с семантикой Python люди шутят, что Гвидо ван
Россум написал dict (открытую хеш-таблицу) и string
(неизменяемые строки), после чего решил больше ничего не
писать
Беглый обзор “внутренностей” Python 30
Дикты и строки
Многие знакомые с семантикой Python люди шутят, что Гвидо ван
Россум написал dict (открытую хеш-таблицу) и string
(неизменяемые строки), после чего решил больше ничего не
писать
Действительно, объекты, модули и неймспейсы - все это обычные
дикты cо строковыми ключами
Беглый обзор “внутренностей” Python 30
Дикты и строки
Многие знакомые с семантикой Python люди шутят, что Гвидо ван
Россум написал dict (открытую хеш-таблицу) и string
(неизменяемые строки), после чего решил больше ничего не
писать
Действительно, объекты, модули и неймспейсы - все это обычные
дикты cо строковыми ключами
Глобальное пространство имен модуля, следовательно, тоже дикт,
а обращение к переменной - это lookup в дикте.
Беглый обзор “внутренностей” Python 30
Дикты и строки
Многие знакомые с семантикой Python люди шутят, что Гвидо ван
Россум написал dict (открытую хеш-таблицу) и string
(неизменяемые строки), после чего решил больше ничего не
писать
Действительно, объекты, модули и неймспейсы - все это обычные
дикты cо строковыми ключами
Глобальное пространство имен модуля, следовательно, тоже дикт,
а обращение к переменной - это lookup в дикте.
Всегда ли это так?
Беглый обзор “внутренностей” Python 30
Дикты и строки
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
LOAD_FAST и LOAD_GLOBAL
3 6 LOAD_FAST 0 (a)
9 LOAD_GLOBAL 0 (b)
Для имен a и b компилятор использовал разные опкоды. Почему?
Беглый обзор “внутренностей” Python 32
LOAD_FAST и LOAD_GLOBAL
3 6 LOAD_FAST 0 (a)
9 LOAD_GLOBAL 0 (b)
Для имен a и b компилятор использовал разные опкоды. Почему?
Оказывается, на этапе компиляции в code object собираются все
имена локальных переменных (то есть присваиваемые в этом
блоке кода и не помеченные явно при помощи global), и
заносятся в список co_locals.
Беглый обзор “внутренностей” Python 32
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
LOAD_FAST и LOAD_GLOBAL
LOAD_FAST, как нетрудно догадаться, просто берет значение по
индексу из массива
Беглый обзор “внутренностей” Python 33
LOAD_FAST и LOAD_GLOBAL
LOAD_FAST, как нетрудно догадаться, просто берет значение по
индексу из массива
LOAD_GLOBAL же вынужден брать строку с именем из co_consts,
после чего искать по этому ключу в дикте неймспейса
Беглый обзор “внутренностей” Python 33
LOAD_FAST и LOAD_GLOBAL
LOAD_FAST, как нетрудно догадаться, просто берет значение по
индексу из массива
LOAD_GLOBAL же вынужден брать строку с именем из co_consts,
после чего искать по этому ключу в дикте неймспейса
Обе операции выполняются с ожидаемой стоимостью O(1), но у
массива константа явно лучше
Беглый обзор “внутренностей” Python 33
LOAD_FAST и LOAD_GLOBAL
LOAD_FAST, как нетрудно догадаться, просто берет значение по
индексу из массива
LOAD_GLOBAL же вынужден брать строку с именем из co_consts,
после чего искать по этому ключу в дикте неймспейса
Обе операции выполняются с ожидаемой стоимостью O(1), но у
массива константа явно лучше
Поэтому закешировать что-то в локальную переменную не самая
плохая идея в плане производительности
Беглый обзор “внутренностей” Python 33
Lexical scoping
Глобальный и локальный неймспейсы достаточно очевидны сами
по себе
Беглый обзор “внутренностей” Python 34
Lexical scoping
Глобальный и локальный неймспейсы достаточно очевидны сами
по себе
Однако Python позволяет делать вот так:
def f():
a = 1
def g(): return a
return g
>>> t = f()
>>> t()
1
Беглый обзор “внутренностей” Python 34
Lexical scoping
Глобальный и локальный неймспейсы достаточно очевидны сами
по себе
Однако Python позволяет делать вот так:
def f():
a = 1
def g(): return a
return g
>>> t = f()
>>> t()
1
Как возвращенной функции удается вернуть значение из
разрушенного фрейма?
Беглый обзор “внутренностей” Python 34
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
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
Lexical scoping
cell object по времени жизни не привязан к фрейму, в котором
он создан, и собирается сборщиком мусора только тогда, когда
станет недостижим.
Беглый обзор “внутренностей” Python 36