Python datamodel

Объекты, значения и типы

У каждого объекта в python есть id, имя и значение. Объект можно представить через адрес в памяти - его идентичность никогда не меняется после создания - is сравнивает идентичность, а id() возвращает айдишник объекта (в CPython это представление адреса в памяти).

Тип объекта определяет операции, которые поддерживаются объектом и возможные значения объекта. type() возвращает тип объекта. Значение типа может меняться после создания объекта - объекты с иззменяемыми типами называются изменяемыми. При этом объекты-контейнеры с неизменяемым типом (к примеру кортежи) могут “содержать” объекты с изменяемым типом. Контейнеры содержат не сами объекты, а ссылки на них, поэтому при изменеении объектов, на которые указывают ссылки контейнеров, следует ожидать изменеения и при вызове контейнеров.

Объекты в python не уничтожаются явным способом, но при отсутствии ссылок на них, их может собрать сборщик мусора. CPython использует схему подсчета ссылок с отложенным подсчетом циклических ссылок, что обеспечивает удаление большинства объектов, на которые никто не ссылается… но не гарантирует удаление всех подобных объектов. Трассировака, отладка и перехват исключений с помощью try... except могут приводить к сохранению ссылок на объекты. Всегда следует явно закрывать открытые файлы через close() (если предоставлен), try... finally или менеджеры контекста with...

Иерархия типов

None

Используется для обозначения отсутствия значения

NotImplemented

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

Ellipsis многоточие ...

numbers.Number

Все числовые типы неизменяемы

Sequences

Конечные упорядоченные наборы объектов, индексированные неотрицательными числами. Последовательность имеет длину, поддерживает операцию нарезки (в т.ч. расширенную, с шагом).

Sets

Конечные неупорядоченные наборы уникальных неизменяемых объектов. Не индексируются. Имеют длину.

Mappings (отображения)

Конечные наборы объектов, индексированные произвольным набором индексов. Имеют длину.

Пример с использованием двух чисел, имеющих одинаковый хеш, провоцирующий неожиданное поведение.

>>> d = {1: 100, 1.0: 200}
>>> print(d)

{1: 200}

Calable tipes

Типы, к которым можно применить операцию вызова. Это пользовательские функции, методы встроенных классов, объекты классов, методы экземпляров классов и все объекты, реализующие метод __call__().

Аргументы вызываемых типов оцениваются до вызова следующим образом: первым делом создается список незаполненных слотов для параметров. Если имеется N позиционных аргументов, они помещаются в первые N слотов. Если присутствуют ключевые аргументы, они преобразуются в позиционные аргументы. Для каждого ключевого аргумента его идентификатор используется для определения соответствующего слота (если идентификатор совпадает с именем первого формального параметра, используется первый слот и т. д.). Если слот уже заполнен, возникает исключение TypeError. В противном случае значение аргумента помещается в слот, заполняя его. Когда все аргументы обработаны, пустые слоты заполняются соответствующим значением по умолчанию из определения функции. Значения по умолчанию вычисляются один раз, когда функция определена; таким образом, изменяемые объекты, используемые в качестве значений по умолчанию, будут использованы всеми вызовами, что может привести к нежелательному поведению. Если есть какие-либо незаполненные слоты, для которых не указано значение по умолчанию, возникает исключение TypeError. В противном случае список заполненных слотов используется в качестве списка аргументов для вызова.

Попытка переопределить уже заполненный слот

>>> def my(a, b, c=4):
...     print(a + b + c)
    
>>> my(1, 2, b=3)
Traceback (most recent call last):
  File "<string>", line 4, in <module>
TypeError: my() got multiple values for argument 'b'

Использование изменяемого объекта при повторных вызовах

>>> def my(a, b=[]):
...     print(b)
...     b.append(a)
...     print(b)
    
>>> my(1)
[]
[1]
>>> my(2)
[1]
[1, 2]

Основные типы вызываемых объектов:

Атрибут Значение  
__doc__ Строка документации функции или, None если она недоступна; не наследуется подклассами запись/чтение
__name__ Имя функции запись/чтение
__qualname__ Квалифицированное имя - включающее путь от глобальной области видимости запись/чтение
__module__ Имя модуля, в котором функция была определена, или None если он недоступен запись/чтение
__defaults__ Кортеж, содержащий значения аргументов по умолчанию для тех аргументов, которые имеют значения по умолчанию, или None если аргументы не имеют значения по умолчанию запись/чтение
__code__ Объект кода, представляющий тело скомпилированной функции запись/чтение
__globals__ Ссылка на словарь, содержащий глобальные переменные функции - глобальное пространство имен модуля, в котором функция была определена только чтение
__dict__ Пространство имен, поддерживающее атрибуты функции запись/чтение
__closure__ None или кортеж, содержащий привязки замыканий только чтение
__annotations__ Словарь с аннотациями параметров. Ключи - это имена параметров и ‘return’ для аннотации возврата, если она есть запись/чтение
__kwdefaults__ Словарь, содержащий значения по умолчанию для ключевых параметров. Если мы не определим * между позиционными и ключевыми параметрами, то вместо словаря будет None… а если определим, то None будет в __defaults__ запись/чтение

Пример

>>> z = 3
>>> def my(a: int, *, b: int = 3) -> int:
...     """This is doc"""
...     def this():
...         return a + b + z
...     return this
    
