Беглый обзор "внутренностей" python
DESCRIPTION
Беглый обзор "внутренностей" Python. Автор: Никита ЛесниковTRANSCRIPT
Беглый обзор “внутренностей” PythonНикита Лесников
Почему это может быть полезно?
Часто у программиста, использующего в работе Python, возникаетодин из следующих вопросов:
какой из двух способов решения проблемы потребляет меньшепамяти?
какой из двух способов работает быстрее?как поведет себя определенная конструкция при измененииruntime среды?
Беглый обзор “внутренностей” Python 2
Почему это может быть полезно?
Часто у программиста, использующего в работе Python, возникаетодин из следующих вопросов:
какой из двух способов решения проблемы потребляет меньшепамяти?какой из двух способов работает быстрее?
как поведет себя определенная конструкция при измененииruntime среды?
Беглый обзор “внутренностей” Python 2
Почему это может быть полезно?
Часто у программиста, использующего в работе Python, возникаетодин из следующих вопросов:
какой из двух способов решения проблемы потребляет меньшепамяти?какой из двух способов работает быстрее?как поведет себя определенная конструкция при измененииruntime среды?
Беглый обзор “внутренностей” Python 2
Почему это может быть полезно?
Решаются они обычно одним из следующих способов:можно спросить у коллег
можно найти ответ на StackOverflowможно замерить самому
Беглый обзор “внутренностей” Python 3
Почему это может быть полезно?
Решаются они обычно одним из следующих способов:можно спросить у коллегможно найти ответ на StackOverflow
можно замерить самому
Беглый обзор “внутренностей” Python 3
Почему это может быть полезно?
Решаются они обычно одним из следующих способов:можно спросить у коллегможно найти ответ на StackOverflowможно замерить самому
Беглый обзор “внутренностей” Python 3
Почему это может быть полезно?
Однако понимание того, как работает интерпретатор на самыхнижних уровнях, позволяет обрести интуитивное пониманиенюансов работы многих конструкций языка.
Кроме того, в случае с Python абсолютное большинствопрограммистов обладает достаточной квалификацией, чтобы безпосредников найти ответ на свой вопрос в исходникахинтерпретатора.
Беглый обзор “внутренностей” Python 4
Почему это может быть полезно?
Однако понимание того, как работает интерпретатор на самыхнижних уровнях, позволяет обрести интуитивное пониманиенюансов работы многих конструкций языка.Кроме того, в случае с Python абсолютное большинствопрограммистов обладает достаточной квалификацией, чтобы безпосредников найти ответ на свой вопрос в исходникахинтерпретатора.
Беглый обзор “внутренностей” Python 4
Почему это может быть полезно?
Знакомство с “внутренностями” полезно также потому, что Pythonявляется open source проектом - кто знает, быть может именно вырешите одну из наболевших проблем? ;)
Кратким изучением наиболее характерных особенностейинтерпретатора мы сейчас и займемся.
Беглый обзор “внутренностей” Python 5
Почему это может быть полезно?
Знакомство с “внутренностями” полезно также потому, что Pythonявляется open source проектом - кто знает, быть может именно вырешите одну из наболевших проблем? ;)Кратким изучением наиболее характерных особенностейинтерпретатора мы сейчас и займемся.
Беглый обзор “внутренностей” Python 5
CPython
Основная реализация Python на сегодняшний день
Написана на C (не С++), кроссплатформенна и довольно легкопереносима на отличные от официально поддерживаемыхплатформыКод простой и понятныйНет, серьезно, простой и понятный, даже для людей, неинтересующихся разработкой языков программирования ;)
Беглый обзор “внутренностей” Python 6
CPython
Основная реализация Python на сегодняшний деньНаписана на C (не С++), кроссплатформенна и довольно легкопереносима на отличные от официально поддерживаемыхплатформы
Код простой и понятныйНет, серьезно, простой и понятный, даже для людей, неинтересующихся разработкой языков программирования ;)
Беглый обзор “внутренностей” Python 6
CPython
Основная реализация Python на сегодняшний деньНаписана на C (не С++), кроссплатформенна и довольно легкопереносима на отличные от официально поддерживаемыхплатформыКод простой и понятный
Нет, серьезно, простой и понятный, даже для людей, неинтересующихся разработкой языков программирования ;)
Беглый обзор “внутренностей” Python 6
CPython
Основная реализация Python на сегодняшний деньНаписана на C (не С++), кроссплатформенна и довольно легкопереносима на отличные от официально поддерживаемыхплатформыКод простой и понятныйНет, серьезно, простой и понятный, даже для людей, неинтересующихся разработкой языков программирования ;)
Беглый обзор “внутренностей” Python 6
CPython
Есть еще PyPy, IronPython, Jython, Boo (хотя это не совсемPython)
Они по своему интересны, но с CPython “внутри” у них малообщегоПоэтому хотя они и могут быть очень полезны на практике,рассматривать их мы не будем
Беглый обзор “внутренностей” Python 7
CPython
Есть еще PyPy, IronPython, Jython, Boo (хотя это не совсемPython)Они по своему интересны, но с CPython “внутри” у них малообщего
Поэтому хотя они и могут быть очень полезны на практике,рассматривать их мы не будем
Беглый обзор “внутренностей” Python 7
CPython
Есть еще PyPy, IronPython, Jython, Boo (хотя это не совсемPython)Они по своему интересны, но с CPython “внутри” у них малообщегоПоэтому хотя они и могут быть очень полезны на практике,рассматривать их мы не будем
Беглый обзор “внутренностей” Python 7
Все - объект.
Беглый обзор “внутренностей” Python 8
Все - объект
В Python все является объектом
Ну то есть вообще все - от чисел до стакфреймовНа C-уровне это выражено типом PyObject *
Любой PyObject имеет стандартный заголовок:
#define PyObject_HEAD \Py_ssize_t ob_refcnt; \struct _typeobject *ob_type;
Поэтому в 64-битном Python число не может быть меньше 24байт. Deal with it.
Беглый обзор “внутренностей” 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
Все - объект
В Python все является объектомНу то есть вообще все - от чисел до стакфреймовНа C-уровне это выражено типом PyObject *
Любой PyObject имеет стандартный заголовок:
#define PyObject_HEAD \Py_ssize_t ob_refcnt; \struct _typeobject *ob_type;
Поэтому в 64-битном Python число не может быть меньше 24байт. Deal with it.
Беглый обзор “внутренностей” 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
Все - объект
В 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 counterob_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 - особый объект
Он один на каждый инстанс интерпретатора. Совсем один.Поэтому для него is и == всегда эквивалентны.Вот так вот делать не стоит:
if x == None:
это медленно, бессмысленно и вообще плохой тон.
Беглый обзор “внутренностей” Python 12
None
None - особый объектОн один на каждый инстанс интерпретатора. Совсем один.
Поэтому для него is и == всегда эквивалентны.Вот так вот делать не стоит:
if x == None:
это медленно, бессмысленно и вообще плохой тон.
Беглый обзор “внутренностей” Python 12
None
None - особый объектОн один на каждый инстанс интерпретатора. Совсем один.Поэтому для него is и == всегда эквивалентны.
Вот так вот делать не стоит:
if x == None:
это медленно, бессмысленно и вообще плохой тон.
Беглый обзор “внутренностей” Python 12
None
None - особый объектОн один на каждый инстанс интерпретатора. Совсем один.Поэтому для него is и == всегда эквивалентны.Вот так вот делать не стоит:
if x == None:
это медленно, бессмысленно и вообще плохой тон.
Беглый обзор “внутренностей” Python 12
intint - тип “малых” целых чисел
В Python целые - неизменяемый типПроверим как на них работает is:
>>> int("100") is int("100")True>>> int("1000") is int("1000")False
Как это объяснить? Оказывается, интерпретатор “кеширует”объекты int со значениями от -5 до 256, для других значенийсоздаются самостоятельные объекты.Поэтому список intов размером до байта будет иметь overhead в 8байт на элемент (указатель), а больших intов - до 32 байт наэлемент (указатель + объект).
Беглый обзор “внутренностей” Python 13
intint - тип “малых” целых чиселВ Python целые - неизменяемый тип
Проверим как на них работает is:
>>> int("100") is int("100")True>>> int("1000") is int("1000")False
Как это объяснить? Оказывается, интерпретатор “кеширует”объекты int со значениями от -5 до 256, для других значенийсоздаются самостоятельные объекты.Поэтому список intов размером до байта будет иметь overhead в 8байт на элемент (указатель), а больших intов - до 32 байт наэлемент (указатель + объект).
Беглый обзор “внутренностей” Python 13
intint - тип “малых” целых чиселВ Python целые - неизменяемый типПроверим как на них работает is:
>>> int("100") is int("100")True>>> int("1000") is int("1000")False
Как это объяснить? Оказывается, интерпретатор “кеширует”объекты int со значениями от -5 до 256, для других значенийсоздаются самостоятельные объекты.Поэтому список intов размером до байта будет иметь overhead в 8байт на элемент (указатель), а больших intов - до 32 байт наэлемент (указатель + объект).
Беглый обзор “внутренностей” Python 13
intint - тип “малых” целых чиселВ Python целые - неизменяемый типПроверим как на них работает is:
>>> int("100") is int("100")True>>> int("1000") is int("1000")False
Как это объяснить? Оказывается, интерпретатор “кеширует”объекты int со значениями от -5 до 256, для других значенийсоздаются самостоятельные объекты.
Поэтому список intов размером до байта будет иметь overhead в 8байт на элемент (указатель), а больших intов - до 32 байт наэлемент (указатель + объект).
Беглый обзор “внутренностей” Python 13
intint - тип “малых” целых чиселВ 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 неизменяемы
Однако sharing объектов их затрагивает куда сильнееИнтерпретатор поддерживает dict так называемых internedстрок, для каждой из которых гарантированно существует ровноодин объектВсе идентификаторы (имена переменных, модулей, методов),автоматически туда помещаютсяОператор == для interned строк выраждается в is (сравнениеуказателей)Помимо этого, по аналогии с int со значениями [−5, 256]разделяются объекты пустой и всех возможных однобуквенныхстрок
Беглый обзор “внутренностей” Python 15
string
Строки, как и целые, в Python неизменяемыОднако sharing объектов их затрагивает куда сильнее
Интерпретатор поддерживает dict так называемых internedстрок, для каждой из которых гарантированно существует ровноодин объектВсе идентификаторы (имена переменных, модулей, методов),автоматически туда помещаютсяОператор == для interned строк выраждается в is (сравнениеуказателей)Помимо этого, по аналогии с int со значениями [−5, 256]разделяются объекты пустой и всех возможных однобуквенныхстрок
Беглый обзор “внутренностей” Python 15
string
Строки, как и целые, в Python неизменяемыОднако sharing объектов их затрагивает куда сильнееИнтерпретатор поддерживает dict так называемых internedстрок, для каждой из которых гарантированно существует ровноодин объект
Все идентификаторы (имена переменных, модулей, методов),автоматически туда помещаютсяОператор == для interned строк выраждается в is (сравнениеуказателей)Помимо этого, по аналогии с int со значениями [−5, 256]разделяются объекты пустой и всех возможных однобуквенныхстрок
Беглый обзор “внутренностей” Python 15
string
Строки, как и целые, в Python неизменяемыОднако sharing объектов их затрагивает куда сильнееИнтерпретатор поддерживает dict так называемых internedстрок, для каждой из которых гарантированно существует ровноодин объектВсе идентификаторы (имена переменных, модулей, методов),автоматически туда помещаются
Оператор == для interned строк выраждается в is (сравнениеуказателей)Помимо этого, по аналогии с int со значениями [−5, 256]разделяются объекты пустой и всех возможных однобуквенныхстрок
Беглый обзор “внутренностей” Python 15
string
Строки, как и целые, в Python неизменяемыОднако sharing объектов их затрагивает куда сильнееИнтерпретатор поддерживает dict так называемых internedстрок, для каждой из которых гарантированно существует ровноодин объектВсе идентификаторы (имена переменных, модулей, методов),автоматически туда помещаютсяОператор == для interned строк выраждается в is (сравнениеуказателей)
Помимо этого, по аналогии с int со значениями [−5, 256]разделяются объекты пустой и всех возможных однобуквенныхстрок
Беглый обзор “внутренностей” Python 15
string
Строки, как и целые, в Python неизменяемыОднако sharing объектов их затрагивает куда сильнееИнтерпретатор поддерживает dict так называемых internedстрок, для каждой из которых гарантированно существует ровноодин объектВсе идентификаторы (имена переменных, модулей, методов),автоматически туда помещаютсяОператор == для interned строк выраждается в is (сравнениеуказателей)Помимо этого, по аналогии с int со значениями [−5, 256]разделяются объекты пустой и всех возможных однобуквенныхстрок
Беглый обзор “внутренностей” Python 15
Краткий вывод
Не используйте is для чего-то кроме None
Unless you know what you’re doing
Беглый обзор “внутренностей” Python 16
Краткий вывод
Не используйте is для чего-то кроме None
Unless you know what you’re doing
Беглый обзор “внутренностей” Python 16
Байткод
Беглый обзор “внутренностей” Python 17
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на C
Его сначала нужно “разжевать” до очень простых операций,работающих с адресами и регистрамиКод на Python также не годится для непосредственногоисполненияПоэтому скрипт при загрузке транслируется в байткодвиртуальной машиныВиртуальная машина (VM) - это подпрограмма интерпретатора,испоняющая байткод.Но кроме отсутствия “железного” воплощения, концептуальныхразличий с процессором нет.
Беглый обзор “внутренностей” Python 18
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на CЕго сначала нужно “разжевать” до очень простых операций,работающих с адресами и регистрами
Код на Python также не годится для непосредственногоисполненияПоэтому скрипт при загрузке транслируется в байткодвиртуальной машиныВиртуальная машина (VM) - это подпрограмма интерпретатора,испоняющая байткод.Но кроме отсутствия “железного” воплощения, концептуальныхразличий с процессором нет.
Беглый обзор “внутренностей” Python 18
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на CЕго сначала нужно “разжевать” до очень простых операций,работающих с адресами и регистрамиКод на Python также не годится для непосредственногоисполнения
Поэтому скрипт при загрузке транслируется в байткодвиртуальной машиныВиртуальная машина (VM) - это подпрограмма интерпретатора,испоняющая байткод.Но кроме отсутствия “железного” воплощения, концептуальныхразличий с процессором нет.
Беглый обзор “внутренностей” Python 18
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на CЕго сначала нужно “разжевать” до очень простых операций,работающих с адресами и регистрамиКод на Python также не годится для непосредственногоисполненияПоэтому скрипт при загрузке транслируется в байткодвиртуальной машины
Виртуальная машина (VM) - это подпрограмма интерпретатора,испоняющая байткод.Но кроме отсутствия “железного” воплощения, концептуальныхразличий с процессором нет.
Беглый обзор “внутренностей” Python 18
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на CЕго сначала нужно “разжевать” до очень простых операций,работающих с адресами и регистрамиКод на Python также не годится для непосредственногоисполненияПоэтому скрипт при загрузке транслируется в байткодвиртуальной машиныВиртуальная машина (VM) - это подпрограмма интерпретатора,испоняющая байткод.
Но кроме отсутствия “железного” воплощения, концептуальныхразличий с процессором нет.
Беглый обзор “внутренностей” Python 18
Виртуальная машина
“Железный” процессор не может напрямую выполнять код на CЕго сначала нужно “разжевать” до очень простых операций,работающих с адресами и регистрамиКод на Python также не годится для непосредственногоисполненияПоэтому скрипт при загрузке транслируется в байткодвиртуальной машиныВиртуальная машина (VM) - это подпрограмма интерпретатора,испоняющая байткод.Но кроме отсутствия “железного” воплощения, концептуальныхразличий с процессором нет.
Беглый обзор “внутренностей” Python 18
Байткод
Команды CPython VM кодируются одним байтом
Каждая из них может иметь опциональный 16-битный аргументКонцептуально VM является стековой машиной (уместныаналогии с обратной польской записью и языком Forth) -значения кладутся на value stack (не путать с call stack), операцииих снимают с вершины и кладут результат обратно.При помощи модуля dis можно посмотреть на байткод “живых”функций
Беглый обзор “внутренностей” Python 19
Байткод
Команды CPython VM кодируются одним байтомКаждая из них может иметь опциональный 16-битный аргумент
Концептуально VM является стековой машиной (уместныаналогии с обратной польской записью и языком Forth) -значения кладутся на value stack (не путать с call stack), операцииих снимают с вершины и кладут результат обратно.При помощи модуля dis можно посмотреть на байткод “живых”функций
Беглый обзор “внутренностей” Python 19
Байткод
Команды CPython VM кодируются одним байтомКаждая из них может иметь опциональный 16-битный аргументКонцептуально VM является стековой машиной (уместныаналогии с обратной польской записью и языком Forth) -значения кладутся на value stack (не путать с call stack), операцииих снимают с вершины и кладут результат обратно.
При помощи модуля dis можно посмотреть на байткод “живых”функций
Беглый обзор “внутренностей” Python 19
Байткод
Команды CPython VM кодируются одним байтомКаждая из них может иметь опциональный 16-битный аргументКонцептуально VM является стековой машиной (уместныаналогии с обратной польской записью и языком Forth) -значения кладутся на value stack (не путать с call stack), операцииих снимают с вершины и кладут результат обратно.При помощи модуля dis можно посмотреть на байткод “живых”функций
Беглый обзор “внутренностей” Python 19
def f():a = 1b = 2c = 3return 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_MULTIPLY28 BINARY_ADD29 RETURN_VALUE
Беглый обзор “внутренностей” Python 20
Константы
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
Константы
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
Константы
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-объекты
Однако их достаточно, чтобы кодировать смещения. Например, всписок констант co_consts
Наряду с другой метаинформацией (количество локальныхпеременных, количество параметров, глубина стека) байткодформирует code object
Именно code object являет собой единицу существованиякомпилированного кода в PythonОни же сериализуются в .pyc файлы
Беглый обзор “внутренностей” Python 22
Code object
Как мы видим, байткод это не просто строка байт, так как16-битных целых недостаточно, чтобы кодировать любыеPython-объектыОднако их достаточно, чтобы кодировать смещения. Например, всписок констант co_consts
Наряду с другой метаинформацией (количество локальныхпеременных, количество параметров, глубина стека) байткодформирует code object
Именно code object являет собой единицу существованиякомпилированного кода в PythonОни же сериализуются в .pyc файлы
Беглый обзор “внутренностей” Python 22
Code object
Как мы видим, байткод это не просто строка байт, так как16-битных целых недостаточно, чтобы кодировать любыеPython-объектыОднако их достаточно, чтобы кодировать смещения. Например, всписок констант co_consts
Наряду с другой метаинформацией (количество локальныхпеременных, количество параметров, глубина стека) байткодформирует code object
Именно code object являет собой единицу существованиякомпилированного кода в PythonОни же сериализуются в .pyc файлы
Беглый обзор “внутренностей” Python 22
Code object
Как мы видим, байткод это не просто строка байт, так как16-битных целых недостаточно, чтобы кодировать любыеPython-объектыОднако их достаточно, чтобы кодировать смещения. Например, всписок констант co_consts
Наряду с другой метаинформацией (количество локальныхпеременных, количество параметров, глубина стека) байткодформирует code object
Именно code object являет собой единицу существованиякомпилированного кода в Python
Они же сериализуются в .pyc файлы
Беглый обзор “внутренностей” Python 22
Code object
Как мы видим, байткод это не просто строка байт, так как16-битных целых недостаточно, чтобы кодировать любыеPython-объектыОднако их достаточно, чтобы кодировать смещения. Например, всписок констант co_consts
Наряду с другой метаинформацией (количество локальныхпеременных, количество параметров, глубина стека) байткодформирует code object
Именно code object являет собой единицу существованиякомпилированного кода в PythonОни же сериализуются в .pyc файлы
Беглый обзор “внутренностей” Python 22
Code objectcode object являются неизменяемыми
В “нормальном” коде они строятся единожды - при загрузкемодуляЭто такой же объект как и все:
def f():def g(): passreturn g
2 0 LOAD_CONST 1 (<code object f ...>)3 MAKE_FUNCTION 06 STORE_FAST 0 (f)
3 9 LOAD_GLOBAL 0 (g)12 RETURN_VALUE
Беглый обзор “внутренностей” Python 23
Code objectcode object являются неизменяемымиВ “нормальном” коде они строятся единожды - при загрузкемодуля
Это такой же объект как и все:
def f():def g(): passreturn g
2 0 LOAD_CONST 1 (<code object f ...>)3 MAKE_FUNCTION 06 STORE_FAST 0 (f)
3 9 LOAD_GLOBAL 0 (g)12 RETURN_VALUE
Беглый обзор “внутренностей” Python 23
Code objectcode object являются неизменяемымиВ “нормальном” коде они строятся единожды - при загрузкемодуляЭто такой же объект как и все:
def f():def g(): passreturn g
2 0 LOAD_CONST 1 (<code object f ...>)3 MAKE_FUNCTION 06 STORE_FAST 0 (f)
3 9 LOAD_GLOBAL 0 (g)12 RETURN_VALUE
Беглый обзор “внутренностей” Python 23
Компиляция
Процесс преобразования исходного кода модуля в наборcode object называется компиляцией
Однако компилятор у Python очень рудиментарный - фактически1-в-1 отображение конструкций в байткодОптимизаций уровня байткода почти нетuncompyle, open source декомпилятор Python, способенвосстановить исходный код по .pyc файлу почти всегда, потерявпри этом разве что комментарииТак как компилятор неоптимизирующий, ему почти всегда можнопомочь (если надо)
Беглый обзор “внутренностей” 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
Компиляция
Процесс преобразования исходного кода модуля в набор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, но всеравно это долго.
Логично не выполнять эту операцию в теле цикла, а сделать ееединожды до начала выполнения.Эта оптимизация известна как loop hoisting, но рудиментарныйкомпилятор Python ее не делает.Поможем ему!
Беглый обзор “внутренностей” Python 26
Loop hoisting
LOAD_ATTR по идее выполняется небыстро - это поиск строки сименем метода в дикте (хеш-таблице). Строка interned, но всеравно это долго.Логично не выполнять эту операцию в теле цикла, а сделать ееединожды до начала выполнения.
Эта оптимизация известна как loop hoisting, но рудиментарныйкомпилятор Python ее не делает.Поможем ему!
Беглый обзор “внутренностей” 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.appendfor 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
Как показывает изучение исходников компилятора, используетсяэтот опкод только для компиляции 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
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(неизменяемые строки), после чего решил больше ничего неписать
Действительно, объекты, модули и неймспейсы - все это обычныедикты cо строковыми ключамиГлобальное пространство имен модуля, следовательно, тоже дикт,а обращение к переменной - это lookup в дикте.Всегда ли это так?
Беглый обзор “внутренностей” Python 30
Дикты и строки
Многие знакомые с семантикой Python люди шутят, что Гвидо ванРоссум написал dict (открытую хеш-таблицу) и string(неизменяемые строки), после чего решил больше ничего неписатьДействительно, объекты, модули и неймспейсы - все это обычныедикты cо строковыми ключами
Глобальное пространство имен модуля, следовательно, тоже дикт,а обращение к переменной - это lookup в дикте.Всегда ли это так?
Беглый обзор “внутренностей” Python 30
Дикты и строки
Многие знакомые с семантикой Python люди шутят, что Гвидо ванРоссум написал dict (открытую хеш-таблицу) и string(неизменяемые строки), после чего решил больше ничего неписатьДействительно, объекты, модули и неймспейсы - все это обычныедикты cо строковыми ключамиГлобальное пространство имен модуля, следовательно, тоже дикт,а обращение к переменной - это lookup в дикте.
Всегда ли это так?
Беглый обзор “внутренностей” Python 30
Дикты и строки
Многие знакомые с семантикой Python люди шутят, что Гвидо ванРоссум написал dict (открытую хеш-таблицу) и string(неизменяемые строки), после чего решил больше ничего неписатьДействительно, объекты, модули и неймспейсы - все это обычныедикты cо строковыми ключамиГлобальное пространство имен модуля, следовательно, тоже дикт,а обращение к переменной - это lookup в дикте.Всегда ли это так?
Беглый обзор “внутренностей” Python 30
Дикты и строки
def f():a = 3return 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_ADD13 RETURN_VALUE
Беглый обзор “внутренностей” Python 31
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
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
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, как нетрудно догадаться, просто берет значение поиндексу из массива
LOAD_GLOBAL же вынужден брать строку с именем из co_consts,после чего искать по этому ключу в дикте неймспейсаОбе операции выполняются с ожидаемой стоимостью O(1), но умассива константа явно лучшеПоэтому закешировать что-то в локальную переменную не самаяплохая идея в плане производительности
Беглый обзор “внутренностей” 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
LOAD_FAST и LOAD_GLOBAL
LOAD_FAST, как нетрудно догадаться, просто берет значение поиндексу из массиваLOAD_GLOBAL же вынужден брать строку с именем из co_consts,после чего искать по этому ключу в дикте неймспейсаОбе операции выполняются с ожидаемой стоимостью O(1), но умассива константа явно лучшеПоэтому закешировать что-то в локальную переменную не самаяплохая идея в плане производительности
Беглый обзор “внутренностей” Python 33
Lexical scoping
Глобальный и локальный неймспейсы достаточно очевидны самипо себе
Однако Python позволяет делать вот так:
def f():a = 1def g(): return areturn g
>>> t = f()>>> t()1
Как возвращенной функции удается вернуть значение изразрушенного фрейма?
Беглый обзор “внутренностей” Python 34
Lexical scoping
Глобальный и локальный неймспейсы достаточно очевидны самипо себеОднако Python позволяет делать вот так:
def f():a = 1def g(): return areturn g
>>> t = f()>>> t()1
Как возвращенной функции удается вернуть значение изразрушенного фрейма?
Беглый обзор “внутренностей” Python 34
Lexical scoping
Глобальный и локальный неймспейсы достаточно очевидны самипо себеОднако Python позволяет делать вот так:
def f():a = 1def g(): return areturn 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 018 STORE_FAST 0 (g)
4 21 LOAD_FAST 0 (g)24 RETURN_VALUE
cell object - это код функции вместе со значениями “внешних”(свободных, free) переменных
Беглый обзор “внутренностей” 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 018 STORE_FAST 0 (g)
4 21 LOAD_FAST 0 (g)24 RETURN_VALUE
cell object - это код функции вместе со значениями “внешних”(свободных, free) переменных
Беглый обзор “внутренностей” Python 35
Lexical scoping
cell object по времени жизни не привязан к фрейму, в которомон создан, и собирается сборщиком мусора только тогда, когдастанет недостижим.
Это “взрослая” реализация лексической области видимостиОднако, к сожалению, она неполна:
def f():a = 1if True: a = 2; print aprint a
этот код выведет 2 2. Аналогичный код на C++, или, скажем,Lua, выдаст 2 1
Это источник трудноуловимых багов
Беглый обзор “внутренностей” Python 36
Lexical scoping
cell object по времени жизни не привязан к фрейму, в которомон создан, и собирается сборщиком мусора только тогда, когдастанет недостижим.Это “взрослая” реализация лексической области видимости
Однако, к сожалению, она неполна:
def f():a = 1if True: a = 2; print aprint a
этот код выведет 2 2. Аналогичный код на C++, или, скажем,Lua, выдаст 2 1
Это источник трудноуловимых багов
Беглый обзор “внутренностей” Python 36
Lexical scoping
cell object по времени жизни не привязан к фрейму, в которомон создан, и собирается сборщиком мусора только тогда, когдастанет недостижим.Это “взрослая” реализация лексической области видимостиОднако, к сожалению, она неполна:
def f():a = 1if True: a = 2; print aprint a
этот код выведет 2 2. Аналогичный код на C++, или, скажем,Lua, выдаст 2 1
Это источник трудноуловимых багов
Беглый обзор “внутренностей” Python 36
Lexical scoping
cell object по времени жизни не привязан к фрейму, в которомон создан, и собирается сборщиком мусора только тогда, когдастанет недостижим.Это “взрослая” реализация лексической области видимостиОднако, к сожалению, она неполна:
def f():a = 1if True: a = 2; print aprint a
этот код выведет 2 2. Аналогичный код на C++, или, скажем,Lua, выдаст 2 1
Это источник трудноуловимых багов
Беглый обзор “внутренностей” Python 36
Выстрелить в ногу!
Что выведет это код?
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
Выстрелить в ногу!
Что выведет это код?
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
Выстрелить в ногу!
Что выведет это код?
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
Выстрелить в ногу!
Что выведет это код?
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
Lexical scoping
К сожалению, различие поведения областей видимости на макро-и микроуровнях настолько глубоко зашито в архитектуре языка,что поправить его практически нереально
Даже если это сделать - это с большой вероятностью приведет к“ломанию” имеющегося кодаПоэтому про данную особенность надо просто помнить, и несильно увлекаться функциональным программированием наPython ;)
Беглый обзор “внутренностей” Python 38
Lexical scoping
К сожалению, различие поведения областей видимости на макро-и микроуровнях настолько глубоко зашито в архитектуре языка,что поправить его практически нереальноДаже если это сделать - это с большой вероятностью приведет к“ломанию” имеющегося кода
Поэтому про данную особенность надо просто помнить, и несильно увлекаться функциональным программированием наPython ;)
Беглый обзор “внутренностей” Python 38
Lexical scoping
К сожалению, различие поведения областей видимости на макро-и микроуровнях настолько глубоко зашито в архитектуре языка,что поправить его практически нереальноДаже если это сделать - это с большой вероятностью приведет к“ломанию” имеющегося кодаПоэтому про данную особенность надо просто помнить, и несильно увлекаться функциональным программированием наPython ;)
Беглый обзор “внутренностей” Python 38
Заключение
Беглый обзор “внутренностей” Python 39
Заключение
К сожалению, регламент данного доклада не дает слишкомразогнаться. За бортом остаются многие интересные темы:
Global Interpreter Lock (GIL), или почему программы на Python нанескольких ядрах работают медленнее, чем на одномframe object и их список, или как greenlet умудряетсяэмулировать кооперативную многозадачность “срезанием” стекаООП в Python, или почему у object нельзя выставить атрибут, ау унаследованного от него пустого класса - можноГенераторы, или как “шаманство” с фреймами позволяетсоздавать видимость их (генераторов) наличия, но с серьезнымиограничениями
Беглый обзор “внутренностей” Python 40
Заключение
К сожалению, регламент данного доклада не дает слишкомразогнаться. За бортом остаются многие интересные темы:Global Interpreter Lock (GIL), или почему программы на Python нанескольких ядрах работают медленнее, чем на одном
frame object и их список, или как greenlet умудряетсяэмулировать кооперативную многозадачность “срезанием” стекаООП в Python, или почему у object нельзя выставить атрибут, ау унаследованного от него пустого класса - можноГенераторы, или как “шаманство” с фреймами позволяетсоздавать видимость их (генераторов) наличия, но с серьезнымиограничениями
Беглый обзор “внутренностей” Python 40
Заключение
К сожалению, регламент данного доклада не дает слишкомразогнаться. За бортом остаются многие интересные темы:Global Interpreter Lock (GIL), или почему программы на Python нанескольких ядрах работают медленнее, чем на одномframe object и их список, или как greenlet умудряетсяэмулировать кооперативную многозадачность “срезанием” стека
ООП в Python, или почему у object нельзя выставить атрибут, ау унаследованного от него пустого класса - можноГенераторы, или как “шаманство” с фреймами позволяетсоздавать видимость их (генераторов) наличия, но с серьезнымиограничениями
Беглый обзор “внутренностей” Python 40
Заключение
К сожалению, регламент данного доклада не дает слишкомразогнаться. За бортом остаются многие интересные темы:Global Interpreter Lock (GIL), или почему программы на Python нанескольких ядрах работают медленнее, чем на одномframe object и их список, или как greenlet умудряетсяэмулировать кооперативную многозадачность “срезанием” стекаООП в Python, или почему у object нельзя выставить атрибут, ау унаследованного от него пустого класса - можно
Генераторы, или как “шаманство” с фреймами позволяетсоздавать видимость их (генераторов) наличия, но с серьезнымиограничениями
Беглый обзор “внутренностей” Python 40
Заключение
К сожалению, регламент данного доклада не дает слишкомразогнаться. За бортом остаются многие интересные темы:Global Interpreter Lock (GIL), или почему программы на Python нанескольких ядрах работают медленнее, чем на одномframe object и их список, или как greenlet умудряетсяэмулировать кооперативную многозадачность “срезанием” стекаООП в Python, или почему у object нельзя выставить атрибут, ау унаследованного от него пустого класса - можноГенераторы, или как “шаманство” с фреймами позволяетсоздавать видимость их (генераторов) наличия, но с серьезнымиограничениями
Беглый обзор “внутренностей” Python 40
Заключение
Однако большой проблемы нет - это не сакральное знание
Все, что я вам сегодня рассказал, я не прочитал в интернете и наStackOverflow - я прочитал это в исходных кодах интерпретатораОни действительно неплохо структурированы и их довольнопросто читать, даже людям без соответствующего бэкграундаМне это помогло глубже понять как работает тот код, который япишу, что порой оказывалось очень полезным при профайлингекритичных местЭто как раз тот случай, когда вместо того, чтобы лезть наStackOverflow, полезнее и интереснее разобраться самому. Такчто дерзайте!
Беглый обзор “внутренностей” Python 41
Заключение
Однако большой проблемы нет - это не сакральное знаниеВсе, что я вам сегодня рассказал, я не прочитал в интернете и наStackOverflow - я прочитал это в исходных кодах интерпретатора
Они действительно неплохо структурированы и их довольнопросто читать, даже людям без соответствующего бэкграундаМне это помогло глубже понять как работает тот код, который япишу, что порой оказывалось очень полезным при профайлингекритичных местЭто как раз тот случай, когда вместо того, чтобы лезть наStackOverflow, полезнее и интереснее разобраться самому. Такчто дерзайте!
Беглый обзор “внутренностей” Python 41
Заключение
Однако большой проблемы нет - это не сакральное знаниеВсе, что я вам сегодня рассказал, я не прочитал в интернете и наStackOverflow - я прочитал это в исходных кодах интерпретатораОни действительно неплохо структурированы и их довольнопросто читать, даже людям без соответствующего бэкграунда
Мне это помогло глубже понять как работает тот код, который япишу, что порой оказывалось очень полезным при профайлингекритичных местЭто как раз тот случай, когда вместо того, чтобы лезть наStackOverflow, полезнее и интереснее разобраться самому. Такчто дерзайте!
Беглый обзор “внутренностей” Python 41
Заключение
Однако большой проблемы нет - это не сакральное знаниеВсе, что я вам сегодня рассказал, я не прочитал в интернете и наStackOverflow - я прочитал это в исходных кодах интерпретатораОни действительно неплохо структурированы и их довольнопросто читать, даже людям без соответствующего бэкграундаМне это помогло глубже понять как работает тот код, который япишу, что порой оказывалось очень полезным при профайлингекритичных мест
Это как раз тот случай, когда вместо того, чтобы лезть наStackOverflow, полезнее и интереснее разобраться самому. Такчто дерзайте!
Беглый обзор “внутренностей” Python 41
Заключение
Однако большой проблемы нет - это не сакральное знаниеВсе, что я вам сегодня рассказал, я не прочитал в интернете и наStackOverflow - я прочитал это в исходных кодах интерпретатораОни действительно неплохо структурированы и их довольнопросто читать, даже людям без соответствующего бэкграундаМне это помогло глубже понять как работает тот код, который япишу, что порой оказывалось очень полезным при профайлингекритичных местЭто как раз тот случай, когда вместо того, чтобы лезть наStackOverflow, полезнее и интереснее разобраться самому. Такчто дерзайте!
Беглый обзор “внутренностей” Python 41
Благодарю за внимание!
Вопросы?