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

Data Science и Machine Learning

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

Java и C#

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

А также:

Комментарии (19)