Python 2.x получил ошибки и мины

Цель моего вопроса - укрепить свою базу знаний с помощью Python и получить более полное представление о ней, которая включает в себя знание ее недостатков и неожиданностей. Если говорить конкретно, меня интересует только интерпретатор CPython.

Я ищу что-то похожее на то, что узнал из моего вопроса о наземных минах PHP, где некоторые ответы были мне хорошо известны, но пара была ужасающей.

Обновление: Видимо, один, может быть, два человека расстроены тем, что я задал вопрос, на который уже частично ответили за пределами переполнения стека. В качестве своего рода компромисса вот URL http://www.ferg.org/projects/python_gotchas.html

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

22 ответа

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

Пример: рассмотрим значение по умолчанию для аргумента текущего времени:

>>>import time
>>> def report(when=time.time()):
...     print when
...
>>> report()
1210294387.19
>>> time.sleep(5)
>>> report()
1210294387.19

when аргумент не меняется. Он оценивается при определении функции. Он не изменится, пока приложение не будет перезапущено.

Стратегия: вы не споткнетесь об этом, если вы по умолчанию None а затем сделайте что-нибудь полезное, когда увидите это:

>>> def report(when=None):
...     if when is None:
...         when = time.time()
...     print when
...
>>> report()
1210294762.29
>>> time.sleep(5)
>>> report()
1210294772.23

Упражнение: чтобы убедиться, что вы поняли: почему это происходит?

>>> def spam(eggs=[]):
...     eggs.append("spam")
...     return eggs
...
>>> spam()
['spam']
>>> spam()
['spam', 'spam']
>>> spam()
['spam', 'spam', 'spam']
>>> spam()
['spam', 'spam', 'spam', 'spam']

Вы должны знать, как переменные класса обрабатываются в Python. Рассмотрим следующую иерархию классов:

class AAA(object):
    x = 1

class BBB(AAA):
    pass

class CCC(AAA):
    pass

Теперь проверьте вывод следующего кода:

>>> print AAA.x, BBB.x, CCC.x
1 1 1
>>> BBB.x = 2
>>> print AAA.x, BBB.x, CCC.x
1 2 1
>>> AAA.x = 3
>>> print AAA.x, BBB.x, CCC.x
3 2 3

Удивлены? Вы не будете, если будете помнить, что переменные класса внутренне обрабатываются как словари объекта класса. Для операций чтения, если имя переменной не найдено в словаре текущего класса, его ищут в родительских классах. Итак, следующий код снова, но с пояснениями:

# AAA: {'x': 1}, BBB: {}, CCC: {}
>>> print AAA.x, BBB.x, CCC.x
1 1 1
>>> BBB.x = 2
# AAA: {'x': 1}, BBB: {'x': 2}, CCC: {}
>>> print AAA.x, BBB.x, CCC.x
1 2 1
>>> AAA.x = 3
# AAA: {'x': 3}, BBB: {'x': 2}, CCC: {}
>>> print AAA.x, BBB.x, CCC.x
3 2 3

То же самое касается обработки переменных класса в экземплярах классов (этот пример рассматривается как продолжение предыдущего):

>>> a = AAA()
# a: {}, AAA: {'x': 3}
>>> print a.x, AAA.x
3 3
>>> a.x = 4
# a: {'x': 4}, AAA: {'x': 3}
>>> print a.x, AAA.x
4 3

Циклы и лямбды (или любое замыкание, действительно): переменные связаны по имени

funcs = []
for x in range(5):
  funcs.append(lambda: x)

[f() for f in funcs]
# output:
# 4 4 4 4 4

Обходной путь - создание отдельной функции или передача аргументов по имени:

funcs = []
for x in range(5):
  funcs.append(lambda x=x: x)
[f() for f in funcs]
# output:
# 0 1 2 3 4

Динамическое связывание делает нахождение в ваших именах переменных на удивление трудным для поиска. Легко потратить полчаса на исправление тривиальной ошибки.

РЕДАКТИРОВАТЬ: пример...

for item in some_list:
    ... # lots of code
... # more code
for tiem in some_other_list:
    process(item) # oops!

