Так ошибаются не только новички: 17 самых распространённых ошибок в коде Python

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Кладите этот список в закладки, его можно использовать как чек-лист для новичка, который уже пишет работающие программы, но ещё не успел споткнуться о подводные камни языка. Если вы только начали изучать Python, читайте эту статью, чтобы избегать неприятных неожиданностей, а ещё при желании посмотрите программу нашего курса по Fullstack-разработке на Python. Если же вы опытный разработчик, просмотрите эту статью на всякий случай: может быть и вы найдёте нечто интересное.


1. Синтаксис

Вы столкнётесь с синтаксической ошибкой, пропуская двоеточие в конце строки или случайно добавив пробел при отступе под оператором if.

2. Ошибка с отступами

Отступы в Python — не только для удобства чтения. Они указывают, к чему относится оператор. При этом ошибки отступа заметить сложнее, чем другие. К примеру, сложно отследить путаницу пробелов и табуляций. Когда табуляции вычисляются как представление пробелов, всё видимое редактором может не увидеть Python. Jupyter Notebook сам заменяет табуляции пробелами и даже бросает ошибки в большинстве случаев.

3. Ошибка с __init__

__init__ — встроенная в классы программа, в объектно-ориентированной терминологии — конструктор. Она выполняется при выделении памяти для нового объекта класса. Инициализация вызывается, когда из класса создаётся объект, и она позволяет инициализировать атрибуты класса. Цель — закрепить значения членов экземпляра для объекта класса. Попытка явно вернуть значение из метода __init__ означает, что программист отклоняется от цели. Это одна из фатальных ошибок Python.

4. Коллизии имён с именами в стандартной библиотеке

Python процветает благодаря стандартной библиотеке. Обычной ошибкой является использование имени модуля, идентичного имени модуля в стандартной библиотеке языка. В итоге импортирован будет модуль из стороннего пакета, а не стандартный модуль.

Правило LEGB

  • Local — локальные переменные функции или класса.

  • Enclosed — определённые внутри объемлющих функций (концепция вложенных функций).

  • Global — глобальные переменные, на самом высоком уровне вложенности.

  • Built-in — имена встроенных модулей и функций.

По сравнению с другими языками программирования Python использует уникальную стратегию определения области видимости переменных. Видимость переменных в Python основано на правиле LEGB.

Когда пользователь создаёт присвоение переменной в некоей области видимости, эта переменная автоматически признаётся Python локальной в данной области и затеняет любую переменную с тем же именем во внешней области. Особенно часто программисты ошибаются с LEGB, когда работают со списками.

5. Ошибки с выражениями

Python позволяет задавать аргументу функции произвольное значение. Эта черта языка замечательна, но, когда значение по умолчанию изменяемо (имеет мутабельный тип данных), она порождает хаос. Посмотрим определение функции:

>>> def foo(bar=[]):        # bar is voluntary and defaults to [] if not defined

...    bar.append("baz")    # but this line can be problematic

...    return bar

Часто путаницу вносит предположение, что значение необязательного аргумента указывать не нужно и что необязательный аргумент будет привязан к определённому по умолчанию выражению в каждой точке, где объявляется функция. Но на самом деле это не так.

6. Ошибка с параметрами исключения

Посмотрим на код:

>>> try:

...     l = ["a", "b"]

...     int(l[2])

... except ValueError, IndexError:  # To catch both exceptions, right?

...     pass

...

Traceback (latest dialled last):

  File "<stdin>", line 3, in <module>

IndexError: list index out of range

В чём проблема? Оператор except не принимает список исключений, как предполагается в этом коде. В Python 2.x этот синтаксис используется, чтобы обернуть исключение в определённый дополнительный второй параметр (в данном случае — e), чтобы открыть доступ к нему для дополнительного исследования.

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

Кроме того, ради максимальной переносимости задействуйте ключевое слово as, которое поддерживается в Python 2 и Python 3:

>>> try:

...     l = ["a", "b"]

...     int(l[2])

... except (ValueError, IndexError) as e:  

...     pass

...

>>>

7. Ошибка с областью видимости

Области видимости Python работают согласно правилу LEGB. Оно не так просто, как кажется. Показанная ниже ошибка в Python — одна из самых распространённых. И здесь мы приходим к необходимости программировать тонко:

>>> x = 10

>>> def foo():

...     x += 1

...     print x

...

>>> foo()

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

  File "<stdin>", line 2, in foo

UnboundLocalError: local variable 'x' referenced before assignment

