Python Russian 09 апреля 2024

🐍❌ 10 основных ошибок начинающих Python-разработчиков

За годы общения с людьми, которые только начинают свой путь в Python, я уже привык видеть одни и те же совершаемые ими ошибки при освоении этого прекрасного и обманчиво-легкого языка. В данной статье хочу показать самые частые из подобных ошибок и дать советы по их решению.
🐍❌ 10 основных ошибок начинающих Python-разработчиков
Автор: Python Russian, youtube.com/@PythonRussian

Но начать хочу с истины, которая не всем и не всегда очевидна: мы читаем код гораздо чаще, чем пишем его. По сути, программирование — это многократное чтение кода с периодическим его написанием. Именно по этой причине и проблемы, указанные ниже, и их решения так связаны именно с читаемостью кода. Читаемость особо критична при работе в команде, когда вы должны читать и понимать код других людей, а они должны понимать и поддерживать ваш код. Но и при самостоятельной разработке, когда вы в гордом одиночестве, читаемость важна, потому что через 2-3 месяца (проверено опытом) вы просто сами не сможете понять, что написали. Понимая важность читаемости кода в сообществе Python принято придерживаться общего стиля написания кода (Pep-8), а также следовать дзену, основные положения которого можно увидеть, набрав в консоли Python команду import this.

А теперь перейдем к ошибкам.

Основные ошибки

1. Нельзя начать бегать, не научившись ходить

Первую ошибку можно философски назвать «нельзя начать бегать, не научившись ходить». Она «бьет» новичка текстом "ModuleNotFoundError: No module named 'telebot'", но могут быть и другие проявления. Не секрет, что многие новички приходят в сообщество Python с желанием написать бота для Телеграма или Дискорда.Обычно при этом знания языка нулевые: в лучшем случае просмотрены несколько видео в стиле «Пишем бота за 10 минут на Python». Ни разу на моей памяти это не кончалось хорошо, так как написание некоего приложения чаще всего заканчивается неудачей, разочарованием в Python или в программировании в целом. Это неудержимое желание сразу с порога, ничего не зная, написать что-то большое и на этом научиться всему, очень многих приводит в стан хейтеров питона, ведь легче винить язык, чем себя. Нельзя написать сложное приложение, не зная основ языка, даже если есть сильное желание и пагубная мысль «по ходу дела научусь».

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

Читать: Марк Лутц «Изучаем Python» Что нужно знать для написания бота: asyncio (если бот асинхроный), ООП, декораторы, структуры данных, работу с модулями и импортами, работу с БД.

🐍 Библиотека питониста
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста»
🐍🎓 Библиотека собеса по Python
Подтянуть свои знания по Python вы можете на нашем телеграм-канале «Библиотека собеса по Python»
🐍🧩 Библиотека задач по Python
Интересные задачи по Python для практики можно найти на нашем телеграм-канале «Библиотека задач по Python»

2. Безразличие к именованию

Это важнейшая проблема, с которой нужно бороться с самого начала освоения языка. Даже в Дзене Питона, который выводится в консоли по команде import this, есть строка Readability counts. Читаемость важна, как мы упоминали выше. Верную привычку надо вырабатывать с самого начала, иначе, наоборот – происходит привыкание к написанию неверных имен и потом от нее сложно избавиться. Примеры:

        l = [1, 2, 3]
s = "Text"
x = ("Ivanov Ivan", "male", 22)
y = ("Petrov Petr", "male", 21)
l1 = [x, y]
    

Как правильно:

        integers = [1, 2, 3] # как вариант a_list
text = "Text"
ivan = ("Ivanov Ivan", "male", 22)
petr = ("Petrov Petr", "male", 21)
students = [ivan, petr]
    

Имя для переменной, структуры данных, функции, класса должно отражать, что оно собой представляет. Если это просто некое значение, то пусть будет 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-строк нет такой проблемы.

Плохо:

        hello = 'Hello'
world = 'World'
print(hello + ", " + world)
print("{}, {}".format(hello, world))
    

Хорошо:

        print(f"{hello}, {world}")
hello_world = f"{hello}, {world}" # сформировали строку без конкатенации, то есть не создавая нескольких объектов

    

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 или сокращенно листкомпс — это мощный и очень удобный инструмент для работы со списками, однако использовать его нужно, только если нам нужно менять данные и/или фильтровать их. Если нет необходимости ни в фильтрации, ни в преобразовании, то не нужно использовать листкомпс, он только затрудняет понимание происходящего, используйте встроенную функцию.