Один из самых больших сюрпризов, которые у меня когда-либо были с Python:

a = ([42],)
a[0] += [43, 44]

Это работает, как и следовало ожидать, за исключением вызова ошибки TypeError после обновления первой записи кортежа! Так a будет ([42, 43, 44],) после выполнения += заявление, но все равно будет исключение. Если вы попробуете это с другой стороны

a = ([42],)
b = a[0]
b += [43, 44]

вы не получите ошибку.

try:
    int("z")
except IndexError, ValueError:
    pass

причина, по которой это не работает, заключается в том, что IndexError - это тип исключения, которое вы перехватываете, а ValueError - это имя переменной, которой вы назначаете исключение.

Правильный код для перехвата нескольких исключений:

try:
    int("z")
except (IndexError, ValueError):
    pass

Некоторое время назад было много дискуссий о скрытых языковых возможностях: hidden-features-of-python. Где были упомянуты некоторые подводные камни (и некоторые хорошие вещи тоже).

Также вы можете проверить Python Warts.

Но для меня целочисленное деление - это гоча:

>>> 5/2
2

Вы, вероятно, хотели:

>>> 5*1.0/2
2.5

Если вы действительно хотите это (C-like) поведение, вы должны написать:

>>> 5//2
2

Как это будет работать и с плавающими (и это будет работать, когда вы в конечном итоге перейдете на Python 3):

>>> 5*1.0//2
2.0

GvR объясняет, как целочисленное деление стало работать так же, как и в истории Python.

Нарезка списков доставила мне много горя. Я на самом деле считаю следующее поведение ошибкой.

Определить список х

>>> x = [10, 20, 30, 40, 50]

Индекс доступа 2:

>>> x[2]
30

Как вы ожидаете.

Разрезать список от индекса 2 до конца списка:

>>> x[2:]
[30, 40, 50]

Как вы ожидаете.

Индекс доступа 7:

>>> x[7]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

Опять же, как вы ожидаете.

Тем не менее, попробуйте нарезать список от индекса 7 до конца списка:

>>> x[7:]
[]

???

Средство состоит в том, чтобы поставить много тестов при использовании нарезки списка. Я хотел бы просто получить ошибку вместо этого. Намного легче отлаживать.

Не включая __init__.py в ваших пакетах. Этот до сих пор иногда получает меня.

Единственное замечание / сюрприз, с которым я имел дело, - это GIL от CPython. Если по какой-либо причине вы ожидаете, что потоки Python в CPython будут работать одновременно... ну, это не так, и это довольно хорошо задокументировано толпой Python и даже самим Гвидо.

Длинное, но подробное объяснение многопоточности CPython и некоторых вещей, происходящих под капотом, и почему настоящий параллелизм с CPython невозможен. http://jessenoller.com/2009/02/01/python-threads-and-the-global-interpreter-lock/

Джеймс Дюмей красноречиво напомнил мне еще одну ошибку Питона:

Не все "включенные батареи" Python замечательны.

Конкретным примером Джеймса были библиотеки HTTP: httplib, urllib, urllib2, urlparse, mimetools, а также ftplib, Некоторые функции дублируются, а некоторые ожидаемые функции полностью отсутствуют, например, обработка перенаправления. Честно говоря, это ужасно.

Если мне когда-нибудь придется что-то захватывать через HTTP, я использую модуль urlgrabber, разветвленный в проекте Yum.

def f():
    x += 1

x = 42
f()

приводит к UnboundLocalErrorпотому что локальные имена обнаруживаются статически. Другой пример будет

def f():
    print x
    x = 43

x = 42
f()

Плавания не печатаются с полной точностью по умолчанию (без repr):

x = 1.0 / 3
y = 0.333333333333
print x  #: 0.333333333333
print y  #: 0.333333333333
print x == y  #: False

repr печатает слишком много цифр:

print repr(x)  #: 0.33333333333333331
print repr(y)  #: 0.33333333333300003
print x == 0.3333333333333333  #: True

Непреднамеренное смешивание классов oldstyle и newstyle может привести к загадочным ошибкам.

