Иерархия встроенных структур данных и практические проверки
Структура типов данных в 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