В чём проблема? Ошибка возникает, когда вы выполняете присвоение переменной в какой-то области. Эта переменная по умолчанию признаётся Python локальной относительно данной области, то есть пропускается любая переменная в любой области с тем же именем. Так многие разработчики получают ошибку UnboundLocalError, когда пишут присвоение выше тела функции. Особенно часто это запутывает в работе со списками:

>>> lst = [1, 2, 3]

>>> def foo1():

...     lst.append(5)   # This goes well...

...

>>> foo1()

>>> lst

[1, 2, 3, 5]

 

>>> lst = [1, 2, 3]

>>> def foo2():

...     lst += [5]      # ...Not this one!

...

>>> foo2()

Traceback (latest call list):

  File "<stdin>", line 1, in <module>

  File "<stdin>", line 2, in foo

UnboundLocalError: local variable 'lst' referenced assignment

Почему работает foo1? И почему foo2 не работает? foo1 не выполняет присвоение lst, а в foo2 lst += [5] — это краткая форма lst = lst + [5].

8. Ошибка изменения списка при итерации

Проблема с кодом ниже очевидна:

>>> odd = lambda x : bool(x % 2)

>>> numbers = [n for n in range(10)]

>>> for i in range(len(numbers)):

...     if odd(numbцers[i]):

...         del numbers[i]  # BAD: Removing item from a list while repeating over it

...

Traceback (latest call last):

     File "<stdin>", line 2, in <module>

IndexError: list index out of range

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

К счастью, в Python есть несколько утончённых стандартов программирования, которые при правильном применении помогут создавать удивительно чёткий и структурированный код. И вот дополнительное преимущество: чем проще код, тем меньше вероятность ошибки из-за неожиданного удаления элемента списка с акцентом на нём.

9. Ошибка связывания переменных

Посмотрим на пример:

>>> def create_multipliers():

...     return [lambda x : i * x for i in range(5)]

>>> for multiplier in create_multipliers():

...     print multiplier(2)

...

Можно предположить такой вывод:

0

2

4

6

8

Но на самом деле вы получите это:

8

8

8

8

8

Такое случается из-за позднего связывания, которое гласит, что значения переменных в замыканиях видны в момент объявления внутренней функции. Эта ошибка — одна из самых распространённых.

Таким образом, в коде выше всякий раз, когда вызывается какая-либо из возвращаемых функций, её значение просматривается в соседней области в момент её вызова. К моменту завершения цикла оно затем присваивается конечному значению 4. И вот решающий проблему трюк:

>>> def create_multipliers():

...     return [lambda x, i=i : i * x for i in range(5)]

...

>>> for multiplier in create_multipliers():

...     print multiplier(2)

...

0

2

4

6

8

Это оно! Здесь мы задействуем мощь аргументов по умолчанию, чтобы создать псевдонимные функции и получить желаемое поведение. Одним это покажется сложностью, другим — тонкостью, а кто-то может вообще избегать подобного. Но понимать это важно в любом случае.

10. Ошибка циклических зависимостей

Пример: два файла, a.py и b.py, импортируют друг друга:

In a.py:

import b

def f():

    return b.x

print f()

In b.py:

import a

x = 1

def g():

    print a.f()

Для начала импортируем a.py:

>>> import a

1

Возможно, это ошеломляет вас. В конце концов, у нас здесь циклический импорт, который, вероятно, должен быть проблемой. Наличие циклического импорта само по себе в Python не проблема: если модуль уже импортирован, Python достаточно умён, чтобы не импортировать его снова.

Тем не менее в зависимости от положения, в котором каждый модуль пытается достичь функций или описанных в другом модуле переменных, проблемы кажутся очевидными.

Итак, вернёмся к нашей модели, где мы импортировали a.py, и он без проблем импортировал b.py, потому что b.py не нуждается ни в чём из a.py. Единственная ссылка в b.py на a — это вызов a.f(), но он находится в g() и ничего в a.py или b.py не вызывает g(). Вот что случится, если попытаться импортировать b.py до импорта a.py:

>>> import b

Traceback (latest call last):

     File "<stdin>", line 1, in <module>

     File "b.py", line 1, in <module>

    import a

     File "a.py", line 6, in <module>

print f()

     File "a.py", line 4, in f

return b.x

AttributeError: 'module' object has no attribute 'x'

b.py рискует импортировать a.py, который, в свою очередь, вызывает f() и пытается связаться с b.x — это действительно проблема, потому что b.x ещё не определён. Так возникает исключение AttributeError. Одно из решений проблемы довольно простое: измените b.py так, чтобы a.py импортировался внутри g():