Скажем, у вас есть простая иерархия классов, состоящая из суперкласса A и подкласса B. Когда создается экземпляр B, конструктор A должен вызываться первым. Код ниже правильно делает это:

class A(object):
    def __init__(self):
        self.a = 1

class B(A):
    def __init__(self):
        super(B, self).__init__()
        self.b = 1

b = B()

Но если вы забыли сделать класс newstyle и определить его следующим образом:

class A:
    def __init__(self):
        self.a = 1

Вы получаете эту трассировку:

Traceback (most recent call last):
  File "AB.py", line 11, in <module>
    b = B()
  File "AB.py", line 7, in __init__
    super(B, self).__init__()
TypeError: super() argument 1 must be type, not classobj

Два других вопроса, касающихся этой проблемы, являются 489269 и 770134

x += [...] это не то же самое, что x = x + [...] когда x это список`

>>> x = y = [1,2,3]
>>> x = x + [4]
>>> x == y
False

>>> x = y = [1,2,3]
>>> x += [4]
>>> x == y
True

Один создает новый список, а другой изменяет на месте

Вы не можете использовать locals()['x'] = что угодно, чтобы изменить значения локальной переменной, как вы могли ожидать.

This works:

>>> x = 1
>>> x
1
>>> locals()['x'] = 2
>>> x
2

BUT:

>>> def test():
...     x = 1
...     print x
...     locals()['x'] = 2
...     print x  # *** prints 1, not 2 ***
...
>>> test()
1
1

Это фактически сожгло меня в ответе здесь на SO, так как я проверил это вне функции и получил изменение, которое я хотел. Впоследствии я обнаружил, что это упоминается и противопоставляется случаю globals() в "Dive Into Python". Смотрите пример 8.12. (Хотя это не замечает, что изменение через localals () будет работать на верхнем уровне, как я покажу выше.)

Повторение списка с вложенными списками

Это застало меня сегодня врасплох и потратило час на отладку:

>>> x = [[]]*5
>>> x[0].append(0)

# Expect x equals [[0], [], [], [], []]
>>> x
[[0], [0], [0], [0], [0]]   # Oh dear

Объяснение: проблема со списком Python

Python 2 имеет несколько удивительное поведение при сравнении:

>>> print x
0
>>> print y
1
>>> x < y
False

В чем дело? repr() в помощь:

>>> print "x: %r, y: %r" % (x, y)
x: '0', y: 1

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

class Foo(object):
    x = {}

Но:

>>> f1 = Foo()
>>> f2 = Foo()
>>> f1.x['a'] = 'b'
>>> f2.x
{'a': 'b'}

Вы почти всегда хотите переменные экземпляра, которые требуют назначения внутри __init__:

class Foo(object):
    def __init__(self):
        self.x = {}

Если вы присваиваете переменную внутри функции, Python предполагает, что переменная определена внутри этой функции:

>>> x = 1
>>> def increase_x():
...     x += 1
... 
>>> increase_x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in increase_x
UnboundLocalError: local variable 'x' referenced before assignment

использование global x (или же nonlocal x в Python 3) чтобы объявить, что вы хотите установить переменную, определенную вне вашей функции.

Из-за "правдивости" это имеет смысл:

>>>bool(1)
True

но вы не можете ожидать, что это пойдет другим путем:

>>>float(True)
1.0

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

Значения range(end_val) не только строго меньше, чем end_val, но строго меньше, чем int(end_val), Для float аргумент rangeэто может быть неожиданным результатом:

from future.builtins import range
list(range(2.89))
[0, 1]

Если вы создаете список таким образом:

arr = [[2]] * 5 
print arr 
[[2], [2], [2], [2], [2]]

Затем это создает массив со всеми элементами, указывающими на один и тот же объект! Это может создать настоящую путаницу. Учти это:

arr[0][0] = 5

тогда если вы печатаете обр

print arr
[[5], [5], [5], [5], [5]]

Правильный способ инициализации массива, например, со списком:

arr = [[2] for _ in range(5)]

arr[0][0] = 5

print arr

[[5], [2], [2], [2], [2]]
Другие вопросы по тегам