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'))  # другое

Сравнение типов в Python - comments

En
Python сравнение типов (python)