Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Python изнутри. Объекты. Предисловие

Anna | 16.06.2014 | нет комментариев
1. Вступление.
2. Объекты. Предисловие

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

Как я и писал в предыдущем эпизоде (тот, что, кстати, оказался удачным; спасибо каждому, ваши просмотры и комментарии дословно принуждают меня двигаться дальше!) – сегодняшний пост посвящён реализации объектов в Python 3.x. Вначале я думал, что это простая тема. Но даже когда я прочитал каждый код, тот, что необходимо было прочитать перед тем, как написать пост, я с трудом могу сказать, что объектная система Питона… гхм, «простая» (и я, определённо, не могу сказать, что до конца разобрался в ней). Но я ещё огромнее удостоверился, что реализация объектов — отличная тема для начала. В следующих постах мы увидим, насколько она значима. В то же время, я подозреваю, немного кто, даже среди ветеранов Питона, в полной мере в ней разбирается. Объекты слабо связаны со каждому остальным Питоном (при написании поста я немного заглядывал в./Python, и огромнее постигал ./Objects и ./Include). Мне показалось проще рассматривать реализацию объектов так, словно она вообще не связана со каждому остальным. Так, словно это многофункциональный API на языке C для создания объектных подсистем. Допустимо, вам тоже будет проще думать таким образом: запомните, всё это каждого лишь комплект конструкций и функций для управления этими конструкциями.

Всё в Питоне — объект: числа, словари, пользовательские и встроенные классы, стековые фреймы и объекты кода. Дабы указатель на участок памяти дозволено было считать объектом, нужны как минимум два поля, определённые в структуре ./Include/object.hPyObject:

typedef struct _object {
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

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

Счётчик ссылок — это число, показывающее, сколько раз другие объекты ссылаются на данный. В коде >>> a = b = c = object() инициализируется пустой объект и связывается с тремя различными именами: ab и c. Всякое имя создаёт новую ссылку на объект, но при этом объект создаётся однажды. Связывание объекта с новым именем либо добавление объекта в список создаёт новую ссылку, но не создаёт новейший объект! На эту тему дозволено ещё много говорить, но это огромнее относится к сборке мусора, а не к объектной системе. Я отменнее напишу об этом обособленный пост, взамен того, Дабы разбирать данный вопрос тут. Но, раньше чем оставить эту тему, скажу, что сейчас нам проще осознать макрос ./Include/object.h:Py_DECREF, с которым мы встретились в первой части: он каждого лишь декрементирует ob_refcnt (и освобождает источники, если ob_refcnt принимает нулевое значение). На этом пока покончим с подсчётом ссылок.

Остаётся разобрать ob_type, указатель на тип объекта, центральное представление объектной модели Питона (имейте в виду: в третьем Питоне, тип и класс по сути одно и то же; по историческим причинам применение этих терминов зависит от контекста). У всякого объекта каждого один тип, тот, что не меняется в течение жизни объекта (тип может поменяться в Исключительно редких обстоятельствах. Для этой задачи не существует API, и вы вряд ли читали бы эту статью, если бы трудились с объектами с изменяющимися типами). Главней, может быть, то, что тип объекта (и только тип объекта) определяет, что дозволено с ним делать (пример в спойлере позже этого абзаца). Как вы помните из первой части, при выполнении операции вычитания вызывается одна и та же функция (PyNumber_Subtract) вне зависимости от типа операндов: для целых чисел, для целого и дробного либо даже для полнейшего вздора, как бы вычитания исключения из словаря.

Показать код

# тип, а не экземпляр, определяет, что дозволено делать с экземпляром
>>> class Foo(object):
...     "I don't have __call__, so I can't be called"
... 
>>> class Bar(object):
...     __call__ = lambda *a, **kw: 42
... 
>>> foo = Foo()
>>> bar = Bar()
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Foo' object is not callable
>>> bar()
42
# может добавить __call__?
>>> foo.__call__ = lambda *a, **kw: 42
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Foo' object is not callable
# а если добавить его к Foo?
>>> Foo.__call__ = lambda *a, **kw: 42
>>> foo()
42
>>>

Вначале это кажется необычным. Как одна сишная функция может поддерживать всякий вид передаваемых ей объектов? Она может получить указатель void * (на самом деле, она получит указатель PyObject *, но это немного что значит, потому что учитываются данные объекта), но как она определит, что делать с полученным доводом? Результат заключён в типе объекта. Тип также является объектом (у него есть и счётчик ссылок, и его личный тип; тип большинства типов — type), но в дополнение к двум основным полям он содержит уйма других полей. Определение конструкции и изложение её полей постигайте тут. Само определение находится в./Include/object.hPyTypeObject. Я рекомендую обращаться к нему по ходу чтения статьи. Многие поля объекта типа именуются слотами и указывают на функции (либо на конструкции, указывающие на родные функции), которые будут исполнены при вызове функции C-API Питона на объектах этого типа. И хоть нам и кажется, что PyNumber_Subtract работает с доводами различных типов, на самом деле типы операндов разыменовываются и вызывается специфичная данному типу функция вычитания. Таким образом, функции C-API не универсальные. Они полагаются на типы и абстрагируются от деталей, и создаётся ощущение, что они работают с всякими данными (при этом выбрасывание исключения TypeError — это тоже работа).

Давайте разберём детали. PyNumber_Subtract вызывает универсальную функцию 2-х доводов./Objects/abstract.cbinary_op, указав, что трудиться необходимо со слотом nb_subtract (сходственные слоты есть и для других операций, скажем, nb_negative для отрицания чисел либо sq_length для определения длины последовательности). binary_op — это обёртка с проверкой ошибок над binary_op1, функцией, которая исполняет всю работу. ./Objects/abstract.cbinary_op1 (почитайте код этой функции — на многое открывает глаза) принимает операнды операции BINARY_SUBTRACT как v и w, и пытается разыменовать v->ob_type->tp_as_numberконструкцию, содержащую укon.org/cpython/file/tip/Objects/typeobject.c”>./Objects/typeobject.ctype_setattro) вызывает ту же самую функцию (update_one_slot), которую использует fixup_slot_dispatchers для урегулирования всех вопросов позже определения нового признака. Вскрываются новые детали!

