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

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

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

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

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

22 Solutions collect form web for “Python 2.x gotchas и наземные мины”

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

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

>>>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 ax, AAA.x 3 3 >>> ax = 4 # a: {'x': 4}, AAA: {'x': 3} >>> print ax, 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 

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

EDIT: пример …

 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 .

Но для меня целочисленное деление получило:

 >>> 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 .

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

Определить список 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:] [] 

???

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

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

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

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

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

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

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

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

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

 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 

Непреднамеренное смешивание классов старости и 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() 

Но если вы забудете сделать класс a 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

 def f(): x += 1 x = 42 f() 

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

 def f(): print x x = 43 x = 42 f() 

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

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

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

 >>> x = [[]]*5 >>> x[0].append(0) # Expect x equals [[0], [], [], [], []] >>> x [[0], [0], [0], [0], [0]] # Oh dear 

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

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

 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 2 есть некоторые удивительные результаты при сравнении:

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

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

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

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 

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

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

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

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

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

 >>>bool(1) True 

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

 >>>float(True) 1.0 

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

  • Численные шаблоны в Python3
  • Преобразуйте строку Python в своих ASCII-представлений
  • Есть ли лучший способ получить названную серию констант (перечисление) в Python?
  • Как я могу вычислить большие числа в python
  • Отделите функцию python на две разные функции
  • Как использовать pip с python 3.4 на окнах?
  • Безопасный способ анализа пользовательской математической формулы в Python
  • Установить тип содержимого почтового сообщения для ical вложения в «text / calendar»; Метод = ЗАПРОС»
  • python help >> модули дают ошибку сегментации - как исправить?
  • Вывод имени не определен в python
  • вызов скрипта python из другого скрипта
  • Python - лучший язык программирования в мире.