x = 1

def g():

    import a # This will be assessed only when g() is called

    print a.f()

No, when we import it, everything seems perfect:

>>> import b

>>> b.g()

1 # Printed a first time since module 'a' calls 'print f()' at the end

1 # Printed a second time, this one is our call to 'g'

11. Ошибка совместимости Python 2 и Python 3

Посмотрим на такой файл foo.py:

import sys

def bar(i):

    if i == 1:

        raise KeyError(1)

    if i == 2:

        raise ValueError(2) 

def bad():

    e = None

    try:

        bar(int(sys.argv[1]))

    except KeyError as e:

        print('key error')

    except ValueError as e:

        print('value error')

    print(e)

bad()

On Python 2, this works well:

$ python foo.py 1

key error

1

$ python foo.py 2

value error

2

Переключаемся на Python 3:

$ python3 foo.py 1

key error

Traceback (latest call last):

  File "foo.py", line 19, in <module>

    bad()

  File "foo.py", line 17, in bad

    print(e)

UnboundLocalError: local variable 'e' referenced before assignment

Что произошло? В Python 3 объект исключения недоступен за пределами области действия блока except. Рационализация заключается в сохранении ссылочного цикла с кадром стека в памяти, пока сборщик мусора не очистит ссылки. Сохраните ссылку на объект исключения за пределами области действия блока except, чтобы он оставался доступным. Вот версия, где такой подход делает код совместимым с Python 2 и Python 3:

12. Ошибка с методом __del__

У нас есть файл mod.py:

import foo

class Bar(object):

        ...

    def __del__(self):

        foo.cleanup(self.myhandle)

Попытаемся вызвать метод в another_mod.py:

import mod

mybar = mod.Bar()

Ждите исключения об ошибке атрибута (AttributeError).

Почему? Когда интерпретатор закрывается, все глобальные переменные модуля привязаны к None, а значит, в предыдущем примере в момент вызова __del__ для имени foo уже было установлено значение None. Решением этой дилеммы программирования на Python — atexit.register().

Когда программа завершает выполнение (при обычном выходе), зарегистрированные обработчики запускаются до закрытия интерпретатора. Решение может выглядеть так:

import foo

import atexit 

def cleanup(handle):

    foo.cleanup(handle)

class Bar(object):

    def __init__(self):

        ...

        atexit.register(cleanup, self.myhandle)

Вот аккуратный и безопасный метод вызова любых необходимых функций очистки в обычном завершении программы. Конечно, что делать с привязанным к имени self.myhandle объектом, зависит от foo.cleanup.

13. Ошибка с идентичностью как равенством

Очень типичная ошибка — при сравнении чисел писать is вместо ==. Поскольку Python кэширует числа, ошибка может быть сокрыта. Чтобы лучше понять сказанное, посмотрим на два примера. Ниже у нас две переменные — sum и add. Каждая хранит сумму двух целых чисел. Сравним их через оператор равенства ==. Вернётся True, потому что значения одинаковы.

Теперь поэкспериментируем с оператором идентичности (is) — здесь тоже вернётся true, потому что Python выделил числам одинаковые адреса, которые можно увидеть по значениям id в коде ниже. Но программист может не быть осведомлён, как именно две отдельные операции (== и is) могут привести к одному и тому же результату, а значит, он может допустить ошибку и не узнать об этом:

Python 2.7.10 (default, Jul 14 2015, 19:46:27)

[GCC 4.8.2] on linux

   sum = 10 + 15

=> None

   add = 5 + 20

=> None

   sum == add

=> True

   sum

=> 25

   add

=> 25

   sum is add

=> True

   id(sum)

=> 25625528

   id(add)

=> 25625528

В следующем примере с длинными целыми числами это обойдётся дорого. Вот в чём хитрость: Python выделяет одинаковые адреса только числам от -5 до 256. Большие числа занимают отдельные адреса:

300 + 200 is 500

=> False

   300 + 200 == 500

=> True

Обращайте внимание на разницу в понятиях, не пишите конструкции вслепую.

14. Геттеры и сеттеры в стиле Java

В Java для доступа к членам класса широко используются геттеры и сеттеры, то есть методы получения и установки значений, но в Python это считается одной из самых распространённых ошибок.

Пример:

class Employee(object):

    def __init__(self, name, exp):

        self._name = name

        self._exp = exp

    # Java-style getter/setter

    def getName(self):

        return self._name

    def setName(self, name):

        self._name = name

    def getExp(self):

        return self._exp

    def setExp(self, exp):

        self._exp = exp

emp = Employee('techbeamers', 10)

print("Employee-1: ", emp.getName(), emp.getExp())

emp.setName('Python Programmer')

emp.setExp(20)

print("Employee-2: ", emp.getName(), emp.getExp())

Вот так это делается в Python:

class Employee(object):

    def __init__(self, name, exp):

        self.name = name

        self.exp = exp

emp = Employee('techbeamers', 10)

print("Default: ", emp.name, emp.exp)

emp.name = 'Python Programmer'

emp.exp = 20

print("Updated: ", emp.name, emp.exp)

15. Ошибка счёта

Указав диапазон (1, 11), вы получите выходные данные значений от 1 до 10.

>>> a = list(range(1, 11))

>>> a

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> a[0]

1

>>> a[0:5]

[1, 2, 3, 4, 5]

1

2

3

4

5

6

71

2

36

7

16. Ошибка с заглавными и строчными буквами

Если вы не можете получить доступ к значению, которого ожидали, присмотритесь к заглавным и строчным буквам. Python чувствителен к регистру: MyVar — это не myvar и не MYVAR:

>>> MyVar = 1

>>> MYVAR

Traceback (latest call last):

File "< stdin >", line 1, in < module >

NameError: name 'MYVAR' is not defined

1

2

3

4

5

17. Ошибка с переменными класса

>> class A(object):

... x = 1

...

>>> class B(A):

... pass

...

>>> class C(A):

... pass

...

>>> print A.x, B.x, C.x

1 1 1

1

2

3

4

5

6

7

8

9

10

11

Может иметь смысл пример ниже:

>> B.x = 2

>>> print A.x, B.x, C.x

1 2 1

1

2

3

# И снова.

>>> A.x = 3

>>> print A.x, B.x, C.x

3 2 3

1

2

3

Что заставило измениться C.x, если мы изменили только A.x? Переменные класса в Python имеют внутреннюю обработку в виде словарей и подчиняются порядку разрешения методов (MRO).

Вот почему вы увидите свойство x только в его базовых классах (только A — в более раннем экземпляре, хотя Python поощряет множественное наследование), поскольку класс C не содержит отдельного и отделённого от A свойства x. И точно так же источники C.x — это на самом деле ссылки на A.x. Без правильного управления это поведение станет проблемой.

Заключение

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

Python, веб-разработка

  • Профессия Fullstack-разработчик на Python

  • Курс «Python для веб-разработки»

  • Профессия Frontend-разработчик

  • Профессия Веб-разработчик

Data Science и Machine Learning

  • Профессия Data Scientist

  • Профессия Data Analyst

  • Курс «Математика для Data Science»

  • Курс «Математика и Machine Learning для Data Science»

  • Курс по Data Engineering

  • Курс «Machine Learning и Deep Learning»

  • Курс по Machine Learning

Мобильная разработка

  • Профессия iOS-разработчик

  • Профессия Android-разработчик

Java и C#

  • Профессия Java-разработчик

  • Профессия QA-инженер на JAVA

  • Профессия C#-разработчик

  • Профессия Разработчик игр на Unity

От основ — в глубину

  • Курс «Алгоритмы и структуры данных»

  • Профессия C++ разработчик

  • Профессия Этичный хакер

А также:

  • Курс по DevOps

Источник: https://habr.com/ru/company/skillfactory/blog/576334/


Интересные статьи

Интересные статьи

На сайте nalog.ru есть очень удобный сервис, который «покрывает» такие страхи владельца бизнеса как увод компании из под контроля без участия самого владельца. Отчасти естественно «покр...
Что приходит Вам в голову, когда Вы слышите “низкоуровневое программирование”? Может быть, C++? Непрекращающийся контроль указателей, попытки оптимизации быстродействия, потребляемой памя...
В первой части статьи мы поговорили о том, что такое ANR (Application Not Responding), и рассмотрели несколько способов сбора информации об этих ошибках. А сегодня я расс...
Моя самая любимая часть в статическом анализе кода — это выдвижение гипотез о потенциальных ошибках в коде с последующей их проверкой. Пример гипотезы: Функции strpos легко передать аргументы в...
Нет, чернобыльская радиация не повредила вашему ребёнку Статья Майкла Шелленбергера – известного автора и колумниста, пишущего про энергию и окружающую среду. С начала мини-сериала «Черно...