Python & AI Tutorials Logo
Программирование Python

40. Написание чистого и читаемого кода

На протяжении этой книги вы изучили синтаксис Python, структуры данных, управляющие конструкции, функции, классы и многие другие концепции программирования. Теперь вы можете писать программы, которые работают. Но есть ключевое различие между кодом, который работает, и кодом, который легко поддерживать — кодом, который вы и другие сможете понять, изменять и отлаживать через месяцы или годы.

Эта глава посвящена написанию чистого, читаемого кода. Вы изучите соглашения и практики, которые делают код на Python профессиональным и поддерживаемым. Это не просто произвольные правила — это проверенные в бою рекомендации, которые упрощают совместную работу, снижают количество ошибок и помогают вам понимать собственный код, когда вы возвращаетесь к нему позже.

40.1) Почему стиль важен: чтение кода против написания кода

40.1.1) Код читают чаще, чем пишут

Когда вы пишете код, вы тратите минуты или часы на его создание. Но этот код будет прочитан много раз: когда вы будете его отлаживать, когда добавите новые возможности, когда другие разработчики будут работать с ним и когда вы вернётесь к нему через месяцы, пытаясь вспомнить, что он делает.

Рассмотрим рабочий, но плохо оформленный код:

python
# ПРЕДУПРЕЖДЕНИЕ: Плохой стиль — только для демонстрации
def c(l):
    t=0
    for i in l:
        t=t+i
    return t/len(l)
 
data=[85,92,78,90,88]
result=c(data)
print(result)  # Output: 86.6

Этот код работает идеально. Он вычисляет среднее значение списка(list) чисел. Но чтобы понять, что именно он делает, требуется внимательный анализ. Теперь сравните его с этой версией:

python
def calculate_average(numbers):
    """Calculate the arithmetic mean of a list of numbers."""
    total = 0
    for number in numbers:
        total = total + number
    return total / len(numbers)
 
test_scores = [85, 92, 78, 90, 88]
average_score = calculate_average(test_scores)
print(average_score)  # Output: 86.6

Что делает вторую версию лучше?

  • Имя функции (calculate_average) ясно указывает на назначение
  • Имена переменных (numbers, total, test_scores) описательные
  • Docstring объясняет, что делает функция
  • Правильные пробелы делают структуру понятной
  • Любой может понять этот код без изучения его в деталях

Обе версии дают одинаковый результат, но вторую версию понять можно сразу.

Ключевая мысль: код вы пишете один раз, но читаете его десятки или сотни раз. Потратив несколько лишних секунд на понятные имена и форматирование, вы сэкономите часы путаницы потом.

40.1.2) Читаемость снижает количество ошибок

Понятный код легче отлаживать, потому что вы быстро понимаете, что делает каждая часть. Когда имена переменных описательные, а структура аккуратная, логические ошибки заметить проще.

python
# Трудно отлаживать — что обозначают эти переменные?
# ПРЕДУПРЕЖДЕНИЕ: Плохой стиль — только для демонстрации
def process(x, y):
    if x > y:
        return x * (1 - y)
    return x
 
result = process(100, 0.1)
python
# Легко отлаживать — сразу понятно, что происходит
def apply_discount(price, discount_rate):
    """Calculate price after applying discount rate (0.0 to 1.0)."""
    discount_amount = price * discount_rate
    final_price = price - discount_amount
    return final_price
 
original_price = 100
discount = 0.1  # скидка 10%
final_price = apply_discount(original_price, discount)
print(f"Final price: ${final_price}")
# Output: Final price: $90.0

Во второй версии вы сразу видите логику: «Мы вычисляем сумму скидки, затем вычитаем её из цены». В первой версии вам приходится мысленно отслеживать, что означают x и y, и разбираться, что значит x * (1 - y).

40.1.3) Согласованность делает совместную работу возможной

Когда все в команде следуют одним и тем же соглашениям о стиле, код становится предсказуемым. Вы не тратите умственные ресурсы на расшифровку разных форматов — вы можете сосредоточиться на понимании логики.

У Python есть официальное руководство по стилю под названием PEP 8 (Python Enhancement Proposal 8). PEP 8 определяет соглашения для:

  • Как именовать переменные, функции и классы
  • Как форматировать код (пробелы, длина строки, отступы)
  • Когда использовать комментарии и docstring
  • Как организовывать импорты

Следование PEP 8 означает, что ваш код будет выглядеть знакомо другим программистам Python, а сотрудничество станет проще. В следующих разделах мы рассмотрим основные рекомендации PEP 8.

Да

Нет

Писать код

Следовать руководству по стилю?

Согласованный, читаемый код

Несогласованный код

Легко понять

Легко сопровождать

Легко сотрудничать

Ментальные издержки

Путаница

Ошибки

40.2) Соглашения об именовании: переменные, функции и классы (PEP 8)

40.2.1) Общие принципы именования

Хорошие имена описательные и однозначные. Они должны говорить, что что-то представляет или делает, без необходимости читать реализацию.

Ключевые принципы:

  • Используйте полные слова, а не сокращения (кроме очень распространённых вроде id, url, html)
  • Будьте конкретны: user_count лучше, чем count, calculate_total_price лучше, чем calculate
  • Избегайте имён из одной буквы, кроме очень коротких циклов или математических формул
  • Не включайте информацию о типе в имена (Python имеет динамическую типизацию)
