Использование typing для строковых типов данных
Основной подход: аннотация строк с помощью встроенного типа str
В большинстве случаев для указания строкового типа аргумента или возвращаемого значения достаточно использовать встроенный тип str. Это простой, понятный и поддерживаемый всеми средствами проверки типов (mypy, Pyright) способ.
def greet(name: str) -> str:
return f'Привет, {name}!'Python typing class (типизация классов в python)
Проблем здесь обычно не возникает. Единственная сложность - в старых проектах, где используется typing.Text (см. варианты ниже), но это уже не актуально для Python 3.11+.
Типичная ошибка
Некоторые разработчики ошибочно импортируют String из typing (такого типа нет) и получают ошибку импорта. Решение - использовать str напрямую.
Как сделать, чтобы аннотация строки учитывала регистр кодировки?
В Python 3 все строки - Unicode, поэтому отдельный тип не нужен. Однако для совместимости с Python 2 (где были байтовые строки и unicode) в модуль typing был добавлен тип Text. Он представляет собой str в Python 3 и unicode в Python 2.
from typing import Text
def process_text(data: Text) -> Text:
return data.upper()Python 3 typing (модуль typing в python 3)
Сейчас этот тип считается устаревшим, его использование не рекомендуется в новых проектах. Лучше просто писать str.
Проблема
Если в проекте используется Text, средства проверки типов могут выдавать предупреждения о его устаревании. Решение - заменить на str.
Как ограничить строковое значение только определёнными литералами?
Когда функция принимает только несколько допустимых строковых значений, удобно использовать Literal из модуля typing. Это позволяет статической проверке типов контролировать, что передаётся только разрешённая строка.
from typing import Literal
def set_mode(mode: Literal['read', 'write', 'append']) -> None:
print(f'Режим установлен: {mode}')
Typing object python (typing.object в python)
Передача 'delete' вызовет ошибку при проверке типа. Это особенно полезно в API, где есть ограниченный набор строковых констант.
Ошибка
Если в проекте используется Python < 3.8, тип Literal может быть недоступен. Решение - обновить версию Python или использовать typing_extensions.Literal.
Как аннотировать строку с дополнительными метаданными?
Иногда нужно указать, что строка должна соответствовать определённому формату (например, email, URL). Для этого можно использовать Annotated. Первый параметр - базовый тип, второй и далее - метаданные.
from typing import Annotated
Email = Annotated[str, 'должен быть валидным email']
def send_mail(address: Email) -> None:
print(f'Письмо отправлено на {address}')Python typing string (typing.string в python)
Такая аннотация не даёт автоматической проверки во время выполнения, но помогает документировать код и может быть использована в линтерах или фреймворках (например, Pydantic).
Проблема
Тип Annotated доступен с Python 3.9, в более старых версиях нужно использовать typing_extensions. Также метаданные не проверяются автоматически - это лишь подсказка разработчику.
Как создать новый тип на основе строки, чтобы различать сходные значения?
NewType позволяет создать отдельный тип, который во время выполнения ведёт себя как строка, но для статического анализа считается другим типом. Полезно для разграничения, например, идентификатора пользователя и имени пользователя.
from typing import NewType
UserId = NewType('UserId', str)
UserName = NewType('UserName', str)
def get_user(user_id: UserId) -> UserName:
return UserName('Иван')
Теперь передача простой строки в функцию, ожидающую UserId, вызовет предупреждение. Это помогает избежать путаницы между разными строковыми сущностями.
Ошибка
NewType не создаёт новый класс, поэтому при выполнении isinstance(user_id, UserId) будет True только для str. Это может сбивать с толку новичков. Нужно помнить, что проверка типов - статическая, а не динамическая.
Как аннотировать строку, которая является шаблоном регулярного выражения?
Для регулярных выражений существует тип Pattern из модуля typing. Он параметризуется типом строки (обычно str).
import re
from typing import Pattern
pattern: Pattern[str] = re.compile(r'\d+')
def find_numbers(text: str) -> list[str]:
return pattern.findall(text)
Тип Pattern может также быть Pattern[bytes] для байтовых шаблонов. Использование этой аннотации улучшает читаемость кода и позволяет статически проверять, что в функцию передаётся именно скомпилированное регулярное выражение.
Проблема
Тип Pattern доступен только для объектов, созданных re.compile(). Если передать строку, ожидающуюся как шаблон, тип не подойдёт. Нужно явно компилировать.
Расширенные примеры использования строковых аннотаций
Пример 1. Комбинация Literal с NewType для конечного набора идентификаторов
from typing import Literal, NewType
Status = NewType('Status', Literal['active', 'inactive', 'pending'])
def set_status(s: Status) -> None:
print(f'Статус изменён на {s}')
# Правильное использование
set_status(Status('active'))
# Ошибка при проверке типов (STATIC): set_status('active') - str, не Status
Вывод: Статус изменён на active
Пояснение: Создаётся псевдоним, который во время выполнения ведёт себя как строка, но статический анализатор разрешает только строки из указанного набора. При этом нужно явно «оборачивать» значение вызовом Status(...).
Пример 2. Аннотация строки с проверкой длины через Annotated и Pydantic
from typing import Annotated
from pydantic import BaseModel, Field
class User(BaseModel):
username: Annotated[str, Field(min_length=3, max_length=20)]
email: Annotated[str, Field(pattern=r'^[\w.-]+@[\w.-]+\.\w+$')]
user = User(username='jo', email='invalid') # Вызовет ошибку валидации
Вывод: ValidationError: 2 validation errors for User username String should have at least 3 characters [type=string_too_short, ...] email String should match pattern '^[\w.-]+@[\w.-]+\.\w+$' [type=string_pattern_mismatch, ...]
Пояснение: Используется Annotated с метаданными из Pydantic, которые интерпретируются во время выполнения. Это даёт и статическую аннотацию, и runtime-валидацию.
Пример 3. Переменная типа TypeVar, ограниченная строкой
from typing import TypeVar, Generic
S = TypeVar('S', bound=str)
class Container(Generic[S]):
def __init__(self, value: S) -> None:
self.value = value
c = Container('hello') # S = str
# c = Container(123) # Ошибка: int не является str
Вывод: (код выполняется без ошибок, но статический анализатор выдаст предупреждение на второй строке)
Пояснение: TypeVar с параметром bound=str ограничивает возможные типы только строками или их подклассами. Это полезно для создания обобщённых классов или функций, работающих со строками.
Пример 4. Использование typing.Pattern с несколькими флагами
import re
from typing import Pattern
pattern: Pattern[str] = re.compile(r'^\d+$', re.IGNORECASE | re.UNICODE)
def is_digit_only(text: str) -> bool:
return bool(pattern.match(text))
print(is_digit_only('123')) # True
print(is_digit_only('abc')) # False
True False
Пояснение: Аннотация Pattern[str] подходит для скомпилированного регулярного выражения, работающего со строками Unicode. Передача re.IGNORECASE не влияет на результат, так как цифры не имеют регистра, но демонстрирует допустимость флагов.
Пример 5. Сравнение Text и str в коде, совместимом с Python 2 и 3
# Python 2/3 совместимый код (устаревший)
from typing import Text
def concat(a: Text, b: Text) -> Text:
return a + b
# Современный Python:
def concat_new(a: str, b: str) -> str:
return a + b
print(concat('Hello', ' World')) # 'Hello World'
print(concat_new('Hello', ' World')) # 'Hello World'
Hello World Hello World
Пояснение: Разница проявляется только в Python 2, где Text соответствует unicode, а str - байтам. В Python 3 оба работают одинаково, но Text считается устаревшим, как упоминалось ранее.
Пример 6. Ошибочное импортирование несуществующего String
# from typing import String # ImportError: cannot import name 'String' from 'typing'
# Правильный вариант:
from typing import Text # (устаревший) или просто использовать str
Ошибка импорта при использовании String
Решение: В модуле typing нет типа с именем String. Всегда используйте встроенный str или, для обратной совместимости, typing.Text, но лучше обновить код.