📜 Старый подход: декоратор без @functools.wraps
def debug(func):
def wrapper(*args, **kwargs):
print(f"Вызов {func.__name__}")
return func(*args, **kwargs)
return wrapper
@debug
def add(a, b):
"""Сложение двух чисел."""
return a + b
print(add.__name__) # wrapper
print(add.__doc__) # None
Что здесь происходит: декоратор работает, но теряет имя и документацию оригинальной функции. Это мешает отладке и документации.
✅ Современный вариант
import functools
def debug(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Вызов {func.__name__}")
return func(*args, **kwargs)
return wrapper
@debug
def add(a: int, b: int) -> int:
"""Сложение двух чисел."""
return a + b
print(add.__name__) # add
print(add.__doc__) # Сложение двух чисел.
Что улучшилось: метаданные сохранены, добавлены аннотации типов. Редакторы могут подсказывать аргументы, а документация не ломается.
📜 Ручная фабрика для фиксации аргументов
def make_adder(value):
def adder(x):
return x + value
return adder
add_five = make_adder(5)
print(add_five(3))
Что здесь происходит: замыкание решает задачу, но для простой фиксации аргумента писать две вложенные функции избыточно.
✅ Современный вариант: functools.partial
from functools import partial
def add(a: int, b: int) -> int:
return a + b
add_five = partial(add, 5)
print(add_five(3))
Что улучшилось: functools.partial делает намерение очевидным и не требует вложенных функций для простых сценариев.
functools.partial создаёт вызываемый объект, а не обычную функцию. Подробнее — в документации functools.
📜 Класс без аннотаций типов
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def get_description(self):
return f"{self.title} — {self.author}"
book = Book("1984", "George Orwell")
print(book.get_description())
Что здесь происходит: код работает, но непонятно, какие типы ожидает конструктор и что возвращает метод.
✅ Современный вариант: аннотации типов
class Book:
def __init__(self, title: str, author: str) -> None:
self.title = title
self.author = author
def get_description(self) -> str:
return f"{self.title} — {self.author}"
book = Book("1984", "George Orwell")
print(book.get_description())
Что улучшилось: контракт класса очевиден, IDE подсказывают ошибки, код легче поддерживать в команде.
✅ Современный вариант: dataclasses для простых моделей
from dataclasses import dataclass
@dataclass
class Book:
title: str
author: str
def get_description(self) -> str:
return f"{self.title} — {self.author}"
book = Book("1984", "George Orwell")
print(book.get_description())
Что улучшилось: декоратор @dataclass автоматически генерирует __init__, __repr__ и другие методы. Меньше шаблонного кода для классов-контейнеров данных.
dataclasses — в документации dataclasses.
✅ Современный вариант: @property для контроля доступа
class Circle:
def __init__(self, radius: float) -> None:
self._radius = radius
@property
def radius(self) -> float:
return self._radius
@radius.setter
def radius(self, value: float) -> None:
if value < 0:
raise ValueError("Радиус не может быть отрицательным")
self._radius = value
def get_area(self) -> float:
from math import pi
return pi * self.radius ** 2
circle = Circle(5)
print(circle.get_area())
circle.radius = 8
print(circle.get_area())
Что улучшилось: вместо прямого изменения поля используется свойство, которое может проверять значения и скрывать детали реализации.
✅ Современный стиль: PEP 8
PEP 8 — руководство по стилю кода Python. Основные рекомендации:
- Имена функций и переменных —
snake_case. - Имена классов —
CamelCase. - Имена констант —
UPPER_SNAKE_CASE. - Отступы — 4 пробела.
- Длина строки — не более 79–88 символов.
- Импорты группируются: стандартная библиотека, сторонние модули, локальные модули.
🕰️ Когда старый подход ещё можно встретить
Учебные материалы и legacy-код могут не использовать аннотации типов, dataclasses или @property. Это нормально, но в новых проектах рекомендуется использовать современный стиль. Аннотации str | None и синтаксис list[str] требуют Python 3.10+.