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

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию

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