Пример:

        evens = [e for e in range(10) if e%2==0] # ok, фильтруем только четные
squares = [e**2 for e in range(10)] # ok, возводим все в квадрат
a_list = [e for e in {1, 2, 3}] # не правильно, нет ни преобразований ни фильтров, нужно использовать list()
a_list = list({1, 2, 3}) # oka_list = list({1, 2, 3}) # ok
    

7. Чрезмерное увлечение списками

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

Альтернативы списку:

  • тапл (кортеж) работает быстрее, чем список и занимает меньше памяти, если не планируем менять данные, то нужно рассмотреть его.
  • очереди (queue, deque) более подходят для операций вставки в начало или конец, кроме того они потокобезопасны.
  • нередко список нам вообще не нужен прямо здесь и сейчас в готовом виде (ибо занимает память), лучше использовать генэксп, например gen = (e**2 for e in range(1000_000)) не займет память в отличие от списка, но аналогично может быть потом использован в цикле.
  • сет (множество) быстрее проверит на наличие элемента, если нам не нужны дубликаты, то лучше использовать его.

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

8. Использование индекса элемента, где он не нужен

Использование индекса элемента, где он не нужен, паттерн range(len(a_list)) Обычно это происходит у тех, кто пришел из других языков программирования, обычный цикл итерации для них это перебор по индексам

        a_list = [1, 2, 3]
for e in range(len(a_list)):
    print(a_list[e]) # обратите внимание, что индекс нам нужен только чтобы получить новый элемент
    

Питоничный способ сделать то же самое:

        for e in a_list:
    print(e)
    

Если же нам все таки нужны индексы, например хотим печатать только элементы с четными индексами, то правильнее использовать enumerate

        for index, element in enumerate(a_list):
    if index % 2 == 0:
        print(element)
    

Читать о enumerate: https://docs.python.org/3.12/library/functions.html#enumerate

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

Написание своих велосипедов, вместо использования встроенных функций (all, any, bin). Это связано опять же с плохим знанием тех батареек, что встроены в язык. По моему опыту чаще всего страдают функции all и any в том плане, что именно их новички реализуют самостоятельно в своем коде, не зная, что все давно написано за них. Новички обычно знакомы со str, int, list, sum, min, max, но когда речь заходит о получении двоичного представления числа или получения информации, что все элементы структуры данных удовлетворяют условию, то начинают писать реализации самостоятельно.

Пример:

        def has_evens(a_list):
    for element in a_list:
        if element % 2 == 0:
            return True
    return False

    

По сути, мы написали реализацию функции any, можно заменить так:

        any(e%2 == 0 for e in a_list) # обратите внимание, что используем генэксп, а не листкомпс, согласно пункта 7
    

10. «Проглатывание» исключений или использование неконкретных исключений

Мы перехватываем исключения и работаем с ними, чтобы понимать, что в программе идет не так и реагируем на подобные исключительные ситуации, но делать это нужно с пониманием того, что и зачем мы делаем. Частое проявление проблемы: «я запустил, но ничего не происходит и ничего не падает».

а) проглатывание исключения в стиле

        try:
    some_function()
except:
    pass
    

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

б) недостаток данных

        try:
    some_function()
except Exception:
    print("Some error")
    

Тут мы, конечно, сообщили о проблеме, но все еще не знаем, в чем она содержится, так как нередко наша some_function может выбрасывать разные исключения.

в) неконкретное исключение

        try:
    some_function()
except Exception as exc:
    print(exc)
    

Здесь мы вывели исключение, но читающему неясно, почему мы перехватываем не конкретное исключение. У него складывается впечатление, что some_function может выбросить, что угодно.

Правильно:

        try:
    some_function()
except SomeFunctionRealError as exc:
    print(exc) 
    # do something
    

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

***

В принципе все ошибки и проблемы, описанные выше, проистекают просто от незнания или недостаточного освоения основ Python и большинство из них просто исчезает по мере накопления опыта. Однако для ускорения этого процесса и раннего формирования правильных привычек я и перечислил то, что встречается наиболее часто. Крайне полезным считаю ознакомление с Pep-8, общего для всех стиля написания кода, а также напоминаю не забывать про Zen of Python

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
PHP Developer
от 200000 RUB до 270000 RUB
Продуктовый аналитик
Екатеринбург, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