Цель моего вопроса - укрепить мою базу знаний с помощью 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.