Использование typing для строковых типов данных

Раздел: Типы данных -> Модуль 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, но лучше обновить код.

Typing.String в Python - comments

En
Python typing string (python)