На этом, вероятно, стоит завершить вступление в объекты Питона. Верю, поездка доставила вам наслаждение, и вы до сих пор со мною. Должен признать, что писать данный пост оказалось значительно труднее, чем я полагал (и без помощи Antoine Pitrou и Mark Dickins поздней ночью на #python-dev я бы скорее каждого сдался!). У нас осталось ещё много увлекательного: какой слот операнда применяется в бинарных операциях? Что происходит при множественном наследовании, и что насчёт тех ужасныймельчайших деталях, связанных с ним? А что с метаклассами? А __slots__ и слабые ссылки? Что творится во встроенных объектах? Каксловариспискимножества и их собраться исполняют свою работу? И напоследок, что насчёт этого Дива?

>>> a = object()
>>> class C(object): pass
... 
>>> b = C()
>>> a.foo = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute 'foo'
>>> b.foo = 5
>>>

Каким образом дозволено легко так добавить произвольный признак в b, экземпляр класса C, тот, что наследуется от object, и невозможно сделать то же самое с a, экземпляром того же самого object? Знающие могут сказать: у b есть __dict__, а у a нет. Да, это так. Но откуда тогда взялась эта новая (и абсолютно нетривиальная!) функциональность, если мы её не наследуем?

Ха! Я безрассудно рад таким вопросам! Результаты будут, но в дальнейшем эпизоде.

Маленький список литературы для любопытствующих:

  • документация по модели данных (питонячья сторона силы);
  • документация C-API по абстрактным и определенным объектом (сишная сторона силы);
  • descrintro, либо Унификация типов и классов в Python 2.2, длинная, мозговыносящая и Исключительно значимая археологическая находка (считаю, что её следует добавить в интерпретатор в качестве пасхалки, предлагаю >>> import THAT);
  • но раньше каждого данный файл — ./Objects/typeobject.c. Читайте его вновь и вновь, до тех пор, пока в слезах не рухнете на кровать.

Славных сновидений.

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB