Иерархия встроенных структур данных и практические проверки

Раздел: Прочее -> Коллекции

Структура типов данных в Python

Основной взгляд на структуру типов в Python опирается на модель данных: каждый объект имеет идентичность, тип и значение. Тип задает операции, наследование и протоколы. Для коллекций полезны три оси классификации: изменяемость, поддержка индексации и хешируемость. Модуль collections.abc формализует многие интерфейсы: Sequence, Mapping, Set, MutableSequence, MutableMapping, MutableSet, Iterable, Container, Sized, Hashable.

from collections import abc

samples = [1, 'a', 3.5, [1, 2], (1, 2), {1, 2}, {'a': 1}, frozenset({1}), b'abc', bytearray(b'abc'), range(3), None]
for x in samples:
    print(type(x).__name__, isinstance(x, abc.Sequence), isinstance(x, abc.Mapping), isinstance(x, abc.Set), isinstance(x, abc.MutableSequence))

программы с массивами на python (программы с массивами на python)

int False False False False
a str True False False False
float False False False False
list True False False True
tuple True False False False
set False False True False
dict False True False False
frozenset False False True False
bytes True False False False
bytearray True False False True
range True False False False
NoneType False False False False

Python пар (пары (ключ-значение) в python)

Практический вывод: isinstance с ABC показывает роль объекта в коллекции, а не только его конкретный класс. Такой подход помогает писать функции, которые принимают tuple, list, range, deque и пользовательские последовательности.

from collections.abc import Mapping

def show_keys(data: Mapping):
    for key in data:
        print(key, '->', data[key])

show_keys({'name': 'Ada', 'year': 1815})

Python разница списков (разница между списками и кортежами в python)

name -> Ada
year -> 1815

списки в языке python (списки в python)

Как увидеть наследование встроенных типов через __mro__?

Вариант с __mro__ подходит для анализа того, как Python ищет атрибуты и методы. Например, bool наследуется от int, а type наследуется от object.

print(bool.__mro__)
print(int.__mro__)
print(type.__mro__)
print(issubclass(bool, int))
print(isinstance(True, int))

значения списка числа python (итерация по значениям списка чисел в python)

(<class 'bool'>, <class 'int'>, <class 'object'>)
(<class 'int'>, <class 'object'>)
(<class 'type'>, <class 'object'>)
True
True

множества set python (множества set в python)

Такой способ удобен для отладки наследования. Ограничение: __mro__ не показывает виртуальное наследование от ABC.

Как проверить интерфейс коллекции без прямого наследования?

Второй вариант использует collections.abc и виртуальные подклассы. Многие встроенные типы зарегистрированы как подклассы абстрактных базовых классов, даже если в __mro__ их нет.

from collections import abc

checks = [
    ([], abc.Sequence),
    ((), abc.Sequence),
    ({}, abc.MutableMapping),
    (set(), abc.MutableSet),
    (frozenset(), abc.Set),
]
for obj, abc_class in checks:
    print(type(obj).__name__, abc_class.__name__, isinstance(obj, abc_class))

print(issubclass(list, abc.MutableSequence))
print(issubclass(dict, abc.MutableMapping))
print(issubclass(str, abc.Sequence))

структура типов данных python (структура типов данных в python)

list Sequence True
tuple Sequence True
dict MutableMapping True
set MutableSet True
frozenset Set True
True
True
True

Как разделить объекты по изменяемости и хешируемости?

Третий вариант проверяет хешируемость через hash. Это важно для ключей dict и элементов set. Изменяемые объекты обычно нехешируемы, но есть исключения: пользовательский класс может реализовать __hash__ даже при наличии изменяемых полей.

items = [([1], 'list'), ((1, 2), 'tuple'), ({'a': 1}, 'dict'), (frozenset({1}), 'frozenset'), (bytearray(b'x'), 'bytearray')]
for obj, name in items:
    try:
        hash(obj)
        print(name, 'hashable')
    except TypeError as error:
        print(name, 'unhashable', error)
list unhashable unhashable type: 'list'
tuple hashable
dict unhashable unhashable type: 'dict'
frozenset hashable
bytearray unhashable unhashable type: 'bytearray'

Типичные проблемы при работе со структурой типов:

  • Проверка type(obj) == list не принимает подклассы list. Для интерфейсов лучше использовать isinstance(obj, abc.Sequence).
  • Ключ dict должен быть хешируемым. tuple с list внутри не может быть ключом, потому что хеш зависит от всех элементов.
  • Изменение объекта после добавления в set или dict нарушает поиск, если хеш зависит от изменяемых полей.
  • bool является подклассом int, поэтому True равен 1 в арифметических и словарных контекстах.
  • ABC не всегда видны в __mro__, поэтому проверка через issubclass надежнее для интерфейсов.

Расширенные примеры классификации типов и коллекций

Карта встроенных и модульных коллекций

Пример показывает, как разные объекты соотносятся с ABC из collections.abc.

Пример
from collections import abc
from collections import deque, Counter, OrderedDict, defaultdict, ChainMap, UserDict, UserList, UserString
from array import array
from types import MappingProxyType

objects = {
    'list': [],
    'tuple': (),
    'dict': {},
    'set': set(),
    'frozenset': frozenset(),
    'str': '',
    'bytes': b'',
    'range': range(3),
    'deque': deque(),
    'Counter': Counter(),
    'OrderedDict': OrderedDict(),
    'defaultdict': defaultdict(int),
    'ChainMap': ChainMap({}, {}),
    'UserDict': UserDict(),
    'UserList': UserList(),
    'UserString': UserString(),
    'array': array('i'),
    'MappingProxyType': MappingProxyType({}),
}

for name, obj in objects.items():
    flags = [
        abc.Sequence.__name__ if isinstance(obj, abc.Sequence) else '',
        abc.Mapping.__name__ if isinstance(obj, abc.Mapping) else '',
        abc.Set.__name__ if isinstance(obj, abc.Set) else '',
        abc.MutableSequence.__name__ if isinstance(obj, abc.MutableSequence) else '',
        abc.MutableMapping.__name__ if isinstance(obj, abc.MutableMapping) else '',
        abc.MutableSet.__name__ if isinstance(obj, abc.MutableSet) else '',
    ]
    print(name, '|', ' '.join(flag for flag in flags if flag))
list | Sequence MutableSequence
tuple | Sequence
dict | Mapping MutableMapping
set | Set MutableSet
frozenset | Set
str | Sequence
bytes | Sequence
range | Sequence
deque | Sequence MutableSequence
Counter | Mapping MutableMapping
OrderedDict | Mapping MutableMapping
defaultdict | Mapping MutableMapping
ChainMap | Mapping MutableMapping
UserDict | Mapping MutableMapping
UserList | Sequence MutableSequence
UserString | Sequence
array | Sequence MutableSequence
MappingProxyType | Mapping

Собственная последовательность на базе Sequence

Класс ниже реализует только __len__ и __getitem__, а ABC добавляет count, index, __contains__, __iter__ и другие методы.

Пример
from collections.abc import Sequence

class Vector(Sequence):
    def __init__(self, data):
        self._data = list(data)

    def __getitem__(self, index):
        return self._data[index]

    def __len__(self):
        return len(self._data)

v = Vector([10, 20, 30])
print(v[1])
print(len(v))
print(list(v))
print(v.count(20))
print(v.index(30))
print(20 in v)
20
3
[10, 20, 30]
1
2
True

Словарь с нормализацией ключей через UserDict

UserDict удобен для наследования отображений без сложных взаимодействий с dict.

Пример
from collections import UserDict

class InsensitiveDict(UserDict):
    def __setitem__(self, key, value):
        super().__setitem__(key.lower(), value)

    def __getitem__(self, key):
        return super().__getitem__(key.lower())

d = InsensitiveDict()
d['Name'] = 'Alice'
print(d['name'])
print(d['NAME'])
print(list(d.keys()))
Alice
Alice
['name']

NamedTuple и dataclass: разные структуры записей

namedtuple создает неизменяемый подкласс tuple. dataclass создает обычный класс с полями и сгенерированными методами.

Пример
from collections import namedtuple
from dataclasses import dataclass

Point = namedtuple('Point', 'x y')
p = Point(1, 2)
print(p, p.x, p.y, isinstance(p, tuple))

@dataclass
class Dot:
    x: int
    y: int

d = Dot(1, 2)
print(d, d.x, d.y, isinstance(d, tuple))
Point(x=1, y=2) 1 2 True
Dot(x=1, y=2) 1 2 False

Числовая башня и ABC numbers

Модуль numbers описывает числовые протоколы. bool наследуется от int, а int регистрируется как Integral.

Пример
import numbers

for value in [True, 1, 1.5, 1 + 2j]:
    print(
        type(value).__name__,
        isinstance(value, numbers.Integral),
        isinstance(value, numbers.Real),
        isinstance(value, numbers.Complex),
    )
bool True True True
int True True True
float False True True
complex False False True

Сопоставление с образцом по типам

Конструкция match позволяет выбирать ветку по классу объекта. Для подклассов срабатывает проверка isinstance.

Пример
def classify(obj):
    match obj:
        case list():
            return 'list'
        case tuple():
            return 'tuple'
        case dict():
            return 'dict'
        case set():
            return 'set'
        case str():
            return 'str'
        case _:
            return 'other'

for item in [[], (), {}, set(), 'abc', 1]:
    print(type(item).__name__, classify(item))
list list
tuple tuple
dict dict
set set
str str
int other

Хеширование составных ключей

Ключ словаря может быть составным, если все его части хешируемы. frozenset внутри tuple сохраняет неизменяемость.

Пример
key = (1, 2, frozenset({'a', 'b'}))
d = {key: 'ok'}
print(d[key])

try:
    bad = {([1], 2): 'bad'}
except TypeError as error:
    print(type(error).__name__, error)
ok
TypeError unhashable type: 'list'

__slots__ и структура экземпляра

Классы с __slots__ не создают __dict__ для каждого экземпляра. Это меняет структуру хранения атрибутов.

Пример
class A:
    def __init__(self):
        self.x = 1

class B:
    __slots__ = ('x',)
    def __init__(self):
        self.x = 1

a = A()
b = B()
print(hasattr(a, '__dict__'))
print(hasattr(b, '__dict__'))
print(b.x)
True
False
1

ChainMap и поиск по нескольким словарям

ChainMap объединяет словари в представление без копирования. Изменения затрагивают первый словарь.

Пример
from collections import ChainMap

defaults = {'theme': 'light', 'lang': 'ru'}
user = {'theme': 'dark'}
config = ChainMap(user, defaults)
print(config['theme'], config['lang'])
config['lang'] = 'en'
print(user)
print(defaults)
dark ru
{'theme': 'dark', 'lang': 'en'}
{'theme': 'light', 'lang': 'ru'}

MappingProxyType и защита от изменений

MappingProxyType создает только читаемое представление словаря. Исходный словарь может меняться, а прокси отражает изменения.

Пример
from types import MappingProxyType

base = {'a': 1}
proxy = MappingProxyType(base)
print(proxy['a'])
base['b'] = 2
print(proxy['b'])

try:
    proxy['c'] = 3
except TypeError as error:
    print(type(error).__name__, error)
1
2
TypeError 'mappingproxy' object does not support item assignment

Protocol и структурная типизация

Protocol описывает интерфейс по набору методов. runtime_checkable разрешает проверку isinstance по наличию методов.

Пример
from typing import Protocol, runtime_checkable

@runtime_checkable
class SizedLike(Protocol):
    def __len__(self) -> int:
        ...

print(isinstance([], SizedLike))
print(isinstance('abc', SizedLike))
print(isinstance(1, SizedLike))
True
True
False

Ссылки, копии и изменяемость

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

Пример
a = [1, [2, 3]]
b = a
c = a.copy()

a.append(4)
a[1].append(5)
print(a)
print(b)
print(c)
print(a is b)
print(a is c)
[1, [2, 3, 5], 4]
[1, [2, 3, 5], 4]
[1, [2, 3, 5]]
True
False

Виртуальное наследование и регистрация

ABC допускает регистрацию класса как виртуального подкласса без изменения его MRO.

Пример
from collections.abc import Sequence

class MyContainer:
    def __getitem__(self, index):
        return index

Sequence.register(MyContainer)
print(issubclass(MyContainer, Sequence))
print(MyContainer.__mro__)
True
(<class '__main__.MyContainer'>, <class 'object'>)

Сравнение tuple, list и deque

Структура коллекции влияет на операции. deque оптимизирован для добавления и удаления с обоих концов.

Пример
from collections import deque

lst = [1, 2, 3]
tpl = (1, 2, 3)
dq = deque([1, 2, 3])
dq.appendleft(0)
dq.append(4)
print(lst[1], tpl[1], dq[0], dq[-1])
print(len(lst), len(tpl), len(dq))
2 2 0 4
3 3 5

Структура типов данных в Python - comments

En
структура типов данных python (python)