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

Не факт, что кто-то это прочтёт или отнесётся серьёзно к прочтению, но возможно, если на эту статью наткнётся какой-нибудь новичок, то ему будет весьма полезно в двух словах понять основы красивого кода, а если это моё детище увидит человек с огромными познаниями и будет с чем-то не согласен, то я всегда готов услышать ваше мнение в комментариях под статьёй :)

Итак, приступим:

1. Правильные именования

Да, самое очевидное и понятное как по мне, но не всегда всеми соблюдаемое.

Давайте своим переменным понятные и очередные названия, например, если переменная содержит период сохранения каких-то данных, так и назовите её save_period, а не как-нибудь a или b.

Также не стоит давать своим переменным зарезервированные имена, то есть, например в Python есть функция sum, ну так и не надо для своей переменной, содержащей сумму каких-то чисел, давать имя sum.

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

2. Комментарии

Пишите комментарии там, где вы создаёте какую-то логику, которая понятно только вам или не совсем очевидна.
Например, вернёмся к всё той же переменной save_period:

save_period = 3 # Период сохранения
def save_game(periode: int):
    ...

Вот так вот делать друзья не стоит, вы на то и пишите понятные имена переменных, чтобы потом никак их не пояснять и по названию сразу понимать их назначение. В данном примере лучше стоит написать комментарий, например, обозначающий единицу измерения времени: секунды, минуты.
А вот пример того, когда комментарий нужен, так как он объясняет какую-то вашу логику:

save_file = ".../my_games/saves/game_1
save_period = 3 # сутки

def save_algorithme(path, period):
   for I in range(-1; period):
       time.sleep(50) # Таймер не убирать, без него не работает!!!!
       ...

Вот такие комментарии весьма уместны, они объяснит какие-то ваши задумки, костыли, чтобы будущие разработчики, которые притронутся к этому коду, либо это будете вы, но через очень долгое время, сразу поняли, что трогать это не стоит.

Докстроки

Ещё одна немало важная вещь из разряда комментариев это docstring. Это строчки которые мы записываем в три двойные кавычки. Их преимущественно используют в описаниях функции, чтобы на будущее для себя или для членов вашей команды обозначить, что делает та или иная функция, какие переменные в неё стоит передавать, что она возвращает (но это можно сделать и иначе, об этом позже).
Вот простой при ер docstring:

def add(a, b):
    """
    Суммирует два числа.
 
    Args:
        a (int): Первое число
        b (int): Второе число
 
    Returns:
        int: Сумма двух чисел
    """
    return a + b

Здесь в описании мы чётко указываем функционал, входные данные и выходные данные. Также, если вам необходимо получить значение какой-либо докстроки в любой из ваших функций, то воспользуйтесь вот таким методом: func_name.__doc__
Если это применить на нашу функцию add, то мы получим вот такой вот вывод:

Суммирует два числа.

Args:
    a (int): Первое число
    b (int): Второе число

Returns:
    int: Сумма двух чисел

3. Грамотное разделение

Ещё один важный момент это разделение вашего кода. Тут есть два варианта: разделение внутри файла и на файлы.
Поговорим о первом: он подразумевает, что код нужно делить на какие-то смысловые блоки и их обозначать. Конечно, если у вас в файле строк 100-150, ну максимум 200, то это не имеет особого значения, но если например вы написали уже 300-500 строк и больше, но на файлы делить как вы считаете нет пока что смысла, то можно поделить на блоки внутри файла. Я приведу пример моего варианта деления, как поставить вам решайте сами, это лишь вариация:

# Переменные
a = 536
b = 463

# ----- Обработка чисел -----
...
# ---------------------------


# ----- Изменение чисел -----
...
# ---------------------------

Думаю основный смысл и посыл понятен. Возможно, это зайдёт не всем, но так код становится намного понятней и читабельнее, если бы всё просто было бы навалено в кучу.
Кстати, для удобства ориентирования в большом коде могу посоветовать полезный плагин на VS Code: https://marketplace.visualstudio.com/items?itemName=alefragnani.Bookmarks
Это простой плагин, который позволяет ставить закладки в файле и быстро по ним перемещаться. Зачастую использую в крупных проектах для быстрого ориентирования.