python
# Плохие имена — непонятно, что они обозначают
# ПРЕДУПРЕЖДЕНИЕ: Плохой стиль — только для демонстрации
# Что такое 'n'? Число? Имя? Узел?
# Что такое 'd'? Дата? Расстояние? Длительность?
# Что такое 'l'? Похоже на цифру 1!
n = "Alice"
d = 25
l = [1, 2, 3]
calc = lambda x: x * 2
 
# Хорошие имена — ясно и описательно
student_name = "Alice"
age_in_years = 25
test_scores = [1, 2, 3]
double_value = lambda x: x * 2

Исключение: короткие переменные цикла

python
# Допустимо: очень коротко, контекст понятен
for i in range(10):
    print(i)
 
for x, y in coordinates:
    distance = (x**2 + y**2) ** 0.5
 
# Но для ясности лучше предпочитать описательные имена
for student_index in range(len(students)):
    print(students[student_index])
 
for point_x, point_y in coordinates:
    distance = (point_x**2 + point_y**2) ** 0.5

40.2.2) Имена переменных и функций: snake_case

В Python переменные и функции используют snake_case: все буквы строчные, слова разделяются подчёркиваниями.

python
# Переменные
user_name = "Bob"
total_price = 99.99
is_valid = True
max_retry_count = 3
 
# Функции
def calculate_tax(amount, rate):
    """Calculate tax on a given amount."""
    return amount * rate
 
def send_email_notification(recipient, message):
    """Send an email to the specified recipient."""
    print(f"Sending to {recipient}: {message}")
 
# Использование функций
tax_amount = calculate_tax(100, 0.08)
send_email_notification("user@example.com", "Welcome!")

Почему snake_case? Это очень читаемо. Подчёркивания создают чёткие границы слов, поэтому имена легко «сканировать» взглядом. Сравните calculatetotalprice (трудно читать) и calculate_total_price (понятно сразу).

40.2.3) Имена констант: UPPER_SNAKE_CASE

Константы — значения, которые не должны меняться во время выполнения программы — используют UPPER_SNAKE_CASE: все буквы заглавные, слова разделяются подчёркиваниями.

python
# Константы на уровне модуля
MAX_LOGIN_ATTEMPTS = 3
DEFAULT_TIMEOUT_SECONDS = 30
PI = 3.14159
DATABASE_URL = "postgresql://localhost/mydb"
 
def validate_password_length(password):
    """Check if password meets minimum length requirement."""
    MIN_PASSWORD_LENGTH = 8  # Константа внутри функции
    return len(password) >= MIN_PASSWORD_LENGTH
 
# Использование констант
if login_attempts > MAX_LOGIN_ATTEMPTS:
    print("Account locked")

Важно: в Python нет встроенного синтаксиса констант. В отличие от некоторых языков (например, const в JavaScript или final в Java), в Python нет способа объявить, что переменную нельзя изменить.

Вместо этого программисты Python используют соглашение об именовании, чтобы обозначить намерение:

  • UPPER_SNAKE_CASE означает: «Я считаю это константой — не изменяйте это»
  • Это инструмент коммуникации между программистами, а не языковая возможность
python
# В Python нет синтаксиса констант — это обычная переменная
MAX_LOGIN_ATTEMPTS = 3
 
# Python не помешает вам изменить её
MAX_LOGIN_ATTEMPTS = 5  # ❌ Технически работает, но нарушает соглашение
 
# Соглашение об именовании — это сигнал о НАМЕРЕНИИ:
# "Я назвал это ЗАГЛАВНЫМИ буквами, чтобы показать, что не хочу это менять"

Лучшая практика: если значение действительно должно меняться во время выполнения программы, не называйте его как константу:

python
# Это значение будет меняться — используйте строчные буквы
max_login_attempts = 3
max_login_attempts = 5  # ✅ OK — имя показывает, что оно может меняться
 
# Это значение никогда не должно меняться — используйте ЗАГЛАВНЫЕ
MAX_LOGIN_ATTEMPTS = 3
# Не присваивайте ему новое значение позже в коде

Это соглашение помогает программистам понимать ваше намерение и избегать ошибок. Когда вы видите MAX_LOGIN_ATTEMPTS, вы знаете, что его не нужно изменять.

40.2.4) Имена классов: PascalCase

Имена классов используют PascalCase (также называется CapWords): каждое слово начинается с заглавной буквы, без подчёркиваний.

python
# Определения классов
class Student:
    """Represent a student with name and grades."""
    def __init__(self, name):
        self.name = name
        self.grades = []
 
class ShoppingCart:
    """Manage items in a shopping cart."""
    def __init__(self):
        self.items = []
    
    def add_item(self, item):
        """Add an item to the cart."""
        self.items.append(item)
 
class DatabaseConnection:
    """Handle database connection and queries."""
    def __init__(self, url):
        self.url = url
 
# Создание экземпляров (примечание: для экземпляров используют имена переменных в snake_case)
student = Student("Alice")
shopping_cart = ShoppingCart()
db_connection = DatabaseConnection("localhost")

Почему PascalCase для классов? Это визуально отличает классы от функций и переменных. Когда вы видите Student(), вы сразу понимаете, что создаётся экземпляр класса. Когда вы видите calculate_average(), вы понимаете, что вызывается функция.

40.2.5) Приватные и внутренние имена: подчёркивание в начале

Имена, начинающиеся с одного подчёркивания (_name), обозначают внутреннее использование — они предназначены для использования внутри модуля или класса, а не внешним кодом.

В Python нет синтаксиса, чтобы пометить методы или атрибуты как «private» (в отличие от private в Java или C++). Вместо этого Python использует соглашение об именовании с ведущим подчёркиванием (_name), чтобы сообщить о намерении.

