try...except с последовательными блоками except, располагая их в порядке от наиболее специфичного к наиболее общему исключению. Это позволяет обработать конкретные типы исключений, а более общие будут обработаны в соответствующих блоках except. Например:
  
try:
  # Код, который может вызвать исключение
  raise SpecificException()
except SpecificException as e:
  # Обработка специфичного исключения
  print(f"Специфичное исключение: {e}")
except GeneralException as e:
  # Обработка более общего исключения (родительского класса SpecificException)
  print(f"Общее исключение: {e}")
except Exception as e:
  # Обработка всех остальных исключений
  print(f"Неизвестное исключение: {e}")
  SpecificException наследуется от GeneralException (или другого базового класса).  Важно помнить, что except Exception следует размещать в самом конце, чтобы не перехватить специфичные исключения раньше времени.
Обработка нескольких собственных исключений с иерархией классов включает в себя создание базового класса исключения, от которого наследуются более специфичные исключения. При обработке необходимо учитывать порядок блоков except, чтобы не перехватить исключение более общим обработчиком, чем требуется.
Пример:
class CustomError(Exception):
    """Базовый класс для всех кастомных исключений"""
    pass
class ValidationError(CustomError):
    """Исключение для ошибок валидации данных"""
    pass
class InvalidEmailError(ValidationError):
    """Исключение для невалидного формата email"""
    pass
class DatabaseError(CustomError):
    """Исключение для ошибок работы с базой данных"""
    pass
class ConnectionError(DatabaseError):
    """Исключение для ошибок соединения с базой данных"""
    pass
def process_data(email):
    try:
        if "@" not in email:
            raise InvalidEmailError("Некорректный формат email")
        # ... другая логика ...
    except InvalidEmailError as e:
        print(f"Ошибка валидации email: {e}")
        # Обработка конкретной ошибки валидации email
    except ValidationError as e:
        print(f"Общая ошибка валидации: {e}")
        # Обработка всех ошибок валидации (включая InvalidEmailError)
    except CustomError as e:
        print(f"Общая ошибка: {e}")
        # Обработка всех кастомных ошибок (включая ValidationError и InvalidEmailError)
    except Exception as e:
        print(f"Непредвиденная ошибка: {e}")
        # Обработка всех остальных исключений
    else:
        print("Данные обработаны успешно")
    finally:
        print("Блок finally: выполняется всегда")
# Пример использования
process_data("invalid-email")
process_data("valid@email.com")
Ключевые моменты:
except должны быть упорядочены от более специфичных к более общим.  Если сначала указать except CustomError, то except InvalidEmailError никогда не будет выполнен, так как InvalidEmailError является подклассом CustomError.CustomError) для обработки целой группы связанных ошибок.else блок:  Блок else выполняется, если в блоке try не возникло ни одного исключения.  Это хорошее место для кода, который должен выполняться только при успешном выполнении.finally блок: Блок finally выполняется всегда, независимо от того, было ли выброшено исключение или нет.  Он полезен для освобождения ресурсов (например, закрытия файлов или соединений с базой данных).Exception или его подклассов.Альтернативный подход (более лаконичный, при необходимости выполнять одинаковые действия для нескольких исключений):
try:
    # ... код, который может вызвать исключение ...
except (InvalidEmailError, ConnectionError) as e:
    print(f"Ошибка при работе с данными: {e}")
    # Обработка InvalidEmailError или ConnectionError
except CustomError as e:
    print(f"Общая кастомная ошибка: {e}")
В этом случае, если возникнет либо InvalidEmailError, либо ConnectionError, будет выполнен один и тот же блок кода.