Сразу скажу я не величайший гуру и знаток всего на свете, я не прочитал чистый код от корки до корки, но всё же мне есть чем поделиться с окружающими.
Не факт, что кто-то это прочтёт или отнесётся серьёзно к прочтению, но возможно, если на эту статью наткнётся какой-нибудь новичок, то ему будет весьма полезно в двух словах понять основы красивого кода, а если это моё детище увидит человек с огромными познаниями и будет с чем-то не согласен, то я всегда готов услышать ваше мнение в комментариях под статьёй :)
Итак, приступим:
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. Данная статья преимущественно рассчитана на начинающих ребят, но я буду рад реакций от людей любого уровня :-)