Что означает _name:

  • «Это только для внутреннего использования»
  • «Я сделал это для использования внутри этого класса/модуля, а не внешним кодом»
  • «Это может измениться в любой момент в будущих версиях — не полагайтесь на это»
python
class BankAccount:
    """Represent a bank account with balance tracking."""
    
    def __init__(self, account_number, initial_balance):
        self.account_number = account_number
        self._balance = initial_balance  # Внутренний атрибут
    
    def deposit(self, amount):
        """Add money to the account."""
        if self._validate_amount(amount):  # Внутренний метод
            self._balance += amount
    
    def _validate_amount(self, amount):
        """Internal helper to validate transaction amounts."""
        return amount > 0
    
    def get_balance(self):
        """Return the current balance."""
        return self._balance
 
# Использование класса
account = BankAccount("12345", 1000)
account.deposit(500)
print(account.get_balance())  # Output: 1500
 
# Технически работает, но нарушает соглашение
print(account._balance)
# Output: 1500 (works, but you shouldn't do this!)
 
# Технически работает, но нарушает соглашение
result = account._validate_amount(100)
# Output: True (works, but you shouldn't do this!)

Ключевой момент: Python не может предотвратить доступ к _balance или вызов _validate_amount(). Подчёркивание — это сигнал между программистами, а не функция безопасности.

Зачем существует это соглашение

Поскольку Python не может обеспечивать приватность, подчёркивание — это способ авторов классов сообщить о своём намерении:

Что сигнализирует подчёркивание:

  • «Это внутренняя реализация — она может измениться в будущих версиях»
  • «Используйте публичные методы — они гарантированно останутся стабильными»
  • «Если вы зависите от внутренних деталей, ваш код может сломаться, когда я обновлю библиотеку»

Соглашение создаёт контракт: авторы классов могут свободно менять внутреннюю реализацию (всё, что начинается с _), но должны сохранять стабильным публичный интерфейс. Это позволяет библиотекам развиваться, не ломая пользовательский код.

40.2.6) Специальные имена: двойные подчёркивания

Имена с двойными ведущими и завершающими подчёркиваниями (__name__) — это специальные методы или магические методы (magic methods), определённые Python. Не создавайте собственные имена с таким шаблоном — он зарезервирован для Python.

python
class Point:
    """Represent a 2D point."""
    
    def __init__(self, x, y):  # Специальный метод: инициализация
        self.x = x
        self.y = y
    
    def __str__(self):  # Специальный метод: строковое представление
        return f"Point({self.x}, {self.y})"
    
    def __add__(self, other):  # Специальный метод: оператор сложения
        return Point(self.x + other.x, self.y + other.y)
 
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1)  # Output: Point(1, 2)
print(p1 + p2)  # Output: Point(4, 6)

Как мы узнали в главе 31, эти специальные методы позволяют перегрузку операторов и интеграцию со встроенными функциями Python.

40.2.7) Сводная таблица именования

ТипСоглашениеПример
Переменныеsnake_caseuser_name, total_count
Функцииsnake_casecalculate_tax(), send_email()
КонстантыUPPER_SNAKE_CASEMAX_SIZE, DEFAULT_TIMEOUT
КлассыPascalCaseStudent, ShoppingCart
Внутренние/приватные_leading_underscore_balance, _validate()
Специальные/магическиеdouble_underscore__init__, __str__

40.3) Оформление кода: отступы, пробелы и пустые строки

40.3.1) Отступы: четыре пробела

Python использует отступы для определения блоков кода. Всегда используйте 4 пробела на уровень отступа — никогда не используйте табы и никогда не смешивайте табы и пробелы.

python
def calculate_grade(score):
    """Determine letter grade from numeric score."""
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    else:
        return "F"
 
# Вложенные отступы: 4 пробела на уровень
def process_students(students):
    """Process a list of student records."""
    for student in students:
        if student["active"]:
            grade = calculate_grade(student["score"])
            print(f"{student['name']}: {grade}")
 
students = [
    {"name": "Alice", "score": 92, "active": True},
    {"name": "Bob", "score": 78, "active": True}
]
process_students(students)
# Output:
# Alice: A
# Bob: C

Почему 4 пробела? Это стандарт сообщества Python. Большая часть кода Python, который вы встретите, использует 4 пробела, поэтому следование этому соглашению делает ваш код согласованным с экосистемой.

Настройка редактора: современные редакторы кода можно настроить так, чтобы при нажатии Tab вставлялись 4 пробела. Это даёт удобство клавиши Tab, сохраняя стандарт в 4 пробела.

40.3.2) Максимальная длина строки: 79 символов

PEP 8 рекомендует ограничивать строки 79 символами (и до 99 символов для docstring и комментариев). Это может показаться слишком строгим, но у этого есть практические плюсы:

  • Код остаётся читаемым на небольших экранах
  • Можно просматривать два файла рядом
  • Это поощряет разбиение сложных выражений на более простые части

Примечание: многие современные проекты используют чуть более длинные лимиты (88, 100 или 120 символов). Главное — согласованность внутри вашего проекта. Выберите предел и придерживайтесь его.

python
# Слишком длинно — трудно читать
# ПРЕДУПРЕЖДЕНИЕ: Плохой стиль — только для демонстрации
def calculate_monthly_payment(principal, annual_rate, years):
    return principal * (annual_rate / 12) * (1 + annual_rate / 12) ** (years * 12) / ((1 + annual_rate / 12) ** (years * 12) - 1)
 
