Что такое документация и docstring
Документация — это описание кода, которое помогает разработчикам понимать, как работают функции, классы и модули. Хорошая документация снижает время на поддержку и упрощает совместную работу.
Docstring (документирующая строка) — это строка, заключённая в тройные кавычки (""" или '''), расположенная сразу после объявления функции, класса или в начале модуля. В отличие от обычного комментария, docstring доступен через help() и атрибут __doc__.
Стандартный формат reST/Epytext
В исходном материале используется формат с полями :param и :return. Это распространённый reST-стиль, который понимают IDE и генераторы документации.
def greet(name: str) -> str:
"""
Функция принимает имя и возвращает строку приветствия.
:param name: Имя пользователя.
:return: Приветственное сообщение.
"""
return f"Hello, {name}!"
Где размещать docstring
- Функция: первая строка тела функции.
- Класс: первая строка тела класса.
- Модуль: в самом начале файла, до импортов или сразу после них.
Справка в Python: help() и __doc__
Функция help() выводит документацию объекта. Если передать в неё пользовательскую функцию с docstring, Python покажет именно её.
def add(a: int, b: int) -> int:
"""Возвращает сумму двух чисел."""
return a + b
help(add) # покажет docstring функции
print(add.__doc__) # тот же текст одной строкой
Без аргументов help() запускает интерактивный справочный режим: можно вводить имена объектов и получать информацию о них.
""" и нажать Enter.
Аннотации типов
Аннотации типов — это способ указать, какие типы данных ожидаются у параметров функции и какой тип она возвращает. Python остаётся динамически типизированным языком: аннотации не проверяются интерпретатором во время выполнения.
Зачем нужны type hints
- Упрощение понимания кода. Сразу видно, какие данные приходят на вход и что возвращается.
- Помощь в отладке. Статический анализатор (например,
mypy) находит потенциальные ошибки до запуска программы. - Автодокументирование. IDE используют аннотации для подсказок и автодополнения.
Базовый синтаксис
def function_name(param1: type1, param2: type2) -> return_type:
# тело функции
return value
variable: type = value
Аннотации для базовых типов
Для большинства встроенных типов название аннотации совпадает с названием типа:
def factorial(n: int) -> int:
"""Возвращает факториал числа."""
result = 1
for i in range(1, n + 1):
result *= i
return result
def convert_to_celsius(fahrenheit: float) -> float:
"""Конвертирует температуру из Фаренгейта в Цельсий."""
return (fahrenheit - 32) * 5 / 9
def is_even(number: int) -> bool:
"""Определяет, является ли число чётным."""
return number % 2 == 0
def log_message(message: str) -> None:
"""Выводит сообщение в консоль, но не возвращает значения."""
print(f"LOG: {message}")
int, Python не выдаст ошибку, но код может упасть позже из-за несовместимых операций.
Аннотации структур данных
В Python 3.9+ встроенные коллекции можно использовать как generic-типы: в квадратных скобках указывается тип содержимого.
Список
def process_numbers(numbers: list[int]) -> list[int]:
# Список чисел
return [n ** 2 for n in numbers]
Кортеж
У кортежа есть два случая: фиксированная длина и произвольная длина.
def get_info() -> tuple[str, float]:
# Первый элемент str, второй элемент float
return "Bob", 4.91
def variable_tuple() -> tuple[int, ...]:
# Кортеж произвольной длины, но только целые числа
return 5, 8, 2
Множество, frozenset и словарь
def unique_chars(text: str) -> set[str]:
# Множество уникальных символов
return set(text)
def frozen_example() -> frozenset[int]:
# Множество уникальных чисел
return frozenset([1, 2, 3])
def count_words(text: str) -> dict[str, int]:
"""Принимает строку и возвращает словарь с подсчётом каждого слова."""
words = text.split()
return {word: words.count(word) for word in words}
Модуль typing: Any, Union, Optional, Callable
Когда одного типа недостаточно, используют специальные конструкции из модуля typing.
Any — любой тип
from typing import Any
def process_data(data: Any) -> str:
"""Принимает данные любого типа и возвращает строку с их представлением."""
return f"Данные: {data}"
Union и оператор | — несколько типов
from typing import Union
# Старый стиль
def calculate(value: Union[int, float]) -> float:
return value ** 2
# Современный стиль Python 3.10+
def calculate_new(value: int | float) -> float:
return value ** 2
Optional — значение может быть None
from typing import Optional
def get_user_name(user_id: int) -> Optional[str]:
"""Возвращает имя пользователя или None, если пользователь не найден."""
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)
Callable — функция как аргумент
from typing import Callable
def execute_function(
func: Callable[[int, int], int],
nums1: list[int],
nums2: list[int]
) -> list[int]:
return [func(a, b) for a, b in zip(nums1, nums2)]
typing есть и более продвинутые инструменты, например TypeVar для параметрических типов и Protocol для структурной типизации. Они используются в больших проектах для повышения гибкости аннотаций.
Передача изменяемых и неизменяемых объектов
В Python аргументы передаются по ссылке, но поведение зависит от того, является ли объект изменяемым.
Неизменяемые типы
Когда внутри функции пытаемся изменить неизменяемый объект, Python создаёт новый объект, а оригинал остаётся прежним.
def modify_value(n: int) -> None:
print(f"Внутри до: {n}, id: {id(n)}")
n += 1
print(f"Внутри после: {n}, id: {id(n)}")
num = 10
modify_value(num)
print(f"Снаружи: {num}, id: {id(num)}")
Изменяемые типы
Изменение внутри функции затрагивает оригинальный объект, потому что внутри и снаружи ссылаемся на один и тот же объект.
def modify_list(lst: list[int]) -> None:
print(f"Внутри до: {lst}, id: {id(lst)}")
lst.append(99)
print(f"Внутри после: {lst}, id: {id(lst)}")
my_list = [1, 2, 3]
modify_list(my_list)
print(f"Снаружи: {my_list}, id: {id(my_list)}")
Как избежать нежелательных изменений
Если нужно работать с изменяемым объектом, но не менять оригинал, создайте копию:
def safe_modify_list(lst: list[int]) -> list[int]:
"""Работает с копией списка, оставляя оригинальный неизменным."""
copy_lst = lst.copy()
copy_lst.append(99)
return copy_lst
.copy() бывает недостаточно. В таких случаях используют copy.deepcopy() из стандартной библиотеки.
mypy. Установка и запуск: pip install mypy, затем mypy your_file.py. Это помогает находить расхождения между аннотациями и фактическим использованием до запуска программы.