Python сравнение типов: от isinstance до утиной типизации
Сравнение типов в Python: обзор подходов и примеры
В Python существует несколько способов проверить или сравнить типы данных. Выбор подхода зависит от цели: нужна ли строгая проверка на точный класс, или достаточно проверить, поддерживает ли объект нужный интерфейс (утиная типизация). Рассмотрим основные варианты с примерами кода и типичными проблемами.
Как эффективно проверить, является ли объект экземпляром одного из нескольких классов?
isinstance() с кортежем типов - это идиоматический и рекомендуемый способ. Он учитывает наследование: возвращает True, если объект является экземпляром любого из перечисленных классов или их подклассов.
value = 42
if isinstance(value, (int, float)):
print('Числовой тип') # Вывод: Числовой типPython сравнение типов (сравнение типов в python)
Проблема: если порядок в кортеже не важен, но список типов может быть любым. Ошибка: забыть скобки, передав несколько аргументов - isinstance(value, int, float) вызовет TypeError.
type(value) == int для проверки точного типа без учёта подклассов. Например, True имеет тип bool, который является подклассом int. isinstance(True, int) вернёт True, а type(True) == int - False. Решение: применять isinstance для полиморфной проверки и type только когда нужно точное совпадение.Как сравнить тип с конкретным классом без учёта наследования?
Используйте type() == SomeClass или type(obj) is SomeClass. Это игнорирует подклассы, что может быть полезно, если нужно убедиться, что передан именно базовый класс, а не его наследник.
class A: pass
class B(A): pass
obj = B()
print(type(obj) == A) # False
print(type(obj) is B) # Trueусловие на тип данных python (условие на тип данных в python)
Проблема: при использовании type(obj) == A для объектов, созданных динамически через type('A', (), {}), результат может быть неожиданным. Ошибка: сравнение с type для встроенных типов (например, type(10) == int) работает корректно, но не проверяет наследование.
Как проверить, является ли объект экземпляром именно того класса, которым он был создан?
Можно использовать obj.__class__ - атрибут, указывающий на класс объекта. Сравнение через obj.__class__ is cls аналогично type(obj) is cls.
class Animal: pass
class Dog(Animal): pass
d = Dog()
print(d.__class__ is Dog) # True
print(d.__class__ is Animal) # FalseЭтот метод редко используют напрямую; обычно предпочитают isinstance или type(). Проблема: для классов с метаклассами __class__ может быть переопределён.
Как проверить, соответствует ли объект определённому интерфейсу (утиная типизация)?
Вместо сравнения типов часто используют проверку наличия методов или атрибутов (например, hasattr(obj, '__iter__')). Это соответствует философии Python «duck typing».
def process(obj):
if hasattr(obj, 'read'):
return obj.read()
else:
raise TypeError('Объект не поддерживает read()')Цель: сделать код гибким и не привязанным к конкретной иерархии классов. Проблема: hasattr может возвращать True для методов, которые не предназначены для вызова, или ловить исключения.
Как проверить, является ли объект наследником абстрактного базового класса (ABC)?
Модуль abc и метод isinstance корректно обрабатывают абстрактные базовые классы, зарегистрированные через register. Например, isinstance([], collections.abc.Iterable) вернёт True, даже если list не наследует от Iterable напрямую.
from collections.abc import Iterable
print(isinstance([1,2,3], Iterable)) # TrueЦель: использовать возможности абстрактных базовых классов для полиморфизма без жёсткой привязки.
Как сравнить типы в условном выражении с учётом нескольких вариантов?
Можно объединить isinstance с кортежем, как в rbase. Альтернатива - цепочка type() == с оператором or, но это менее читаемо.
x = 'hello'
if type(x) == str or type(x) == bytes:
print('Строкоподобный тип')Проблема: не учитывает подклассы str или bytes. Для полного охвата лучше isinstance(x, (str, bytes)).
Расширенные примеры сравнения типов в Python
Пример 1. Проверка типа при обработке данных из внешнего источника с учётом возможных подтипов.
def safe_divide(a, b):
if not isinstance(a, (int, float)):
raise TypeError('Первый аргумент должен быть числом')
if not isinstance(b, (int, float)):
raise TypeError('Второй аргумент должен быть числом')
if b == 0:
return float('inf')
return a / b
print(safe_divide(10, 3)) # 3.333...
print(safe_divide(10.0, 2)) # 5.0
# print(safe_divide('10', 2)) # TypeErrorПример 2. Использование type() для точного сравнения, когда подклассы должны быть отвергнуты (например, сериализация).
def serialize(value):
if type(value) is int:
return str(value)
elif type(value) is float:
return f'{value:.2f}'
else:
raise ValueError('Неподдерживаемый тип')
print(serialize(42)) # '42'
print(serialize(True)) # ValueError, так как type(True) is bool, а не intПример 3. Проверка типа с помощью isinstance и ABC для создания универсальной функции.
from collections.abc import Hashable
def get_hash(obj):
if isinstance(obj, Hashable):
return hash(obj)
else:
raise TypeError('Объект не хешируем')
print(get_hash('строка')) # хэш
print(get_hash([1,2])) # TypeErrorПример 4. Динамическая проверка типа через __class__ для реализации кастомного копирования.
class MyClass:
def __init__(self, value):
self.value = value
def copy(self):
return self.__class__(self.value)
obj = MyClass(10)
copy_obj = obj.copy()
print(type(copy_obj) is MyClass) # TrueПример 5. Сравнение типов с использованием модуля typing для аннотаций и проверки во время выполнения (через isinstance с помощью typing.get_type_hints).
from typing import get_type_hints
def validate(func, *args):
hints = get_type_hints(func)
for arg_name, arg_val in zip(func.__code__.co_varnames, args):
expected_type = hints.get(arg_name)
if expected_type and not isinstance(arg_val, expected_type):
raise TypeError(f'{arg_name} ожидается {expected_type}, получен {type(arg_val)}')
def example(a: int, b: str):
pass
validate(example, 10, 'test') # OK
# validate(example, '10', 'test') # TypeErrorПример 6. Проверка типа с учётом Union из typing. isinstance не поддерживает Union напрямую, поэтому нужно разворачивать.
from typing import Union
def check_union(value):
types = (int, float) # Union[int, float]
if isinstance(value, types):
return 'число'
else:
return 'другое'
print(check_union(3.14)) # число
print(check_union('abc')) # другое