# Лучше — разбито на читаемые строки
def calculate_monthly_payment(principal, annual_rate, years):
    """Calculate monthly loan payment using amortization formula."""
    monthly_rate = annual_rate / 12
    num_payments = years * 12
    
    numerator = principal * monthly_rate * (1 + monthly_rate) ** num_payments
    denominator = (1 + monthly_rate) ** num_payments - 1
    
    return numerator / denominator
 
payment = calculate_monthly_payment(200000, 0.045, 30)
print(f"Monthly payment: ${payment:.2f}")  # Output: Monthly payment: $1013.37

Разбиение длинных строк: когда нужно разбить строку, используйте неявное продолжение строки внутри круглых, квадратных или фигурных скобок:

python
# Длинный вызов функции
result = some_function(
    first_argument,
    second_argument,
    third_argument,
    fourth_argument
)
 
# Длинный список
colors = [
    "red", "green", "blue",
    "yellow", "orange", "purple",
    "pink", "brown", "gray"
]
 
# Длинная строка
message = (
    "This is a very long message that needs to be broken "
    "across multiple lines for readability. Python automatically "
    "concatenates adjacent string literals."
)
print(message)
# Output: This is a very long message that needs to be broken across multiple lines for readability. Python automatically concatenates adjacent string literals.

40.3.3) Пробелы вокруг операторов и после запятых

Используйте пробелы вокруг операторов и после запятых, чтобы улучшить читаемость:

python
# Плохие пробелы — тесно и трудно читать
# ПРЕДУПРЕЖДЕНИЕ: Плохой стиль — только для демонстрации
x=5
y=x*2+3
result=calculate_tax(100,0.08)
data=[1,2,3,4,5]
 
# Хорошие пробелы — ясно и читаемо
x = 5
y = x * 2 + 3
result = calculate_tax(100, 0.08)
data = [1, 2, 3, 4, 5]
 
# Пробелы в выражениях
total = (price * quantity) + shipping_cost
is_valid = (age >= 18) and (has_license == True)
 
# Пробелы в определениях функций
def calculate_discount(price, discount_rate, minimum_purchase=0):
    """Calculate discounted price if minimum purchase is met."""
    if price >= minimum_purchase:
        return price * (1 - discount_rate)
    return price

Исключение: не ставьте пробелы вокруг = в именованных аргументах или значениях параметров по умолчанию:

python
# Правильные пробелы для именованных аргументов
result = calculate_discount(price=100, discount_rate=0.1, minimum_purchase=50)
 
# Правильные пробелы для параметров по умолчанию
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

40.3.4) Пустые строки для логического разделения

Используйте пустые строки, чтобы разделять логические части кода:

Две пустые строки между функциями и классами верхнего уровня:

python
def first_function():
    """First function."""
    pass
 
 
def second_function():
    """Second function."""
    pass
 
 
class MyClass:
    """A class definition."""
    pass

Одна пустая строка между методами внутри класса:

python
class Student:
    """Represent a student with grades."""
    
    def __init__(self, name):
        self.name = name
        self.grades = []
    
    def add_grade(self, grade):
        """Add a grade to the student's record."""
        self.grades.append(grade)
    
    def get_average(self):
        """Calculate the student's grade average."""
        if not self.grades:
            return 0
        return sum(self.grades) / len(self.grades)

Пустые строки внутри функций для разделения логических шагов:

python
def process_order(order_items, customer):
    """Process a customer order and calculate total."""
    
    # Рассчитать промежуточную сумму
    subtotal = 0
    for item in order_items:
        subtotal += item["price"] * item["quantity"]
    
    # Применить скидку клиента
    discount = 0
    if customer["is_premium"]:
        discount = subtotal * 0.1
    
    # Рассчитать налог
    tax = (subtotal - discount) * 0.08
    
    # Рассчитать итоговую сумму
    total = subtotal - discount + tax
    
    return {
        "subtotal": subtotal,
        "discount": discount,
        "tax": tax,
        "total": total
    }

Эти пустые строки работают как визуальные «абзацы», делая структуру кода очевидной сразу.

40.3.5) Избегание пробелов в конце строк

Не оставляйте пробелы в конце строк — они невидимы, но могут вызывать проблемы в системах контроля версий и некоторых редакторах.

python
# Плохо — невидимые пробелы в конце строк (показаны как · для иллюстрации)
# ПРЕДУПРЕЖДЕНИЕ: Плохой стиль — только для демонстрации
def calculate(x):···
    return x * 2···
 
# Хорошо — нет пробелов в конце строк
def calculate(x):
    return x * 2

Большинство современных редакторов можно настроить так, чтобы они автоматически удаляли пробелы в конце строк при сохранении файла.

40.4) Документация: написание полезных комментариев и docstring

40.4.1) Когда писать комментарии

Комментарии объясняют, почему код что-то делает, а не что он делает. Хорошо названные переменные и функции должны делать «что» очевидным.

python
# Плохой комментарий — говорит очевидное
# ПРЕДУПРЕЖДЕНИЕ: Плохой стиль — только для демонстрации
x = x + 1  # Добавить 1 к x
 
# Хороший комментарий — объясняет почему
x = x + 1  # Поправка для индексации с нуля
 
# Плохой комментарий — дублирует код
# ПРЕДУПРЕЖДЕНИЕ: Плохой стиль — только для демонстрации
# Проверить, что age больше или равно 18
if age >= 18:
    print("Adult")
 
# Хороший комментарий — объясняет бизнес-логику
# Законный возраст употребления алкоголя в США
if age >= 21:
    print("Can purchase alcohol")

Когда комментарии полезны:

  1. Объяснение сложных алгоритмов:
python
def binary_search(sorted_list, target):
    """Search for target in sorted list using binary search."""
    left = 0
    right = len(sorted_list) - 1
    
    while left <= right:
        # Вычислить середину, избегая переполнения целых чисел
        # (right + left) // 2 может переполниться при очень больших индексах
        mid = left + (right - left) // 2
        
        if sorted_list[mid] == target:
            return mid
        elif sorted_list[mid] < target:
            left = mid + 1  # Цель находится в правой половине
        else:
            right = mid - 1  # Цель находится в левой половине
    
    return -1  # Цель не найдена
  1. Прояснение неочевидных бизнес-правил:
python
def calculate_shipping_cost(weight, distance):
    """Calculate shipping cost based on weight and distance."""
    base_cost = 5.00
    
    # Акция бесплатной доставки для тяжёлых товаров (политика компании на 2024 год)
    # Это стимулирует оптовые заказы и снижает стоимость доставки на единицу товара
    if weight > 50:
        return 0
    
    # Стандартный тариф: $0.50 за фунт плюс $0.10 за милю
    # На основе договора с перевозчиком, согласованного в Q1 2024
    return base_cost + (weight * 0.50) + (distance * 0.10)
  1. Документирование обходных решений или временных решений:
python
def process_data(data):
    """Process incoming data records."""
    # TODO: Это временное исправление для некорректных записей
    # Удалить после того, как проверка данных будет реализована выше по цепочке
    if not isinstance(data, list):
        data = [data]
    
    for record in data:
        # Обработать каждую запись
        pass

40.4.2) Написание эффективных docstring

Docstring — это специальные комментарии, документирующие модули, классы и функции. Они заключены в тройные кавычки и идут первой инструкцией в определении.

python
def calculate_bmi(weight_kg, height_m):
    """
    Calculate Body Mass Index (BMI).
    
    BMI is calculated as weight in kilograms divided by the square of height in meters.
    
    Args:
        weight_kg: Weight in kilograms (float or int)
        height_m: Height in meters (float or int)
    
    Returns:
        float: The calculated BMI value
    
    Example:
        >>> calculate_bmi(70, 1.75)
        22.857142857142858
    """
    return weight_kg / (height_m ** 2)
 
# Доступ к docstring
print(calculate_bmi.__doc__)
# Output:
#     Calculate Body Mass Index (BMI).
#     
#     BMI is calculated as weight in kilograms divided by the square of height in meters.
#     ...

Однострочные docstring для простых функций:

python
def square(x):
    """Return the square of x."""
    return x * x
 
def is_even(n):
    """Return True if n is even, False otherwise."""
    return n % 2 == 0

Многострочные docstring для сложных функций:

python
def find_prime_factors(n):
    """
    Find all prime factors of a positive integer.
    
    This function returns a list of prime numbers that, when multiplied
    together, equal the input number. The factors are returned in ascending order.
    
    Args:
        n: A positive integer greater than 1
    
    Returns:
        list: Prime factors in ascending order
    
    Raises:
        ValueError: If n is less than 2
    
    Example:
        >>> find_prime_factors(12)
        [2, 2, 3]
        >>> find_prime_factors(17)
        [17]
    """
    if n < 2:
        raise ValueError("n must be at least 2")
    
    factors = []
    divisor = 2
    
    while n > 1:
        while n % divisor == 0:
            factors.append(divisor)
            n = n // divisor
        divisor += 1
    
    return factors

Docstring классов:

python
class BankAccount:
    """
    Represent a bank account with deposit and withdrawal operations.
    
    This class maintains an account balance and provides methods for
    depositing and withdrawing money. All transactions are validated to prevent negative balances.
    
    Attributes:
        account_number: Unique identifier for the account
        balance: Current account balance in dollars
    """
    
    def __init__(self, account_number, initial_balance=0):
        """
        Initialize a new bank account.
        
        Args:
            account_number: Unique account identifier (string)
            initial_balance: Starting balance (default: 0)
        """
        self.account_number = account_number
        self.balance = initial_balance
    
    def deposit(self, amount):
        """
        Add money to the account.
        
        Args:
            amount: Amount to deposit (must be positive)
        
        Raises:
            ValueError: If amount is not positive
        """
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        self.balance += amount

40.4.3) Соглашения для docstring

Первая строка: краткое резюме того, что делает функция/класс. Должна помещаться в одну строку.

Пустая строка: отделяет резюме от подробного описания.

Подробное описание: объясняет, что делает функция, важные детали и как ей пользоваться.

Args/Parameters: перечисляет каждый параметр с его типом и назначением.

Returns: описывает, что возвращает функция, и тип.

Raises: документирует исключения, которые может выбросить функция.

Example: показывает типичное использование (необязательно, но полезно).

python
def calculate_compound_interest(principal, rate, time, compounds_per_year=1):
    """
    Calculate compound interest on an investment.
    
    Uses the compound interest formula: A = P(1 + r/n)^(nt)
    where A is the final amount, P is principal, r is annual rate,
    n is compounds per year, and t is time in years.
    
    Args:
        principal: Initial investment amount (float)
        rate: Annual interest rate as decimal (e.g., 0.05 for 5%)
        time: Investment period in years (float)
        compounds_per_year: Number of times interest compounds annually
                           (default: 1 for annual compounding)
    
    Returns:
        float: Final amount after compound interest
    
    Example:
        >>> calculate_compound_interest(1000, 0.05, 10, 12)
        1647.0095
    """
    return principal * (1 + rate / compounds_per_year) ** (compounds_per_year * time)