>>> print(my.__doc__)\
This is doc  
>>> print(my.__name__)
my
>>> print(my.__qualname__)
my
>>> print(my.__module__)
__main__
>>> print(my.__defaults__)
None
>>> print(my.__code__)
<code object my at 0x7fae9a4883a0, file "<string>", line 2>
>>> print(my.__globals__)
{'__name__': '__main__','__doc__': None, '__package__': None, 
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, 
'__spec__': None, '__annotations__': {}, 
'__builtins__': <module 'builtins' (built-in)>, 'z': 3, 
'my': <function my at 0x7fee736c9d30>}
>>> my.n = 4
>>> print(my.__dict__)
{'n': 4}
>>> print(my.__closure__)
None
>>> print(my.__annotations__)
{'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}
>>> print(my.__kwdefaults__)
{'b': 3}

>>> f = my(1)
>>> print(f.__closure__)
(<cell at 0x7fee738214c0: int object at 0x955e40>,
 <cell at 0x7fee7372cd90: int object at 0x955e80>)

Специальные атрибуты только для чтения: __self__ это объект экземпляра класса, __func__ это объект функции; __doc__ документация метода (то же, что и __func__.__doc__); __name__ это имя метода (то же, что и __func__.__name__); __module__- это имя модуля, в котором был определен метод, или None если он недоступен.

Пример

>>> class My():
    
...     def my(self):
...         """This is the doc"""
...         pass

>>> m = My()

>>> print(m.my.__self__)
<__main__.My object at 0x7fb701ba44c0>
>>> print(m.my.__doc__)
This is the doc
>>> print(m.my.__func__)
<function My.my at 0x7fb701a4cdc0>
>>> print(m.my.__name__)
my
>>> print(m.my.__module__)
__main__

Объект создается при получении атрибута класса. если этот атрибут является функцией или методом класса.

Когда объект метода экземпляра создается путем извлечения функции из экземпляра класса __self__ является экземпляром, а объект метода называется связанным (bound method). __func__ - это исходный объект функции.

Когда объект метода экземпляра создается путем извлечения объекта метода класса из класса, его __self__ атрибутом является сам класс, а его __func__ атрибутом является объект функции, лежащий в основе метода класса.

>>> class My:
...     def foo(self):
...         pass

>>> def this():
...     pass

>>> def that():
...     pass

>>> My.this = this
>>> m = My()
>>> m.that = that

>>> if __name__ == '__main__':
...     print(this)
<function this at 0x7fdc845c9a60>
...     print(My.this)
<function this at 0x7fdc845c9a60>
...     print(m.this)
<bound method this of <__main__.My object at 0x7fdc84698400>>
...     print(m.this.__func__)
<function this at 0x7fdc845c9a60>
...     print(m.this.__self__)
<__main__.My object at 0x7fdc84698400>
...     print(m.that)
<function that at 0x7fdc845c9af0>

Преобразование объекта функции в объект метода экземпляра происходит каждый раз, когда атрибут извлекается из экземпляра. В некоторых случаях хорошей оптимизацией является присвоение атрибута локальной переменной и вызов этой локальной переменной. Это преобразование происходит только для пользовательских функций; другие вызываемые объекты (и все невызываемые объекты) извлекаются без преобразования. Определяемые пользователем функции, которые являются атрибутами экземпляра класса, не преобразуются в связанные методы; это происходит только тогда, когда функция является атрибутом класса.

Пример yield и return

>>> def that():
...     this = [1, 2, 3, 4]
...     for i in this:
...         if i == 3:
...             return 'this is madness'
...         else:
...             yield i

>>> t = that()

>>> print(t)
<generator object that at 0x7f8468a1f580>
>>> print(next(t))
1
>>> print(next(t))
1

>>> try:
...     print(next(t))
>>> except StopIteration as s:
...     print(s)
this is madness

>>> print(list(t))
[]

Пример с подъемом ошибки

>>> def that():
...     this = [1, 2, 3, 4]
...     for i in this:
...         try:
...             raise StopIteration
...         except:
...             yield i

>>> t = that()

>>> print(t)
<generator object that at 0x7fa53bbb1580>
>>> for i in range(5):
...     print(next(t))
1
2
3
4
Traceback (most recent call last):
  File "<string>", line 16, in <module>
StopIteration

Modules

Объект модуля имеет пространство имен, реализованное объектом словаря, на котоырй будут ссылаться все функции модуля через свой атрибут __globals__. Ссылки на атрибуты модуля транслируются в поиск по данному словарю. Подробнее о пространствах имен [python-namespaces].

Дефолтно определено и доступно для записи:

В CPython словарь модуля будет очищен, когда модуль уйдет из области видимости функции, даже если в словаре все еще есть активные ссылки. Можно скопировать словарь или держать ссылку на модуль в пространстве видимости модуля.

Custom classes

Типы пользовательских классов создаются определениями классов. У класса есть пространство имен, реализованное объектом словаря. Ссылки на атрибуты классов переводятся в поисковые запросы в этом словаре. Если имя атрибута там не найдено, поиск атрибута продолжается в родительских классах используя специальную схему разрешения поиска MRO.

Когда ссылка на атрибут класса приводит к объекту метода класса, он преобразуется в объект метода экземпляра, __self__ атрибут которого принимает сам класс. Если это статический метод, то происходит преобразование в объект статического метода.

Назначения атрибутов класса обновляют словарь текущего класса, а не словарь родительского класса.

Особые атрибуты:

Class instances

Экземпляр класса имеет пространство имен, реализованное как словарь. При поиске ссылок на атрибуты этот словарь опрашивается в первую очередь. Если атрибут не найден, а класс экземпляра имеет атрибут с таким именем, поиск продолжается уже в словаре самого класса. Если обнаружен атрибут класса, который является объектом определяемой пользователем функции, он преобразуется в объект метода экземпляра (см.выше), __self__ атрибут которого является экземпляром. Также преобразуются объекты статических методов и методов класса. Если атрибут класса не найден, а у класса объекта есть __getattr__() метод, для поиска вызывается данный метод.

Назначение и удаление атрибутов обновляют словарь экземпляра, но не словарь класса. Если в классе есть метод __setattr__() или __delattr__() - они вызываются вместо непосредственного обновления словаря экземпляра.

Экземпляры классов могут выполнять роль чисел, последовательностей или отображений, если у них есть методы с определенными специальными именами. Смотри список методов

Специальные атрибуты: __dict__ словарь атрибутов; __class__ это класс экземпляра.

I/O objects - объекты ввода/вывода

Представляют из мебя открытые файлы.

Internal types

Некоторые типы, используемые внутри интерпретатора, доступны пользователю. Их определения могут измениться в будущих версиях интерпретатора

Специальные методы

Класс может реализовывать определенные операции, которые вызываются специальным синтаксисом (например, арифметические операции или индексирование и разрезание), определяя методы со специальными именами. Это позволяет классам определять собственное поведение по отношению к операторам языка.

Установка специального метода в значение None указывает, что соответствующая операция недоступна. Например, если для класса установлено __iter__() значение None, итерация этого класса невозможна, поэтому вызов iter() его экземпляров вызовет TypeError (без возврата к __getitem__()).

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

Базовые спец.методы

Доступ к атрибутам

Читай про доступ к атрибутам модуля

Методы применяются только тогда, когда экземпляр класса, содержащего метод (класс дескриптора), появляется в классе владельца owner (дескриптор должен находиться либо в словаре класса владельца, либо в словаре классов для одного из его родителей)

Дескрипторы

Дескриптор является атрибутом объекта с «связыванным поведением», один атрибут доступа которого был изменен с помощью методов в протоколе дескриптора: __get__(), __set__() и __delete__(). Если какой-либо из этих методов определен для объекта, он называется дескриптором. Поведение по умолчанию для доступа к атрибуту заключается в получении, установке или удалении атрибута из словаря объекта.

Поведение по умолчанию для доступа к атрибуту заключается в получении, установке или удалении атрибута из словаря объекта. Если искомое в словаре значение является объектом, определяющим один из методов дескриптора, тогда Python может переопределить поведение по умолчанию и вместо этого вызвать метод дескриптора. То, где это происходит в цепочке приоритетов, зависит от того, какие методы дескриптора были определены и как они были вызваны.

Методы Python (в том числе отмеченные символами @staticmethod и @classmethod) реализованы как дескрипторы, не являющиеся дескрипторами данных. Соответственно, экземпляры могут переопределять методы. Это позволяет отдельным экземплярам приобретать поведение, которое отличается от поведения других экземпляров того же класса.

Функция property() реализована в виде дескриптора данных. Соответственно, экземпляры не могут переопределить поведение свойства.

Подробнее читай тут [python-descriptors]

slots

__slots__ позволяют явно объявлять данные класса (например, свойства) и запрещать создание __dict__ и __weakref__. Это гарантирует экономию ресурсов при частых вызовах.

Data model

Настройки при создании класса и метаклассы

Эмуляция универсальных типов

При использовании аннотации типа, часто бывает полезно использовать параметризацию универсального типа с помощью квадратных скобок. Класс можно параметризовать, только если он определяет специальный метод класса __class_getitem__()

Смотри [typing]

Эмуляция вызываемых объектов

__call__ вызывается, когда инстанс вызывается как функция, если этот метод определен

Эмуляция поведения контейнеров

Эта группа методов может быть определена для реализации контейнерных объектов.

Контейнеры обычно представляют собой последовательности (например, lists или tuples) или отображения (например dictionaries), но могут также представлять другие контейнеры.

Первый набор методов используется либо для имитации последовательности, либо для имитации отображения; разница в том , что для последовательности, допустимые ключи должны быть целыми числами к, для которых 0 <= k < N, где N представляет собой длину последовательности, или slice объектов, которые определяют диапазон элементов.

Рекомендуется, чтобы отображения обеспечивали методы keys(), values(), items(), get(), clear(), setdefault(), pop(), popitem(), copy(), и update() что обкспечивает поведение, подобное словарям. collections.abc обеспечивает абстрактный базовый класс.

Изменяемые последовательности должны обеспечивать методы append(), count(), index(), extend(), insert(), pop(), remove(), reverse() и sort() аналогично python спискам.

Последовательности так-же должны реализовывать __add__(), __radd__(), __iadd__(), __mul__(), __rmul__() и __imul__() методы и другие численные операции.

Рекомендуется реализовать __contains__() как для отображений так и для последовательностей, чтобы использовать in оператор, а так-же __iter__() для итерации.

Дополнительный список методов смотри тут

Контекстные менеджеры

Менеджер контекста является объектом, который определяет контекст выполнения. Диспетчер контекста обрабатывает вход и выход из желаемого контекста для выполнения блока кода. Менеджеры контекста обычно вызываются с помощью with оператора, но также могут использоваться путем прямого вызова их методов.

Типичные применения диспетчеров контекста включают сохранение и восстановление различных видов глобального состояния, блокировку и разблокировку ресурсов, закрытие открытых файлов и т.д.

Pattern matching

Сопрограммы

Реализация __await__ делает объект awaitable.

Объекты сопрограмм - это awaitable объекты. Выполнением сопрограммы можно управлять путем вызова__await__() и повторения результата. Когда сопрограмма завершает выполнение и возвращает управление, итератор поднимает StopIteration, а значение атрибута value ошибки содержит возвращаемое значение. Если сопрограмма вызывает исключение, оно распространяется итератором. Сопрограммы не должны напрямую вызывать необработанные StopIteration.

У сопрограмм также есть методы, которые аналогичны методам генераторов. Подробнее о методах

Смотри подробнее [asyncio]

Асинхронные итераторы

Асинхронный итератор может вызвать асинхронный код в __anext__ методе

Пример

class Reader:
    async def readline(self):
        ...

    def __aiter__(self):
        return self

    async def __anext__(self):
        val = await self.readline()
        if val == b'':
            raise StopAsyncIteration
        return val

Асинхронные менеджеры контекста

Асинхронный менеджер контекста - это контекстный менеджер, способный приостанавливать выполнение. Используется с оператором await width

Пример

class AsyncContextManager:
    async def __aenter__(self):
        await log('entering context')

    async def __aexit__(self, exc_type, exc, tb):
        await log('exiting context')

Смотри еще:

>>> На главную