Python 2.x gotchas и наземные мины

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

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

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

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

Ответ 1

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

Пример: считать дефолт аргумента текущему времени:

>>>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']

Ответ 2

Вы должны знать, как обрабатываются переменные класса в 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

Ответ 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

Ответ 4

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

EDIT: пример...

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

Ответ 5

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

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

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

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

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

Ответ 6

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

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

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

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

Ответ 7

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

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

Но для меня целое деление a gotcha:

>>> 5/2
2

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

>>> 5*1.0/2
2.5

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

>>> 5//2
2

Так же будет работать и с float (и он будет работать, когда вы в конце концов перейдете к Python 3):

>>> 5*1.0//2
2.0

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

Ответ 8

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

Определите список x

>>> 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:]
[]

???

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

Ответ 9

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

Ответ 10

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

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

Ответ 11

Джеймс Дюмей красноречиво напомнил мне о другом Python:

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

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

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

Ответ 12

Поплавки по умолчанию не печатаются с полной точностью (без 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

Ответ 13

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

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

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

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

b = B()

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

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

Ответ 14

def f():
    x += 1

x = 42
f()

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

def f():
    print x
    x = 43

x = 42
f()

Ответ 15

Вы не можете использовать 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. (Хотя он не заметил, что изменение через locals() будет работать на верхнем уровне, как показано выше.)

Ответ 16

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

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

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

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

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

Ответ 17

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

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 = {}

Ответ 18

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

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

Что происходит? repr() на помощь:

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

Ответ 19

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

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

Ответ 20

Если вы назначаете переменную внутри функции, 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), чтобы объявить, что вы хотите установить переменную, определенную вне вашей функции.

Ответ 21

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

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

Ответ 22

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

>>>bool(1)
True

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

>>>float(True)
1.0

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