40.4.4) Комментарии TODO для будущих задач

Используйте комментарии TODO, чтобы отмечать места, которые потребуют внимания в будущем:

python
def process_payment(amount, payment_method):
    """Process a payment transaction."""
    # TODO: Добавить поддержку платежей в криптовалюте
    # TODO: Реализовать проверки на мошенничество
    
    if payment_method == "credit_card":
        return process_credit_card(amount)
    elif payment_method == "paypal":
        return process_paypal(amount)
    else:
        raise ValueError(f"Unsupported payment method: {payment_method}")

Многие редакторы умеют искать комментарии TODO, поэтому легко находить места, которые требуют доработки.

40.5) Организация кода: импорты, константы, функции и main

40.5.1) Стандартная структура модуля

Хорошо организованный модуль Python следует такой структуре:

  1. Docstring модуля: описывает, что делает модуль
  2. Импорты: стандартная библиотека, сторонние пакеты, затем локальные импорты
  3. Константы: константы на уровне модуля
  4. Функции и классы: основной код
  5. Блок выполнения main: код, который запускается при выполнении скрипта
python
"""
student_manager.py
 
Manage student records including grades and GPA calculations.
 
This module provides functions for adding students, recording grades,
and calculating grade point averages.
"""
 
# Импорты стандартной библиотеки
import sys
from datetime import datetime
 
# Импорты сторонних библиотек (если есть)
# import requests
 
# Локальные импорты (если есть)
# from .database import save_student
 
# Константы
MAX_GRADE = 100
MIN_GRADE = 0
PASSING_GRADE = 60
 
# Функции
def calculate_gpa(grades):
    """
    Calculate GPA from a list of numeric grades.
    
    Args:
        grades: List of numeric grades (0-100)
    
    Returns:
        float: GPA on 4.0 scale
    """
    if not grades:
        return 0.0
    
    average = sum(grades) / len(grades)
    
    # Преобразовать к шкале 4.0
    if average >= 90:
        return 4.0
    elif average >= 80:
        return 3.0
    elif average >= 70:
        return 2.0
    elif average >= 60:
        return 1.0
    else:
        return 0.0
 
def validate_grade(grade):
    """
    Check if a grade is within valid range.
    
    Args:
        grade: Numeric grade to validate
    
    Returns:
        bool: True if grade is valid, False otherwise
    """
    return MIN_GRADE <= grade <= MAX_GRADE
 
# Выполнение main
if __name__ == "__main__":
    # Код, который выполняется при прямом запуске скрипта
    test_grades = [85, 92, 78, 88]
    gpa = calculate_gpa(test_grades)
    print(f"GPA: {gpa}")  # Output: GPA: 3.0

40.5.2) Организация импортов

Группируйте импорты в три секции, разделённые пустыми строками:

  1. Импорты стандартной библиотеки: встроенные модули Python
  2. Импорты сторонних библиотек: установленные пакеты (например, requests, numpy)
  3. Локальные импорты: ваши собственные модули
python
# Импорты стандартной библиотеки
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
 
# Импорты сторонних библиотек
import requests
from flask import Flask, render_template
 
# Импорты локального приложения
from myapp.database import connect_db
from myapp.models import User, Product
from myapp.utils import format_currency

Стили импорта:

python
# Импортировать весь модуль
import math
result = math.sqrt(16)  # Output: 4.0
 
# Импортировать конкретные элементы
from math import sqrt, pi
result = sqrt(16)  # Output: 4.0
 
# Импортировать с псевдонимом
import numpy as np
array = np.array([1, 2, 3])
 
# Импортировать несколько элементов
from os import path, getcwd, listdir

Избегайте импортов со звёздочкой (from module import *) — они делают непонятным, откуда берутся имена:

python
# Плохо — непонятно, откуда берётся sqrt
# ПРЕДУПРЕЖДЕНИЕ: Плохой стиль — только для демонстрации
from math import *
result = sqrt(16)
 
# Хорошо — явный импорт
from math import sqrt
result = sqrt(16)

40.5.3) Организация констант

Размещайте константы на уровне модуля ближе к началу, после импортов:

python
"""Configuration settings for the application."""
 
import os
 
# Константы приложения
APP_NAME = "Student Manager"
VERSION = "1.0.0"
DEBUG_MODE = True
 
# Конфигурация базы данных
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///students.db")
MAX_CONNECTIONS = 10
 
# Бизнес-правила
MAX_STUDENTS_PER_CLASS = 30
PASSING_GRADE = 60
GRADE_WEIGHTS = {
    "homework": 0.3,
    "midterm": 0.3,
    "final": 0.4
}
 
def calculate_final_grade(homework, midterm, final):
    """Calculate weighted final grade."""
    return (
        homework * GRADE_WEIGHTS["homework"] +
        midterm * GRADE_WEIGHTS["midterm"] +
        final * GRADE_WEIGHTS["final"]
    )

40.5.4) Логический порядок функций

Организуйте функции в логическом порядке:

  1. Сначала публичные функции: функции, предназначенные для использования другими модулями
  2. Затем вспомогательные функции: внутренние функции, которые поддерживают публичные
  3. Связанные функции рядом: группируйте функции, которые работают вместе
python
"""Order processing module."""
 
# Функции публичного API
def process_order(order_items, customer):
    """
    Process a customer order.
    
    This is the main entry point for order processing.
    """
    subtotal = _calculate_subtotal(order_items)
    discount = _calculate_discount(subtotal, customer)
    tax = _calculate_tax(subtotal - discount)
    total = subtotal - discount + tax
    
    return {
        "subtotal": subtotal,
        "discount": discount,
        "tax": tax,
        "total": total
    }
 
