Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру 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