🐍❌ 10 основных ошибок начинающих Python-разработчиков
За годы общения с людьми, которые только начинают свой путь в Python, я уже привык видеть одни и те же совершаемые ими ошибки при освоении этого прекрасного и обманчиво-легкого языка. В данной статье хочу показать самые частые из подобных ошибок и дать советы по их решению.
Но начать хочу с истины, которая не всем и не всегда очевидна: мы читаем код гораздо чаще, чем пишем его. По сути, программирование — это многократное чтение кода с периодическим его написанием. Именно по этой причине и проблемы, указанные ниже, и их решения так связаны именно с читаемостью кода. Читаемость особо критична при работе в команде, когда вы должны читать и понимать код других людей, а они должны понимать и поддерживать ваш код. Но и при самостоятельной разработке, когда вы в гордом одиночестве, читаемость важна, потому что через 2-3 месяца (проверено опытом) вы просто сами не сможете понять, что написали. Понимая важность читаемости кода в сообществе Python принято придерживаться общего стиля написания кода (Pep-8), а также следовать дзену, основные положения которого можно увидеть, набрав в консоли Python команду import this
.
А теперь перейдем к ошибкам.
Основные ошибки
1. Нельзя начать бегать, не научившись ходить
Первую ошибку можно философски назвать «нельзя начать бегать, не научившись ходить». Она «бьет» новичка текстом "ModuleNotFoundError: No module named 'telebot'"
, но могут быть и другие проявления. Не секрет, что многие новички приходят в сообщество Python с желанием написать бота для Телеграма или Дискорда.Обычно при этом знания языка нулевые: в лучшем случае просмотрены несколько видео в стиле «Пишем бота за 10 минут на Python». Ни разу на моей памяти это не кончалось хорошо, так как написание некоего приложения чаще всего заканчивается неудачей, разочарованием в Python или в программировании в целом. Это неудержимое желание сразу с порога, ничего не зная, написать что-то большое и на этом научиться всему, очень многих приводит в стан хейтеров питона, ведь легче винить язык, чем себя. Нельзя написать сложное приложение, не зная основ языка, даже если есть сильное желание и пагубная мысль «по ходу дела научусь».
Решение: Начать с основ. Бот никуда не убежит — просто отложите его реализацию до того, как Python не будет освоен хотя бы в рамках первого тома Марка Лутца.
Читать: Марк Лутц «Изучаем Python» Что нужно знать для написания бота: asyncio (если бот асинхроный), ООП, декораторы, структуры данных, работу с модулями и импортами, работу с БД.
2. Безразличие к именованию
Это важнейшая проблема, с которой нужно бороться с самого начала освоения языка. Даже в Дзене Питона, который выводится в консоли по команде import this
, есть строка Readability counts. Читаемость важна, как мы упоминали выше. Верную привычку надо вырабатывать с самого начала, иначе, наоборот – происходит привыкание к написанию неверных имен и потом от нее сложно избавиться. Примеры:
Как правильно:
Имя для переменной, структуры данных, функции, класса должно отражать, что оно собой представляет. Если это просто некое значение, то пусть будет value, если это студенты, то students и так далее. Чтобы читающий мог только по названию понять, что хранится внутри переменной или что делает функция. Нужно бороться с желанием называть все одной буквой или буквой с индексом, типа x1. Читатели вашего кода, к которым относитесь и вы, через несколько месяцев, скажут вам спасибо.
Важное исключение: на своей практике я встречал ситуацию, которая является исключением из данного правила — продукт, который использовался учеными-физиками, у которых целый ряд коэффициентов имеют краткую форму в стиле ac_1, но при этом вполне понятную читающим, потому что именно так они именуют некоторые свои величины и называть как-то по-другому, как раз нельзя, чтобы не испортить читаемость.
Читать: https://peps.python.org/pep-0008/
3. Использование в именах названия встроенных функций и библиотек
Интерпретатор нас не предупредит и не запретит написать list = [1, 2, 3]
, но после такой записи мы «перекрыли» доступ к встроенной функции и теперь list ссылается на наш конкретный список из 3-х элементов, что может вызвать очень много проблем при работе с таким кодом в дальнейшем. Поэтому стоит избегать называть свои переменные, модули именами встроенных функций и библиотек. Самые частые жертвы такого использования: list
, str
, string
(модуль в стандартной библиотеке), dict
, set
, id
, filter
, json
, all
.
Решение: как и в пункте 2 тщательнее продумывать именование, знать встроенные функции и библиотеки. Встроенные функции: https://docs.python.org/3.12/library/functions.html#built-in-funcs
4. Использование старых форматов работы со строкой и конкатенация строк
Уже давно появились модные и молодежные f-строки, которые не только проще, но и читаемее, легче в усвоении. Если вы хотите использовать % или format, то стоит остановиться и подумать, как проще реализовать задуманное с помощью f-строк. Кроме того, напоминаю, что при сложении (конкатенации) строк мы каждый раз создаем новый объект и чем больше у нас таких сложений, тем больше объектов создается. У f-строк нет такой проблемы.
Плохо:
Хорошо:
5. Выполнение лишней работы
Самый яркий пример, который не теряет с годами своей популярности — это value = str(input())
В данном случае получив строку, которая нам вернет встроенная функция input мы еще раз пытаемся превратить ее в строку, что совершенно излишне — строка от этого «строковее» не станет. Сам этот паттерн str(input())
встречается нередко в коде начинающих и явно имеет какой-то источник, но я пока так и не сумел отыскать его и понять, откуда пошла эта мода на приведение инпута к строке. Буду рад, если кто-то приоткроет завесу тайны и сообщит источник этого подхода.
Еще, как пример, value = a_dict.get("some_key", None)
встречается гораздо реже, но суть та же самая: мы просим вернуть нам из словаря значение по ключу "some_key", а если его нет, то вернуть None. Но функция get у словаря делает это по умолчанию, потому это излишне. То есть можно упростить до value = a_dict.get("some_key")
Не стоит тратить время и ресурсы компьютера на то, что выполняется по-умолчанию.
6. Использование листкомпс вместо встроенной функции
List comprehension или сокращенно листкомпс — это мощный и очень удобный инструмент для работы со списками, однако использовать его нужно, только если нам нужно менять данные и/или фильтровать их. Если нет необходимости ни в фильтрации, ни в преобразовании, то не нужно использовать листкомпс, он только затрудняет понимание происходящего, используйте встроенную функцию.
Пример:
7. Чрезмерное увлечение списками
Проблема в том, что список в Python очень прост и удобен и с него начинается знакомство со структурами данных. Его используют в обучающих примерах, и мы быстро привыкаем к нему. Это приводит к тому, что список начинают использовать везде, игнорируя более выгодные подходы и структуры данных. При написании кода рука сама тянется к кнопке с открывающей квадратной скобкой и с этой привычкой надо бороться.
Альтернативы списку:
- тапл (кортеж) работает быстрее, чем список и занимает меньше памяти, если не планируем менять данные, то нужно рассмотреть его.
- очереди (queue, deque) более подходят для операций вставки в начало или конец, кроме того они потокобезопасны.
- нередко список нам вообще не нужен прямо здесь и сейчас в готовом виде (ибо занимает память), лучше использовать генэксп, например
gen = (e**2 for e in range(1000_000))
не займет память в отличие от списка, но аналогично может быть потом использован в цикле. - сет (множество) быстрее проверит на наличие элемента, если нам не нужны дубликаты, то лучше использовать его.
Решение: знать другие структуры данных, понимать их преимущества для различных ситуаций
8. Использование индекса элемента, где он не нужен
Использование индекса элемента, где он не нужен, паттерн range(len(a_list))
Обычно это происходит у тех, кто пришел из других языков программирования, обычный цикл итерации для них это перебор по индексам
Питоничный способ сделать то же самое:
Если же нам все таки нужны индексы, например хотим печатать только элементы с четными индексами, то правильнее использовать enumerate
Читать о enumerate: https://docs.python.org/3.12/library/functions.html#enumerate
9. Написание своих велосипедов, вместо использования встроенных функций
Написание своих велосипедов, вместо использования встроенных функций (all, any, bin). Это связано опять же с плохим знанием тех батареек, что встроены в язык. По моему опыту чаще всего страдают функции all
и any
в том плане, что именно их новички реализуют самостоятельно в своем коде, не зная, что все давно написано за них. Новички обычно знакомы со str, int, list, sum, min, max, но когда речь заходит о получении двоичного представления числа или получения информации, что все элементы структуры данных удовлетворяют условию, то начинают писать реализации самостоятельно.
Пример:
По сути, мы написали реализацию функции any, можно заменить так:
10. «Проглатывание» исключений или использование неконкретных исключений
Мы перехватываем исключения и работаем с ними, чтобы понимать, что в программе идет не так и реагируем на подобные исключительные ситуации, но делать это нужно с пониманием того, что и зачем мы делаем. Частое проявление проблемы: «я запустил, но ничего не происходит и ничего не падает».
а) проглатывание исключения в стиле
Здесь, в случае проблемы мы не только не знаем, что именно произошло, но и не предприняли ничего для исправления — просто продолжаем работу. Это самый вредный паттерн в работе, так как мы перехватываем почти все исключения и не знаем, что упало.
б) недостаток данных
Тут мы, конечно, сообщили о проблеме, но все еще не знаем, в чем она содержится, так как нередко наша some_function может выбрасывать разные исключения.
в) неконкретное исключение
Здесь мы вывели исключение, но читающему неясно, почему мы перехватываем не конкретное исключение. У него складывается впечатление, что some_function может выбросить, что угодно.
Правильно:
Мы перехватываем только конкретное исключение, которое может реально выбросить наша функция, сообщаем об этом и предпринимаем при необходимости какие-то действия. При этом, если исключение будет другое, то мы тут совершенно справедливо упадем, завершив программу, потому что произошло то, чего мы не ждали. А читающему наш код вполне понятно, что функция выбрасывает только это конкретное исключение и ничего другого не ждет.
В принципе все ошибки и проблемы, описанные выше, проистекают просто от незнания или недостаточного освоения основ Python и большинство из них просто исчезает по мере накопления опыта. Однако для ускорения этого процесса и раннего формирования правильных привычек я и перечислил то, что встречается наиболее часто. Крайне полезным считаю ознакомление с Pep-8, общего для всех стиля написания кода, а также напоминаю не забывать про Zen of Python