def validate_order(order_items):
    """Validate that an order contains valid items."""
    if not order_items:
        return False
    
    for item in order_items:
        if not _validate_item(item):
            return False
    
    return True
 
# Внутренние вспомогательные функции
def _calculate_subtotal(items):
    """Calculate order subtotal (internal use)."""
    total = 0
    for item in items:
        total += item["price"] * item["quantity"]
    return total
 
def _calculate_discount(subtotal, customer):
    """Calculate customer discount (internal use)."""
    if customer.get("is_premium"):
        return subtotal * 0.1
    return 0
 
def _calculate_tax(amount):
    """Calculate sales tax (internal use)."""
    TAX_RATE = 0.08
    return amount * TAX_RATE
 
def _validate_item(item):
    """Validate a single order item (internal use)."""
    required_fields = ["name", "price", "quantity"]
    return all(field in item for field in required_fields)

Обратите внимание, что публичные функции (process_order, validate_order) идут первыми, а вспомогательные функции (с префиксом _) — после. Это делает понятным, какие функции являются основным API.

40.5.5) Организация классов внутри модулей

Когда модуль содержит классы, организуйте их логично:

python
"""User management system."""
 
# Константы
DEFAULT_ROLE = "user"
ADMIN_ROLE = "admin"
 
# Сначала базовые классы
class User:
    """Base user class."""
    
    def __init__(self, username, email):
        self.username = username
        self.email = email
        self.role = DEFAULT_ROLE
    
    def can_edit(self, resource):
        """Check if user can edit a resource."""
        return resource.owner == self.username
 
# Затем производные классы после базовых
class AdminUser(User):
    """Administrator with elevated privileges."""
    
    def __init__(self, username, email):
        super().__init__(username, email)
        self.role = ADMIN_ROLE
    
    def can_edit(self, resource):
        """Admins can edit any resource."""
        return True
 
# Связанные классы сгруппированы вместе
class Resource:
    """Represent a resource that can be owned and edited."""
    
    def __init__(self, name, owner):
        self.name = name
        self.owner = owner
 
# Вспомогательные функции, связанные с классами
def create_user(username, email, is_admin=False):
    """Factory function to create appropriate user type."""
    if is_admin:
        return AdminUser(username, email)
    return User(username, email)

Принципы организации классов:

  • Базовые классы перед производными (читателю нужно сначала понять базовый)
  • Связанные классы группируются вместе (User и Resource связаны)
  • Вспомогательные функции, работающие с классами, идут после определений классов
  • У каждого класса должен быть понятный docstring, объясняющий его назначение

40.6) Шаблон if name == "main"

40.6.1) Понимание шаблона

У каждого файла Python есть встроенная переменная __name__. Python автоматически устанавливает значение этой переменной в зависимости от того, как используется файл:

  • Когда вы запускаете файл напрямую (например, python my_script.py), Python устанавливает __name__ в "__main__"
  • Когда вы импортируете файл как модуль, Python устанавливает __name__ в имя модуля (имя файла без .py)

Это позволяет писать код, который выполняется только когда файл запускают напрямую, а не когда его импортируют:

python
"""math_utils.py - Mathematical utility functions."""
 
def add(a, b):
    """Add two numbers."""
    return a + b
 
def multiply(a, b):
    """Multiply two numbers."""
    return a * b
 
# Этот код выполняется только при прямом запуске файла
if __name__ == "__main__":
    # Протестировать функции
    print(f"5 + 3 = {add(5, 3)}")  # Output: 5 + 3 = 8
    print(f"5 * 3 = {multiply(5, 3)}")  # Output: 5 * 3 = 15

Когда вы запускаете python math_utils.py, вы увидите вывод. Но когда вы импортируете его в другом файле:

python
# another_file.py
from math_utils import add, multiply
 
result = add(10, 20)
print(result)  # Output: 30
# Тестовый код из math_utils.py НЕ выполняется

Обратите внимание, что тестовый код (внутри if __name__ == "__main__":) НЕ выполняется при импорте!

python math_utils.py

import math_utils

Файл Python выполняется

Как он запускается?

name = 'main'

name = 'math_utils'

Код в if name == 'main' выполняется

Код в if name == 'main' пропускается

40.6.2) Почему этот шаблон важен

Этот шаблон служит нескольким важным целям:

1. Тестирование и демонстрация: вы можете включать примеры использования в тот же файл, что и ваши функции:

python
"""temperature.py - Temperature conversion utilities."""
 
def celsius_to_fahrenheit(celsius):
    """Convert Celsius to Fahrenheit."""
    return (celsius * 9/5) + 32
 
def fahrenheit_to_celsius(fahrenheit):
    """Convert Fahrenheit to Celsius."""
    return (fahrenheit - 32) * 5/9
 
if __name__ == "__main__":
    # Продемонстрировать функции
    print("Temperature Conversion Examples:")
    print(f"0°C = {celsius_to_fahrenheit(0)}°F")  # Output: 0°C = 32.0°F
    print(f"100°C = {celsius_to_fahrenheit(100)}°F")  # Output: 100°C = 212.0°F
    print(f"32°F = {fahrenheit_to_celsius(32)}°C")  # Output: 32°F = 0.0°C

2. Переиспользуемые модули: один и тот же файл может быть и самостоятельным скриптом, и импортируемым модулем:

python
"""data_processor.py - Process and analyze data files."""
 
import sys
 
