← Назад к вопросам

Какие знаешь особенности аннотации словаря?

1.6 Junior🔥 181 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Особенности аннотации словаря в Python

Аннотация словаря (type hints для словарей) позволяет указать типы ключей и значений. Это улучшает читаемость, IDE autocompletion и помогает инструментам статического анализа.

1. Базовая аннотация с dict[]

# Python 3.9+ использует встроенные типы вместо typing
my_dict: dict[str, int] = {"a": 1, "b": 2}

# Читается как: словарь со строковыми ключами и целыми значениями

# До Python 3.9 использовалось Dict из typing
from typing import Dict
my_dict: Dict[str, int] = {"a": 1, "b": 2}

2. Различные типы ключей и значений

# Строки в ключах, целые значения
user_ages: dict[str, int] = {"Alice": 30, "Bob": 25}

# Целые ключи, строки в значениях
status_codes: dict[int, str] = {200: "OK", 404: "Not Found"}

# Кортежи в ключах (иммутабельные)
coordinates: dict[tuple[int, int], str] = {(0, 0): "origin", (1, 1): "point"}

# Список значений (List из typing)
from typing import List
my_dict: dict[str, List[int]] = {"numbers": [1, 2, 3], "odds": [1, 3, 5]}

# Вложенные словари
config: dict[str, dict[str, str]] = {
    "database": {"host": "localhost", "port": "5432"},
    "cache": {"type": "redis", "ttl": "3600"}
}

3. Any и Union для гибкости

from typing import Any, Union

# Any допускает любой тип
flexible_dict: dict[str, Any] = {
    "name": "Alice",
    "age": 30,
    "active": True,
    "metadata": {"created": "2023-01-01"}
}

# Union для нескольких возможных типов
status_dict: dict[str, Union[int, str]] = {
    "status": 200,
    "message": "OK"
}

# Или более красиво в Python 3.10+: str | int
modern_dict: dict[str, str | int] = {
    "status": 200,
    "message": "OK"
}

4. Аннотирование функций с словарями

def process_user(user: dict[str, str]) -> dict[str, Any]:
    """
    Обрабатывает пользователя.
    
    Args:
        user: словарь с ключами name, email
    
    Returns:
        Словарь с результатом обработки
    """
    return {
        "name": user["name"],
        "email": user["email"],
        "processed": True
    }

# Вызов с типпингом
result = process_user({"name": "Alice", "email": "alice@example.com"})
print(result["processed"])  # True

# IDE поймёт типы!  -> result["processed"] это Any
print(result["unknown_key"])  # Типпер не предупредит (Any допускает всё)

5. Типизация через TypedDict

Лучший способ типизации словарей со строгой структурой

from typing import TypedDict

class UserDict(TypedDict):
    name: str
    email: str
    age: int

def process_user(user: UserDict) -> None:
    print(user["name"])  # OK
    print(user["email"])  # OK
    print(user["age"])  # OK
    print(user["unknown"])  # ERROR! Типпер предупредит

# Вызов
user: UserDict = {"name": "Alice", "email": "alice@example.com", "age": 30}
process_user(user)  # OK

process_user({"name": "Bob", "email": "bob@example.com"})  # ERROR! age отсутствует

TypedDict с опциональными полями

from typing import TypedDict, Optional

class ConfigDict(TypedDict, total=False):  # Все поля опциональны
    host: str
    port: int
    ssl: bool

config: ConfigDict = {"host": "localhost"}  # OK, port и ssl опциональны

# Или явно обозначить опциональные
class UserProfileDict(TypedDict):
    name: str
    email: str
    phone: Optional[str]  # Может быть None

profile: UserProfileDict = {"name": "Alice", "email": "alice@example.com", "phone": None}

6. Наследование TypedDict

from typing import TypedDict

class PersonDict(TypedDict):
    name: str
    age: int

class EmployeeDict(PersonDict):
    employee_id: str
    salary: float

employee: EmployeeDict = {
    "name": "Alice",
    "age": 30,
    "employee_id": "E123",
    "salary": 50000.0
}

7. Required и NotRequired (Python 3.11+)

from typing import TypedDict, Required, NotRequired

class UserDict(TypedDict):
    name: Required[str]      # ОБЯЗАТЕЛЬНО
    email: Required[str]     # ОБЯЗАТЕЛЬНО
    phone: NotRequired[str]  # ОПЦИОНАЛЬНО

user: UserDict = {"name": "Alice", "email": "alice@example.com"}
# phone опциональен

8. Аннотация в классах

class UserManager:
    users: dict[str, dict[str, Any]] = {}
    
    def add_user(self, user_id: str, data: dict[str, str]) -> None:
        self.users[user_id] = data
    
    def get_user(self, user_id: str) -> dict[str, str]:
        return self.users.get(user_id, {})

manager = UserManager()
manager.add_user("u1", {"name": "Alice", "email": "alice@example.com"})
user_data = manager.get_user("u1")
print(user_data["name"])  # IDE знает, что это str

9. Словарь как параметр с конкретным типом

def update_config(config: dict[str, str | int]) -> None:
    """Обновляет конфигурацию."""
    for key, value in config.items():
        if isinstance(value, str):
            print(f"{key}: {value.upper()}")
        else:
            print(f"{key}: {value * 2}")

update_config({"name": "alice", "timeout": 30})

10. Аннотирование с .get() и доступом

from typing import Optional

config: dict[str, str | None] = {"host": "localhost", "port": None}

# Безопасный доступ с .get()
host: str | None = config.get("host")  # Может быть None
port: str | None = config.get("port", "5432")  # Default если нет

# Со значением по умолчанию
value: str = config.get("unknown", "default")  # Всегда str

11. Словари с переменным числом типов

from typing import Literal

# Ограничивающие значения
status_dict: dict[str, Literal["active", "inactive", "pending"]] = {
    "user1": "active",
    "user2": "inactive"
}

status_dict["user3"] = "unknown"  # Типпер предупредит!

12. Сравнение подходов

# ❌ Без типизации (плохо)
def process(data):
    return data["key"]  # IDE не знает, что внутри

# ❌ С dict[str, Any] (лучше, но не идеально)
def process(data: dict[str, Any]) -> Any:
    return data["key"]

# ✅ С TypedDict (отлично!)
from typing import TypedDict

class DataDict(TypedDict):
    key: str
    value: int

def process(data: DataDict) -> str:
    return data["key"]

13. Инструменты статического анализа

# mypy проверяет аннотации
# mypy --strict script.py

config: dict[str, str] = {"host": "localhost", "port": 5432}
# mypy ERROR: Expected str for dict value, got int

# Правильно:
config: dict[str, str | int] = {"host": "localhost", "port": 5432}

14. Настоящий пример: Flask приложение

from typing import TypedDict, Optional
from flask import Flask, request

class RequestPayload(TypedDict):
    username: str
    email: str
    age: Optional[int]

app = Flask(__name__)

@app.route('/users', methods=['POST'])
def create_user() -> dict[str, str]:
    data: RequestPayload = request.get_json()
    
    return {
        "status": "created",
        "id": "user_123",
        "username": data["username"]
    }

Выводы

  • dict[K, V] — базовая аннотация словаря
  • TypedDict — лучший способ типизировать структурированные словари
  • Any — используй осторожно, теряется типизация
  • Union / | — несколько возможных типов
  • Literal — ограниченный набор значений
  • Optional — может быть None
  • Статический анализ — mypy проверит типы

Правильная типизация словарей делает код безопаснее и понятнее!

Какие знаешь особенности аннотации словаря? | PrepBro