А теперь второй способ — деление на файлы. Когда ваши проекты начинают разрастаться и становится огромными, то становится просто необходимым создание иерархий в проекте.
Тут всё очень просто. У вас есть например корневая папка проекта, где лежит, например, файл, который запускает весь проект. Есть папка Data Bases, где лежат файлы с прописанными функциями обращения к БД и сами БД. Есть например папка handlers, если это к примеру телеграмм бот, где лежат все хэндлеры бота, или есть папка keybords с клавиатурами для каждого хэндлера и т. д.
Представим вот такой вот проект:

- project
	- helper.py
	- homebot.py
	- ids_base.db
    - channel_photos
        - sort_script.py
        - sort_script2.py
    - data_base
        - databases.py
        - mass_db.py
        - users_db.py
    - ...

Вот вам пример иерархии из моего реального проекта, конечно это только кусочек для наглядности. Вот к примеру, мне нужно извлечь из файла databases.py класс под названием Database в файл helper.py. Как мне это сделать? А всё просто, обычными импортами с указанием пути к нужному файлу. Импорт будет в таком случаи выглядеть примерно так:

from data_base.databases import Database
# или иначе
import data_base.databases as db
db.Databases

Раньше, на старых версиях Python, чтобы подобные импорты работали в каждую директорию проекта, в любую абсолютно требовалось создавать файл __init__.py, об этом способе я как-нибудь расскажу поподробнее, просто знайте, что с версии Python 3.3 использование __init__.py стало необязательно.

4. Типизация

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

  1. str - символьные строки

  2. int - целые числа

  3. float - число с плавающей точкой (десятичные дроби)

  4. complex - комплексные числа

  5. bool - булевые значения (True/False)

  6. list - список

  7. dict - словарь

  8. tuple - кортеж

  9. set - множество

  10. frozenset - неизменяемое множество

  11. bytes - байтовые последовательности

  12. bytearray - изменяемые байты Вот основные типы данных в Python. Конечно, их может быть больше, если добавлять специализированные библиотеки такие как: collections, typing и другие. Так как же работает та самая типизация? А всё на самом деле просто. Представим обычную функцию python:

def summary(a, b, c):
	return a + b + c

Что она делает? Ну думаю тут всем понятно, что возвращает сумму 3 числовых значения. Но каких? Целых чисел? Дробных? Комплексных? не понятно. Вот как раз-таки для того, чтобы вносить нужную ясность в код существует аннотация типов в Python. Рассмотрим теперь всю ту же функцию, но теперь мы будем знать, какое именно число вводить в функцию:

def summary(a: int|float, b: int|float, c: int|float) -> int|float:
	return a + b + c

Выглядит немного громоздко и непонятно, но сейчас всю объясню и упрощу. Начнём с того, что теперь после каждого параметра функции мы указываем его тип с помощью двоеточия, то есть, если нам нужен тип строки, то это будет выглядеть так: a: str. Всё остальное делается по аналогии. Но а если мы хотим, чтоб наша функция учитывала и целые числа и дробные? Для этого нам нужно указать сразу два типа: int и float. Поэтому в параметре мы используем символ | вот так: a: int|float. Это является своего родом заменой слова "или".
Думаю у самых внимательных возник вопрос а что это за символы -> перед двоеточием. А это, так скажем, обозначение того, что выдаст нам функция после return. То есть, мы знаем, что наша функция в итоге выдаст либо int, либо float, поэтому и пишем после -> int|float.
Вот примерно так всё это работает друзья, не сложно и интеренсо. Думаю в будущем выложу более подробную статью по этой теме рассказав обо всех типах данных для начинающих, а также о библиотеке typing.
Примечание: если что, в данной ситуации int|float можно было заменить просто на float, так как int - это подкласс float

5. Ошибки

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

def divide(a, b):
    if b == 0:
        return None
    return a / b

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

def divide(a, b):
    if b == 0:
        raise ValueError("Деление на ноль")
    return a / b

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

Ловите конкретные исключения. Я думаю многие из вас слышали о конструкции try: ... except: .... В блоке try: выполняется какая-то команда и если она ловит ошибку, то выполняется блок except:, так вот, блоков except: может быть большое количество и каждый обрабатывать свою ошибку, рассмотрим вот такой код:

def divide_god(a, b):
    if b == 0:
        raise ValueError("Деление на ноль")
    elif a == b:
	    raise TypeError("Богу математики такое не понраву")
    else:
        return a / b

try:
	divide_god(5, 5)
except ValueError:
	print("Зачем ты делил на ноль!?")
except TypeError:
	print("Ты не смог ему угодить...")

Вот в таком случаи, вне зависимости от того, какие значения мы подставим в функцию divide_god мы всегда учитываем каждый вид ошибки и "ловим" его при выполнении. Конечно никто вам не мешает написать только except: ... и "ловить" любые ошибки, но поверьте мне, рано или поздно это сыграет с вами злую шутку.


Думаю на этом всё. Я сказал всё, что считаю нужным знать для написания красивого и чистого, а также понятно кода на Python. Данная статья преимущественно рассчитана на начинающих ребят, но я буду рад реакций от людей любого уровня :-)

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


  1. Andrey_Solomatin
    07.07.2025 11:42

    # ----- Обработка чисел -----

    Можно просто в отдельную функцию вынести.

    import data_base.databases as db

    Алиасы нужны для решения конфликта имён и только для этого. Добавление промежуточных имён редко делает код чище. Практики принятые в датасаенсе часто не являются примерам хорошего дизайна.

    Мои инструменты это автоматизация. Форматер + линтер + статический анализ задают нижнюю планку качества кода на хорошем уровне. Мой личный шаблон https://github.com/Cjkjvfnby/project_template, давно не обновлялся, но всё еще актуальный.


  1. Andrey_Solomatin
    07.07.2025 11:42

    def save_game(période: int):

    Что за перьёд? Товаришь Chat J'ai peté постарался?


    1. Dhwtj
      07.07.2025 11:42

      Что за англоцентризм?


    1. DarkScara Автор
      07.07.2025 11:42

      нет, просто я пользуюсь как английской раскладкой, так и французской


  1. Kerman
    07.07.2025 11:42

    Как писать красивый и чистый код питонистам?

    А никак, бгггг

    Шучу, серьёзный ответ будет: "а так же, как и всем остальным"

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


    1. SystemSoft
      07.07.2025 11:42

      те кто не поймут про какой период сохранения - тоже в дворники


    1. evgenyk
      07.07.2025 11:42

      Ну вообще-то это переводится примерно так: "Сохрани период". С именами все очень и очень непросто.


      1. Kerman
        07.07.2025 11:42

        Я в курсе. И назвал бы переменную SaveDelayAfterLastActionSec, ой, простите, тут питоний, значит save_delay_after_last_action_sec

        И вообще бы вынес нахер в константы в начало файла. Соответственно, капсом.

        Вопрос: а нужен ли мне после этого вообще комментарий?


        1. evgenyk
          07.07.2025 11:42

          Может лучше saving_delay?


          1. Kerman
            07.07.2025 11:42

            нет, у saving другая семантика. Это слово обозначает экономию


            1. evgenyk
              07.07.2025 11:42

              In the computer context, saving refers to the process of writing data to a storage device so it can be accessed and used later, even after the computer is turned off.


              1. Kerman
                07.07.2025 11:42

                Для этого нужно, чтобы saving был отглагольным существительным. Нужно указать, что именно оно сейвит. Как существительное само по себе - это экономия.


    1. Dhwtj
      07.07.2025 11:42

      . Если человек не может перевести "save_period", то пусть идёт в дворники

      Переведи, пожалуйста сложный медицинский термин с русского на английский и обратно быстро и однозначно.


      1. Kerman
        07.07.2025 11:42

        Зачем? Медтермины обычно на латыни, пусть в латыни и остаются. Это общепризнанный общемировой формат


        1. Dhwtj
          07.07.2025 11:42

          Издеваешься

          Состояние (шаг) бизнес процесса:

          Проведено медицинское обследование после производственной травмы.

          Возможно 10 вариантов перевода на английский. А с английского неоднозначности в понимании на русском

          Вот тебе в панамку от LLM

          1. MedicalExamCompleted

          2. PostInjuryAssessmentDone

          3. InjuryMedicalReviewCompleted

          4. OccupationalExamConducted

          5. WorkInjuryDiagnosisDone

          6. PostTraumaMedicalEvaluation

          7. InjuryAssessmentFinalized

          8. MedicalReviewConcluded

          9. WorkTraumaEvaluationComplete

          10. PostInjuryCheckupFinished

          Какой-то один реализован в коде, но ты не знаешь какой. Теперь найди его, зная только русское название, которое и употребляет бизнес, а значит в ТЗ тоже русское

          И это я названия заболеваний ещё не приводил


          1. Kerman
            07.07.2025 11:42

            Я не понимаю, почему я издеваюсь.

            Есть английский язык как стандарт для айти. Что это вообще за вопрос такой был: переведи медицинский термин туда и обратно? Зачем? Как это связано с темой разговора?

            Если у вас есть предметная область и вы пишете исходный код для системы, работающей в ней, у вас УЖЕ ЕСТЬ термины, от которых можно отталкиваться. У вас уже есть медицинское обследование. У вас есть производственная травма. Вам рассказать, как перевести на английский термин "после"?

            И, пожалуйста, не надо мне тыкать. Может оказаться, что я старше вас лет на 10.

            О. Вспомнил, кто вы такой. Отрицатель ООП. Тогда ладно. Мои аргументы бессильны. Сдаюсь.


            1. Dhwtj
              07.07.2025 11:42

              Бизнес говорит: нужно сделать выгрузку всех обследований в состоянии "Проведено медицинское обследование после производственной травмы", а таких состояний штук 30. Ладно, если они в явном виде перечислены в каком-то enum с комментариями и в одном месте программы. А если это просто тупой ключ в словаре / хешмэпе?

              Ну ладно состояние

              А если это поле? И нет карты однозначного соответствия бизнес термина (что я слышу от заказчика) и того что в коде или БД


              1. Kerman
                07.07.2025 11:42

                карты однозначного соответствия бизнес термина

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

                Но не хотите - не делайте. Кто я такой, чтобы вам указывать, что делать?

                Это ваши проблемы будут дальше, когда (или если) вы попадёте в другую компанию, а там будет всё незнакомо. Думаете почему люди стремятся стандартизировать что-то? Вот именно поэтому. Чтобы на одном языке шёл разговор.

                Как я уже сказал - международный язык для разработки - английский. Если вы пишете переменные по-русски, то я не имею права вас осуждать. Но осуждаю.


            1. Dhwtj
              07.07.2025 11:42

              Мои аргументы бессильны. Сдаюсь.

              Слабак! )))


  1. DMGarikk
    07.07.2025 11:42

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

    лучше подбирать примеры не настолько странно, ведь всё красиво, по математике верно

    ...а потом оказывается что это код - кусок парсера какихто данных где у значения b может быть вполне штатный показатель - 0 (нуль), а ф-ция возвращает какуюто расчетную ф-цию относительно него и эта цифра опциональна...по этому она возвращает None и вешать сюда еще и исключение - это будет излишнее усложнение кода

    тут вероятно правильным выходом переименовать divide в правильное название или более конкретно рассматривать поставленную задачу вместе с контекстом


  1. kaktus087
    07.07.2025 11:42

    Сюда же можно отнести использование PyCharm, потому что иначе смысла от аннотаций нет.

    А также использование виртуальных окружений под каждый отдельный проект.


    1. andreymal
      07.07.2025 11:42

      потому что иначе смысла от аннотаций нет.

      mypy, pyright, pylsp


      1. kaktus087
        07.07.2025 11:42

        Да, согласен. Но это уже не настолько базовые вещи.


  1. ABowser
    07.07.2025 11:42

    Давайте своим переменным понятные и очередные названия, например, если переменная содержит период сохранения каких-то данных, так и назовите её save_period, а не как-нибудь

    Вам бы сначала в английский, а ещё ранее, в следование тому, что сами советуете...