def load_data(filename):
    """Load data from a file."""
    with open(filename) as f:
        return [line.strip() for line in f]
 
def analyze_data(data):
    """Perform analysis on data."""
    return {
        "count": len(data),
        "average_length": sum(len(item) for item in data) / len(data)
    }
 
if __name__ == "__main__":
    # При запуске как скрипта обработать аргументы командной строки
    if len(sys.argv) < 2:
        print("Usage: python data_processor.py <filename>")
        sys.exit(1)
    
    filename = sys.argv[1]
    data = load_data(filename)
    results = analyze_data(data)
    
    print(f"Processed {results['count']} items")
    print(f"Average length: {results['average_length']:.2f}")

Вы можете запустить это как скрипт:

bash
$ python data_processor.py data.txt
Processed 42 items
Average length: 15.23

Или импортировать в другом файле:

python
# my_analysis.py
from data_processor import load_data, analyze_data
 
my_data = load_data("myfile.txt")
results = analyze_data(my_data)
print(f"Found {results['count']} items")

40.6.3) Распространённые шаблоны для main-блоков

Шаблон 1: простые тестовые случаи

python
"""calculator.py - Basic calculator operations."""
 
def add(a, b):
    """Add two numbers."""
    return a + b
 
def subtract(a, b):
    """Subtract b from a."""
    return a - b
 
if __name__ == "__main__":
    # Быстрые тесты
    assert add(2, 3) == 5
    assert subtract(10, 4) == 6
    print("All tests passed!")  # Output: All tests passed!

Шаблон 2: функция main

Для более сложных скриптов определите функцию main():

python
"""report_generator.py - Generate reports from data."""
 
import sys
 
def load_data(filename):
    """Load data from file."""
    # Implementation here
    pass
 
def generate_report(data):
    """Generate report from data."""
    # Implementation here
    pass
 
def save_report(report, output_file):
    """Save report to file."""
    # Implementation here
    pass
 
def main():
    """Main entry point for the script."""
    if len(sys.argv) < 3:
        print("Usage: python report_generator.py <input> <output>")
        return 1
    
    input_file = sys.argv[1]
    output_file = sys.argv[2]
    
    try:
        data = load_data(input_file)
        report = generate_report(data)
        save_report(report, output_file)
        print(f"Report saved to {output_file}")
        return 0
    except Exception as e:
        print(f"Error: {e}")
        return 1
 
if __name__ == "__main__":
    # Завершить с кодом статуса из main (0 = успех, 1 = ошибка)
    sys.exit(main())

У этого шаблона есть несколько преимуществ:

  • Функцию main() можно тестировать независимо
  • Понятная точка входа для скрипта
  • Корректные коды завершения (0 для успеха, ненулевые для ошибок)
  • Чёткое разделение между логикой скрипта и функциями модуля

40.6.4) Лучшие практики для main-блоков

Держите main-блоки сфокусированными: код внутри if __name__ == "__main__" должен в основном управлять выполнением скрипта, а не содержать сложную логику:

python
# Плохо — сложная логика в main-блоке
# ПРЕДУПРЕЖДЕНИЕ: Плохой стиль — только для демонстрации
if __name__ == "__main__":
    data = []
    for i in range(100):
        if i % 2 == 0:
            data.append(i * 2)
    result = sum(data) / len(data)
    print(result)
 
# Хорошо — логика в функциях, main-блок координирует
def generate_even_doubles(limit):
    """Generate doubled even numbers up to limit."""
    return [i * 2 for i in range(limit) if i % 2 == 0]
 
def calculate_average(numbers):
    """Calculate average of numbers."""
    return sum(numbers) / len(numbers)
 
if __name__ == "__main__":
    data = generate_even_doubles(100)
    result = calculate_average(data)
    print(result)  # Output: 99.0

Используйте функцию main() для сложных скриптов: как показано выше, определение функции main() делает ваш скрипт более тестируемым и организованным.

Документируйте использование скрипта: если ваш скрипт принимает аргументы командной строки, документируйте их в docstring модуля:

python
"""
file_processor.py - Process text files with various operations.
 
Usage:
    python file_processor.py <input_file> <output_file> [--uppercase]
 
Arguments:
    input_file: Path to input file
    output_file: Path to output file
    --uppercase: Convert text to uppercase (optional)
"""
 
import sys
 
def process_file(input_path, output_path, uppercase=False):
    """Process file with specified options."""
    with open(input_path) as f:
        content = f.read()
    
    if uppercase:
        content = content.upper()
    
    with open(output_path, 'w') as f:
        f.write(content)
 
if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(__doc__)  # Вывести docstring модуля
        sys.exit(1)
    
    input_file = sys.argv[1]
    output_file = sys.argv[2]
    uppercase = "--uppercase" in sys.argv
    
    process_file(input_file, output_file, uppercase)
    print(f"Processed {input_file} -> {output_file}")

Написание чистого, читаемого кода — это навык, который развивается с практикой. Соглашения и шаблоны в этой главе — не произвольные правила, а проверенные практики, которые делают код проще для понимания, сопровождения и отладки. По мере того как вы будете писать больше кода на Python, эти паттерны станут для вас естественными.

Помните: код читают намного чаще, чем пишут. Несколько лишних секунд, потраченных на выбор понятного имени, добавление полезного комментария или правильную организацию импортов, сэкономят часы путаницы позже — для вас и для других, кто будет работать с вашим кодом.

В следующей главе мы рассмотрим техники отладки и тестирования, которые опираются на эти практики чистого кода и помогают писать не просто читаемый код, но и корректный и надёжный код.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai