Сразу скажу я не величайший гуру и знаток всего на свете, я не прочитал чистый код от корки до корки, но всё же мне есть чем поделиться с окружающими.
Не факт, что кто-то это прочтёт или отнесётся серьёзно к прочтению, но возможно, если на эту статью наткнётся какой-нибудь новичок, то ему будет весьма полезно в двух словах понять основы красивого кода, а если это моё детище увидит человек с огромными познаниями и будет с чем-то не согласен, то я всегда готов услышать ваше мнение в комментариях под статьёй :)
Итак, приступим:
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.
str
- символьные строкиint
- целые числаfloat
- число с плавающей точкой (десятичные дроби)complex
- комплексные числаbool
- булевые значения (True/False)list
- списокdict
- словарьtuple
- кортежset
- множествоfrozenset
- неизменяемое множествоbytes
- байтовые последовательности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)
Andrey_Solomatin
07.07.2025 11:42def save_game(période: int):
Что за
перьёд
? Товаришь Chat J'ai peté постарался?DarkScara Автор
07.07.2025 11:42нет, просто я пользуюсь как английской раскладкой, так и французской
Kerman
07.07.2025 11:42Как писать красивый и чистый код питонистам?
А никак, бгггг
Шучу, серьёзный ответ будет: "а так же, как и всем остальным"
И про комментарии в корне не согласен. Если человек не может перевести "save_period", то пусть идёт в дворники. Тот, кто пишет комментарий "период сохранения" тоже пусть в дворники идёт. Комментарий должен быть "Период сохранения в секундах после последнего действия пользователя".
evgenyk
07.07.2025 11:42Ну вообще-то это переводится примерно так: "Сохрани период". С именами все очень и очень непросто.
Kerman
07.07.2025 11:42Я в курсе. И назвал бы переменную SaveDelayAfterLastActionSec, ой, простите, тут питоний, значит save_delay_after_last_action_sec
И вообще бы вынес нахер в константы в начало файла. Соответственно, капсом.
Вопрос: а нужен ли мне после этого вообще комментарий?
evgenyk
07.07.2025 11:42Может лучше saving_delay?
Kerman
07.07.2025 11:42нет, у saving другая семантика. Это слово обозначает экономию
evgenyk
07.07.2025 11:42In 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.
Kerman
07.07.2025 11:42Для этого нужно, чтобы saving был отглагольным существительным. Нужно указать, что именно оно сейвит. Как существительное само по себе - это экономия.
Dhwtj
07.07.2025 11:42. Если человек не может перевести "save_period", то пусть идёт в дворники
Переведи, пожалуйста сложный медицинский термин с русского на английский и обратно быстро и однозначно.
Kerman
07.07.2025 11:42Зачем? Медтермины обычно на латыни, пусть в латыни и остаются. Это общепризнанный общемировой формат
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
Какой-то один реализован в коде, но ты не знаешь какой. Теперь найди его, зная только русское название, которое и употребляет бизнес, а значит в ТЗ тоже русское
И это я названия заболеваний ещё не приводил
Kerman
07.07.2025 11:42Я не понимаю, почему я издеваюсь.
Есть английский язык как стандарт для айти. Что это вообще за вопрос такой был: переведи медицинский термин туда и обратно? Зачем? Как это связано с темой разговора?
Если у вас есть предметная область и вы пишете исходный код для системы, работающей в ней, у вас УЖЕ ЕСТЬ термины, от которых можно отталкиваться. У вас уже есть медицинское обследование. У вас есть производственная травма. Вам рассказать, как перевести на английский термин "после"?
И, пожалуйста, не надо мне тыкать. Может оказаться, что я старше вас лет на 10.
О. Вспомнил, кто вы такой. Отрицатель ООП. Тогда ладно. Мои аргументы бессильны. Сдаюсь.
Dhwtj
07.07.2025 11:42Бизнес говорит: нужно сделать выгрузку всех обследований в состоянии "Проведено медицинское обследование после производственной травмы", а таких состояний штук 30. Ладно, если они в явном виде перечислены в каком-то enum с комментариями и в одном месте программы. А если это просто тупой ключ в словаре / хешмэпе?
Ну ладно состояние
А если это поле? И нет карты однозначного соответствия бизнес термина (что я слышу от заказчика) и того что в коде или БД
Kerman
07.07.2025 11:42карты однозначного соответствия бизнес термина
Вообще карты соответствия делают даже англоговорящие разрабы. Просто, чтобы в коде и в обсуждениях с бизнесом термины совпадали.
Но не хотите - не делайте. Кто я такой, чтобы вам указывать, что делать?
Это ваши проблемы будут дальше, когда (или если) вы попадёте в другую компанию, а там будет всё незнакомо. Думаете почему люди стремятся стандартизировать что-то? Вот именно поэтому. Чтобы на одном языке шёл разговор.
Как я уже сказал - международный язык для разработки - английский. Если вы пишете переменные по-русски, то я не имею права вас осуждать. Но осуждаю.
DMGarikk
07.07.2025 11:42В случаи, если вы пытаетесь делить на ноль, чего нельзя делать в математике, то данная функция возвращает значение
None
. Это не самый правильный вариант, лучше использоватьraise
и "вызывать" конкретную ошибкулучше подбирать примеры не настолько странно, ведь всё красиво, по математике верно
...а потом оказывается что это код - кусок парсера какихто данных где у значения b может быть вполне штатный показатель - 0 (нуль), а ф-ция возвращает какуюто расчетную ф-цию относительно него и эта цифра опциональна...по этому она возвращает None и вешать сюда еще и исключение - это будет излишнее усложнение кода
тут вероятно правильным выходом переименовать divide в правильное название или более конкретно рассматривать поставленную задачу вместе с контекстом
kaktus087
07.07.2025 11:42Сюда же можно отнести использование PyCharm, потому что иначе смысла от аннотаций нет.
А также использование виртуальных окружений под каждый отдельный проект.
ABowser
07.07.2025 11:42Давайте своим переменным понятные и очередные названия, например, если переменная содержит период сохранения каких-то данных, так и назовите её
save_period
, а не как-нибудьВам бы сначала в английский, а ещё ранее, в следование тому, что сами советуете...
Andrey_Solomatin
Можно просто в отдельную функцию вынести.
Алиасы нужны для решения конфликта имён и только для этого. Добавление промежуточных имён редко делает код чище. Практики принятые в датасаенсе часто не являются примерам хорошего дизайна.
Мои инструменты это автоматизация. Форматер + линтер + статический анализ задают нижнюю планку качества кода на хорошем уровне. Мой личный шаблон https://github.com/Cjkjvfnby/project_template, давно не обновлялся, но всё еще актуальный.