- PVSM.RU - https://www.pvsm.ru -
Недавно мы писали [1] о забавных, хитрых и странных примерах на JavaScript. Теперь пришла очередь Python. У Python, высокоуровневого и интерпретируемого языка, много удобных свойств. Но иногда результат работы некоторых кусков кода на первый взгляд выглядит неочевидным.
Ниже — забавный проект, в котором собраны примеры неожиданного поведения в Python с обсуждением того, что происходит под капотом. Часть примеров не относятся к категории настоящих WTF?!, но зато они демонстрируют интересные особенности языка, которых вы можете захотеть избегать. Я думаю, это хороший способ изучить внутреннюю работу Python, и надеюсь, вам будет интересно.
Если вы уже опытный программист на Python, то многие примеры могут быть вам знакомы и даже вызовут ностальгию по тем случаям, когда вы ломали над ними голову :)
is
не то, что оно есть [15]is not ...
отличается от is (not ...)
[16] Return
возвращает везде [25]Примечание: Все приведённые примеры протестированы на интерактивном интерпретаторе Python 3.5.2 и должны работать во всех версиях языка, если иное явно не указано в описании.
Структура примеров:
# Код.
# Подготовка к магии...
Результат (версия Python):
>>> инициирующее_выражение
Вероятно, неожиданный результат
(Опционально): Однострочное описание неожиданного результата.
Объяснение:
Краткое объяснение того, что произошло и почему.
Поясняющие примеры (если необходимо)
Результат:
>>> инициирование # какого-то примера, срывающего покровы с магии
# объясняющий результат
Мне кажется, что лучший способ извлечь максимальную пользу из этих примеров — это читать их в хронологическом порядке:
P. S. Также можете читать эти примеры в командной строке. Только сначала установите npm-пакет wtfpython
,
$ npm install -g wtfpython
Теперь запустите wtfpython
в командной строке, и в результате эта коллекция откроется в вашем $PAGER
.
#TODO: Добавьте пакет pypi для чтения в командной строке.
Результат:
>>> value = 11
>>> valuе = 32
>>> value
11
Wat?
Примечание: проще всего воспроизвести этот пример с помощью копирования и вставки в ваш файл/оболочку.
Объяснение
Некоторые Unicode-символы выглядят так же, как и ASCII, но различаются интерпретатором.
>>> value = 42 #ascii e
>>> valuе = 23 #cyrillic e, Python 2.x interpreter would raise a `SyntaxError` here
>>> value
42
def square(x):
"""
Простая функция для вычисления квадрата числа путём сложения.
"""
sum_so_far = 0
for counter in range(x):
sum_so_far = sum_so_far + x
return sum_so_far
Результат (Python 2.x):
>>> square(10)
10
Разве должно было получиться не 100?
Примечание: если не можете воспроизвести результат, попробуйте запустить в оболочке файл mixed_tabs_and_spaces.py [43].
Объяснение
square
заменяется восемью пробелами и попадает в цикл.Результат (Python 3.x):
TabError: inconsistent use of tabs and spaces in indentation
1.
some_dict = {}
some_dict[5.5] = "Ruby"
some_dict[5.0] = "JavaScript"
some_dict[5] = "Python"
Результат:
>>> some_dict[5.5]
"Ruby"
>>> some_dict[5.0]
"Python"
>>> some_dict[5]
"Python"
Python уничтожил существование JavaScript?
Объяснение
Неизменяемые объекты с одинаковыми значениями в Python всегда получают одинаковые хеши.
>>> 5 == 5.0
True
>>> hash(5) == hash(5.0)
True
Примечание: объекты с разными значениями тоже могут получить одинаковые хеши (такая ситуация называется хеш-коллизией).
some_dict[5] = "Python"
существующее выражение «JavaScript» переписывается на «Python», потому что Python распознаёт 5
и 5.0
как одинаковые ключи словаря some_dict
. array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]
Результат:
>>> print(list(g))
[8]
Объяснение
in
обрабатывается во время объявления, а условная клауза — во время run time. array
к списку [2, 8, 22]
, а поскольку из 1
, 8
и 15
только значение счётчика 8
больше 0
, то генератор выдаёт только 8
. x = {0: None}
for i in x:
del x[i]
x[i+1] = None
print(i)
Результат:
0
1
2
3
4
5
6
7
Да, выполняется ровно восемь раз и останавливается.
Объяснение:
list_1 = [1, 2, 3, 4]
list_2 = [1, 2, 3, 4]
list_3 = [1, 2, 3, 4]
list_4 = [1, 2, 3, 4]
for idx, item in enumerate(list_1):
del item
for idx, item in enumerate(list_2):
list_2.remove(item)
for idx, item in enumerate(list_3[:]):
list_3.remove(item)
for idx, item in enumerate(list_4):
list_4.pop(idx)
Результат:
>>> list_1
[1, 2, 3, 4]
>>> list_2
[2, 4]
>>> list_3
[]
>>> list_4
[2, 4]
Знаете, почему получился результат [2, 4]
?
Объяснение:
Менять объект во время его итерирования — всегда плохая идея. Лучше тогда итерировать копию объекта, что и делает list_3[:]
.
>>> some_list = [1, 2, 3, 4]
>>> id(some_list)
139798789457608
>>> id(some_list[:]) # Notice that python creates new object for sliced list.
139798779601192
Разница между del
, remove
и pop
:
del var_name
просто убирает привязку var_name
локального или глобального пространства имён (поэтому list_1
остаётся незатронутым). remove
убирает первое совпадающее значение, а не конкретный индекс, вызывая ValueError
при отсутствии значения. pop
убирает элемент с конкретным индексом и возвращает его, вызывая IndexError
, если задан неверный индекс. Почему получилось [2, 4]
?
1
из list_2
или list_4
, то содержимым списков становится [2, 3, 4]
. Оставшиеся сдвигаются вниз, то есть 2
оказывается на индексе 0, 3
— на индексе 1. Поскольку следующая итерация будет выполняться применительно к индексу 1 (где у нас 3
), 2
окажется пропущена. То же самое произойдёт с каждым вторым элементом в списке. Похожий пример, связанный со словарями в Python, прекрасно объяснён [47] на StackOverflow. Результат:
>>> print("\ some string \")
>>> print(r" some string")
>>> print(r" some string ")
File "<stdin>", line 1
print(r" some string ")
^
SyntaxError: EOL while scanning string literal
Объяснение
r
, обратный слеш не имеет особого значения. Это вовсе не WTF, а лишь некоторые прикольные вещи, и их нужно опасаться :)
def add_string_with_plus(iters):
s = ""
for i in range(iters):
s += "xyz"
assert len(s) == 3*iters
def add_string_with_format(iters):
fs = "{}"*iters
s = fs.format(*(["xyz"]*iters))
assert len(s) == 3*iters
def add_string_with_join(iters):
l = []
for i in range(iters):
l.append("xyz")
s = "".join(l)
assert len(s) == 3*iters
def convert_list_to_string(l, iters):
s = "".join(l)
assert len(s) == 3*iters
Результат:
>>> timeit(add_string_with_plus(10000))
100 loops, best of 3: 9.73 ms per loop
>>> timeit(add_string_with_format(10000))
100 loops, best of 3: 5.47 ms per loop
>>> timeit(add_string_with_join(10000))
100 loops, best of 3: 10.1 ms per loop
>>> l = ["xyz"]*10000
>>> timeit(convert_list_to_string(l, 10000))
10000 loops, best of 3: 75.3 µs per loop
Объяснение
+
для генерирования длинных строк: в Python str
— неизменяемая, поэтому для каждой пары конкатенаций левая и правая строки должны быть скопированы в новую строку. Если вы конкатенируете четыре строки длиной по 10 символов, то копируйте (10 + 10) + ((10 + 10) + 10) + (((10 + 10) +10) +10) = 90 символов вместо 40. По мере увеличения количества и размера строк ситуация вчетверо ухудшается. .format.
или %
(но на коротких строках это работает чуть медленнее, чем +). ''.join(iterable_object)
. >>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # Notice that both the ids are same.
140420665652016
# using "+", three strings:
>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.25748300552368164
# using "+=", three strings:
>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.012188911437988281
Объяснение:
+=
быстрее +
более чем двух строк, потому что первая строка (например, s1
для s1 += s2 + s3
) не уничтожается, пока строка не будет обработана целиком. Клауза else
для циклов. Типичный пример:
def does_exists_num(l, to_find):
for num in l:
if num == to_find:
print("Exists!")
break
else:
print("Does not exist")
Результат:
>>> some_list = [1, 2, 3, 4, 5]
>>> does_exists_num(some_list, 4)
Существует!
>>> does_exists_num(some_list, -1)
Не существует.
Клауза else
в обработке исключений. Пример:
try:
pass
except:
print("Exception occurred!!!")
else:
print("Try block executed successfully...")
Результат:
Try block executed successfully...
Объяснение:
else
исполняется после цикла только тогда, когда после всех итераций нет явного break
. else
после блока try
также называется клаузой завершения (completion clause), поскольку доступность else
в выражении try
означает, что блок try
успешно завершён. is
не то, что оно естьЭтот пример очень широко известен.
>>> a = 256
>>> b = 256
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a is b
False
>>> a = 257; b = 257
>>> a is b
True
Объяснение:
Разница между is
и ==
is
проверяет, чтобы оба операнда ссылались на один объект (т. е. проверяет, идентичны ли они друг другу). ==
сравнивает значения операндов и проверяет на идентичность. is
используется для эквивалентности ссылок, а ==
— для эквивалентности значений. Поясняющий пример:>>> [] == []
True
>>> [] is [] # These are two empty lists at two different memory locations.
False
256
— существующий объект, а 257
— нет
При запуске Python в памяти размещаются числа от -5
до 256
. Они используются часто, так что целесообразно держать их наготове.
Цитата из https://docs.python.org/3/c-api/long.html [50]
В текущей реализации поддерживается массив целочисленных объектов для всех чисел с –5 по 256, так что когда вы создаёте int из этого диапазона, то получаете ссылку на существующий объект. Поэтому должна быть возможность изменить значение на 1. Но подозреваю, что в этом случае поведение Python будет непредсказуемым. :-)
>>> id(256)
10922528
>>> a = 256
>>> b = 256
>>> id(a)
10922528
>>> id(b)
10922528
>>> id(257)
140084850247312
>>> x = 257
>>> y = 257
>>> id(x)
140084850247440
>>> id(y)
140084850247344
Интерпретатор оказался не так умён, и во время исполнения y = 257
не понял, что мы уже создали целое число со значением 257
, поэтому создаёт в памяти другой объект.
a
и b
ссылаются на один объект при инициализации с одинаковым значением в одной строке.
>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
140640774013392
>>> id(b)
140640774013488
a
и b
присваивается значение 257
, интерпретатор Python создаёт новый объект, в то же время делая на него ссылку из второй переменной. Если же присвоить значения в разных строках, то интерпретатор не будет «знать», что у нас уже есть 257
в виде объекта. .py
, то не увидите такого поведения, потому что файл компилируется за раз. is not ...
отличается от is (not ...)
>>> 'something' is not None
True
>>> 'something' is (not None)
False
Объяснение
is not
— это одиночный бинарный оператор, поведение которого отличается от ситуации, когда по отдельности используются is
и not
. is not
выдаёт False
, если переменные с обеих сторон оператора указывают на один объект. В противном случае выдаётся True
. funcs = []
results = []
for x in range(7):
def some_func():
return x
funcs.append(some_func)
results.append(some_func())
funcs_results = [func() for func in funcs]
Результат:
>>> results
[0, 1, 2, 3, 4, 5, 6]
>>> funcs_results
[6, 6, 6, 6, 6, 6, 6]
Если до добавления some_func
в funcs
значения x
в каждой итерации были разными, все функции возвращали 6.
//OR
>>> powers_of_x = [lambda x: x**i for i in range(10)]
>>> [f(2) for f in powers_of_x]
[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
Объяснение
funcs = []
for x in range(7):
def some_func(x=x):
return x
funcs.append(some_func)
Результат:
>>> funcs_results = [func() for func in funcs]
>>> funcs_results
[0, 1, 2, 3, 4, 5, 6]
1.
for x in range(7):
if x == 6:
print(x, ': for x inside loop')
print(x, ': x in global')
Результат:
6 : for x inside loop
6 : x in global
Но x
не был определён для цикла вне области видимости.
2.
# This time let's initialize x first
x = -1
for x in range(7):
if x == 6:
print(x, ': for x inside loop')
print(x, ': x in global')
Результат:
6 : for x inside loop
6 : x in global
3.
x = 1
print([x for x in range(5)])
print(x, ': x in global')
Результат (on Python 2.x):
[0, 1, 2, 3, 4]
(4, ': x in global')
Результат (on Python 3.x):
[0, 1, 2, 3, 4]
1 : x in global
Объяснение
«Для генерирования списков больше не поддерживается синтаксическая форма
[... for var in item1, item2, ...]
. Используйте вместо неё[... for var in (item1, item2, ...)]
. Также обратите внимание, что генерирования списков имеют разные семантики: они ближе к синтаксическому сахару применительно к генерирующему выражению внутри конструктораlist()
, и, в частности, переменные управления циклом больше не утекают в окружающую область видимости».
# Let's initialize a row
row = [""]*3 #row i['', '', '']
# Let's make a board
board = [row]*3
Результат:
>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]
Но мы же не присваивали три X, верно?
Объяснение
Эта визуализация объясняет, что происходит в памяти при инициализации переменной row
:
А когда посредством умножения row
инициализируется board
, то в памяти происходит вот что (каждый из элементов board[0]
, board[1]
и board[2]
является ссылкой на один и тот же список, указанный в row
):
def some_func(default_arg=[]):
default_arg.append("some_string")
return default_arg
Результат:
>>> some_func()
['some_string']
>>> some_func()
['some_string', 'some_string']
>>> some_func([])
['some_string']
>>> some_func()
['some_string', 'some_string', 'some_string']
Объяснение
[]
в качестве аргумента в some_func
, то для переменной default_arg
не было использовано значение по умолчанию, поэтому функция вернула то, что ожидалось.def some_func(default_arg=[]):
default_arg.append("some_string")
return default_arg
Результат:
>>> some_func.__defaults__ #This will show the default argument values for the function
([],)
>>> some_func()
>>> some_func.__defaults__
(['some_string'],)
>>> some_func()
>>> some_func.__defaults__
(['some_string', 'some_string'],)
>>> some_func([])
>>> some_func.__defaults__
(['some_string', 'some_string'],)
None
в качестве значения по умолчанию с последующей проверкой, передано ли какое-то значение в функцию, соответствующую этому аргументу. Пример:
def some_func(default_arg=None):
if not default_arg:
default_arg = []
default_arg.append("some_string")
return default_arg
1.
a = [1, 2, 3, 4]
b = a
a = a + [5, 6, 7, 8]
Результат:
>>> a
[1, 2, 3, 4, 5, 6, 7, 8]
>>> b
[1, 2, 3, 4]
2.
a = [1, 2, 3, 4]
b = a
a += [5, 6, 7, 8]
Результат:
>>> a
[1, 2, 3, 4, 5, 6, 7, 8]
>>> b
[1, 2, 3, 4, 5, 6, 7, 8]
Объяснение
a += b
ведёт себя не так же, как a = a + b
a = a + [5,6,7,8]
генерирует новый объект и присваивает a ссылку на него, оставляя b
без изменений.a + =[5,6,7,8]
фактически преобразуется (mapped to) в функцию extend, которая работает с объектом таким образом, что a
и b
всё ещё указывают на один и тот же объект, который был изменён на месте.some_tuple = ("A", "tuple", "with", "values")
another_tuple = ([1, 2], [3, 4], [5, 6])
Результат:
>>> some_tuple[2] = "change this"
TypeError: 'tuple' object does not support item assignment
>>> another_tuple[2].append(1000) #This throws no error
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000])
>>> another_tuple[2] += [99, 999]
TypeError: 'tuple' object does not support item assignment
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000, 99, 999])
Но ведь кортежи неизменяемы, разве нет...
Объяснение
Объект неизменяемого типа последовательности (immutable sequence type) не может измениться после своего создания. Если объект содержит ссылки на другие объекты, то эти объекты могут быть изменяемыми и могут быть изменены. Однако коллекцию объектов, на которую прямо ссылается неизменяемый объект, изменить нельзя.
+=
изменяет список на месте. Присвоение элемента (item assignment) не работает, но, когда возникает исключение, элемент уже был изменён на месте.a = 1
def some_func():
return a
def another_func():
a += 1
return a
Результат:
>>> some_func()
1
>>> another_func()
UnboundLocalError: local variable 'a' referenced before assignment
Объяснение
another_func
, но она не была ранее инициализирована в той же области, которая кидает ошибку.global
для модифицирования переменной внешней области видимости a
в another_func
.def another_func()
global a
a += 1
return a
Результат:
>>> another_func()
2
e = 7
try:
raise Exception()
except Exception as e:
pass
Результат (Python 2.x):
>>> print(e)
# prints nothing
Результат (Python 3.x):
>>> print(e)
NameError: name 'e' is not defined
Объяснение
Источник [54].
Если назначает исключение с целевым as
, оно очищается в конце клаузы except
. Как если бы
except E as N:
foo
было преобразовано в
except E as N:
try:
foo
finally:
del N
Это означает, что исключение нужно назначать на другое имя, чтобы можно было ссылаться на него после клаузы except
. Исключения очищаются потому, что к ним прикрепляется обратная трассировка (traceback), в результате во фрейме стека формируется ссылочный цикл (reference cycle), поддерживающий все локалы в этом фрейме живыми, пока не пройдёт следующая итерация сборки мусора.
except
. Но это не относится к функциям, имеющим отдельные внутренние области видимости. Иллюстрация:
def f(x):
del(x)
print(x)
x = 5
y = [5, 4, 3]
**Результат:**
f(x)
UnboundLocalError: local variable 'x' referenced before assignment
f(y)
UnboundLocalError: local variable 'x' referenced before assignment
x
5
y
[5, 4, 3]- В Python 2.x имя переменной e присвоено экземпляру `Exception()`, так что при попытке вывода на экран вы ничего не увидите.
Результат (Python 2.x):
>>> e
Exception()
>>> print e
# Nothing is printed!
def some_func():
try:
return 'from_try'
finally:
return 'from_finally'
Результат:
>>> some_func()
'from_finally'
Объяснение
try
выражения try…finally
выполняется return
, break
или continue
, то на выходе также исполняется клауза finally
. return
. Поскольку клауза finally
исполняется всегда, выражение return
, исполненное в клаузе finally
, всегда будет последним исполненным. True = False
if True == False:
print("I've lost faith in truth!")
Результат:
I've lost faith in truth!
Объяснение
bool
(программисты использовали 0 для false и ненулевое значение вроде 1 для true). Затем в язык добавили True
, False
и тип bool
, но из-за обратной совместимости нельзя было сделать True
и False
константами — они представляли собой просто встроенные переменные. >>> True is False == False
False
>>> False is False is False
True
>>> 1 > 0 < 1
True
>>> (1 > 0) < 1
False
>>> 1 > (0 < 1)
False
Объяснение
Как сказано в https://docs.python.org/2/reference/expressions.html#not-in [55]
Формально, если a, b, c, ..., y, z — выражения, а op1, op2, ..., opN —операторы сравнения, тогда a op1 b op2 c… y opN z эквивалентно op1 b и b op2 c и… y opN z, за исключением того, что каждое выражение вычисляется однократно.
Хотя такое поведение могло показаться вам глупостью, оно очень удобно в ситуациях вроде a == b == c и 0 <= x <= 100
.
False is False is False
эквивалентно (False is False) and (False is False)
True is False == False
эквивалентно True is False and False == False
, и поскольку первая часть выражения (True is False
) вычисляется как False
, то и всё выражение вычисляется как False
. 1 > 0 < 1
эквивалентно 1 > 0
and 0 < 1
, что вычисляется как True
. (1 > 0) < 1
эквивалентно True < 1
и >>> int(True)
1
>>> True + 1 #not relevant for this example, but just for fun
2
Так что 1 < 1
вычисляется как False
1.
x = 5
class SomeClass:
x = 17
y = (x for i in range(10))
Результат:
>>> list(SomeClass.y)[0]
5
2.
x = 5
class SomeClass:
x = 17
y = [x for i in range(10)]
Результат (Python 2.x):
>>> SomeClass.y[0]
17
Результат (Python 3.x):
>>> SomeClass.y[0]
5
Объяснение
some_list = [1, 2, 3]
some_dict = {
"key_1": 1,
"key_2": 2,
"key_3": 3
}
some_list = some_list.append(4)
some_dict = some_dict.update({"key_4": 4})
Результат:
>>> print(some_list)
None
>>> print(some_dict)
None
Объяснение
Большинство методов, изменяющих элементы объектов последовательности/преобразования (sequence/mapping objects) вроде list.append
, dict.update
, list.sort
и т. д., изменяют объекты на месте и возвращают None
. Причина — в улучшении производительности благодаря избеганию созданий копии объекта, если операцию можно выполнить на месте (взято отсюда [56])
Это вовсе не WTF, но у меня ушла куча времени на осознание того, что в Python существуют такие вещи. Делюсь с начинающими.
a = float('inf')
b = float('nan')
c = float('-iNf') #These strings are case-insensitive
d = float('nan')
Результат:
>>> a
inf
>>> b
nan
>>> c
-inf
>>> float('some_other_string')
ValueError: could not convert string to float: some_other_string
>>> a == -c #inf==inf
True
>>> None == None # None==None
True
>>> b == d #but nan!=nan
False
>>> 50/a
0.0
>>> a/a
nan
>>> 23 + b
nan
Объяснение
'inf'
и 'nan'
— специальные строковые значения (чувствительные к регистру). Если явно привести их к типу float
, то можно использовать их для представления, соответственно, математических «бесконечности» и «не числа».
1.
class A:
x = 1
class B(A):
pass
class C(A):
pass
Результат:
>>> A.x, B.x, C.x
(1, 1, 1)
>>> B.x = 2
>>> A.x, B.x, C.x
(1, 2, 1)
>>> A.x = 3
>>> A.x, B.x, C.x
(3, 2, 3)
>>> a = A()
>>> a.x, A.x
(3, 3)
>>> a.x += 1
>>> a.x, A.x
(4, 3)
2.
class SomeClass:
some_var = 15
some_list = [5]
another_list = [5]
def __init__(self, x):
self.some_var = x + 1
self.some_list = self.some_list + [x]
self.another_list += [x]
Результат:
>>> some_obj = SomeClass(420)
>>> some_obj.some_list
[5, 420]
>>> some_obj.another_list
[5, 420]
>>> another_obj = SomeClass(111)
>>> another_obj.some_list
[5, 111]
>>> another_obj.another_list
[5, 420, 111]
>>> another_obj.another_list is SomeClass.another_list
True
>>> another_obj.another_list is some_obj.another_list
True
Объяснение
+=
модифицирует изменяемый объект на месте без создания нового объекта. Так что изменение атрибута одного экземпляра влияет также на другие экземпляры и атрибут класса.some_list = [1, 2, 3]
try:
# This should raise an ``IndexError``
print(some_list[4])
except IndexError, ValueError:
print("Caught!")
try:
# This should raise a ``ValueError``
some_list.remove(4)
except IndexError, ValueError:
print("Caught again!")
Результат (Python 2.x):
Caught!
ValueError: list.remove(x): x not in list
Результат (Python 3.x):
File "<input>", line 3
except IndexError, ValueError:
^
SyntaxError: invalid syntax
Объяснение
except
вам нужно передавать их в первый аргумент в виде взятого в круглые скобки кортежа. Второй аргумент — опциональное имя, которое потом привязывается к экземпляру после кидаемого исключения. Пример:some_list = [1, 2, 3]
try:
# This should raise a ``ValueError``
some_list.remove(4)
except (IndexError, ValueError), e:
print("Caught again!")
print(e)
Результат (Python 2.x):
Caught again!
list.remove(x): x not in list
Результат (Python 3.x):
File "<input>", line 4
except (IndexError, ValueError), e:
^
IndentationError: unindent does not match any outer indentation level
as
. Пример:some_list = [1, 2, 3]
try:
some_list.remove(4)
except (IndexError, ValueError) as e:
print("Caught again!")
print(e)
Результат:
Caught again!
list.remove(x): x not in list
from datetime import datetime
midnight = datetime(2018, 1, 1, 0, 0)
midnight_time = midnight.time()
noon = datetime(2018, 1, 1, 12, 0)
noon_time = noon.time()
if midnight_time:
print("Time at midnight is", midnight_time)
if noon_time:
print("Time at noon is", noon_time)
Результат:
('Time at noon is', datetime.time(12, 0))
The midnight time is not printed.
Объяснение
До Python 3.5 булевым значением для объекта datetime.time
было False
, если требовалось представить полночь в формате UTC. Из-за этого могут возникать ошибки при использовании синтаксиса if obj
: при проверке, имеет ли obj
значение null или другой эквивалент «пустоты».
1.
# A simple example to count the number of boolean and
# integers in an iterable of mixed data types.
mixed_list = [False, 1.0, "some_string", 3, True, [], False]
integers_found_so_far = 0
booleans_found_so_far = 0
for item in mixed_list:
if isinstance(item, int):
integers_found_so_far += 1
elif isinstance(item, bool):
booleans_found_so_far += 1
Результат:
>>> booleans_found_so_far
0
>>> integers_found_so_far
4
2.
another_dict = {}
another_dict[True] = "JavaScript"
another_dict[1] = "Ruby"
another_dict[1.0] = "Python"
Результат:
>>> another_dict[True]
"Python"
Объяснение
int
>>> isinstance(True, int)
True
>>> isinstance(False, int)
True
True
равно 1
, а False
— 0
.
>>> True == 1 == 1.0 and False == 0 == 0.0
True
Почти каждый Python-программист сталкивался с этой ситуацией.
t = ('one', 'two')
for i in t:
print(i)
t = ('one')
for i in t:
print(i)
t = ()
print(t)
Результат:
one
two
o
n
e
tuple()
Объяснение
t = ('one',)
или t = 'one'
, (отсутствует запятая), иначе интерпретатор решит, что t
является str
и итерирует её символ за символом. tuple
. Предложено [58] @MittalAshok.
some_string = "wtf"
some_dict = {}
for i, some_dict[i] in enumerate(some_string):
pass
Результат:
>>> some_dict # An indexed dict is created.
{0: 'w', 1: 'f', 2: 'f'}
Объяснение
for
определено в учебнике Python [59] как:for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
Здесь exprlist
— цель присвоения. Это означает, что эквивалент {exprlist} = {next_value}
исполняется для каждого элемента из итерируемых. Интересный пример предложен [60] @tukkek:
for i in range(4):
print(i)
i = 10
Результат:
0
1
2
3
Вы думали, что цикл будет прогнан только один раз?
Объяснение
i = 10
никогда не влияет на итерации цикла из-за особенностей работы цикла в Python. Перед началом каждой итерации следующий элемент, предоставленный итератором (range(4)
в данном случае), распаковывается и присваивается к переменной из целевого списка (i
в данном случае). enumerate(some_string)
в каждой итерации извлекает новое значение i
(счётчик A
увеличивается) и символ из some_string
. Затем она задаёт (просто присваивает) этому символу ключ i
из словаря some_dict
. Развёртку цикла можно упростить до:>>> i, some_dict[i] = (0, 'w')
>>> i, some_dict[i] = (1, 't')
>>> i, some_dict[i] = (2, 'f')
>>> some_dict
Предложено [61] @MostAwesomeDude.
x = True
y = False
Результат:
>>> not x == y
True
>>> x == not y
File "<input>", line 1
x == not y
^
SyntaxError: invalid syntax
Объяснение
==
выше по старшинству, чем оператор not
. not x == y
эквивалентно not (x == y)
, что эквивалентно выражению not (True == False)
, которое вычисляется как True
. x == not y
выдаёт SyntaxError
, потому что его можно представить как эквивалент (x == not) y
, а не x == (not y)
, как вы могли подумать в первый момент. not
— часть оператора not in
(потому что операторы ==
и not in
имеют одинаковое старшинство), но когда следом за токеном not
он не находит токен in
, то выдаёт SyntaxError. Предложено [62] PiaFraus [63].
a, b = a[b] = {}, 5
Результат:
>>> a
{5: ({...}, 5)}
Объяснение
(target_list "=")+ (expression_list | yield_expression)
а также:
Оператор присваивания обрабатывает список выражений (это может быть одиночное выражение или список выражений, разделённых запятыми, во втором случае получается кортеж) и присваивает единственный результирующий объект каждому элементу из списка, слева направо.
+
в (target_list "=")+
означает, что может быть один или несколько целевых списков. В этом случае целевыми списками являются a
, b
и a[b]
(обратите внимание, что список выражений только один, в нашем случае {}
, 5
).{}
, 5
распаковывается в a
, b
, и теперь у нас a = {}
и b = 5
.a
теперь присваивается {}
, который является изменяемым объектом.a[b]
(вы можете подумать, что будет выдана ошибка, потому что a
и b
не были перед этим определены. Но помните, мы просто присвоили a
к {}
и b
к 5
).5
из словаря кортежу ({}, 5)
, создавая тем самым циклическую ссылку ({...}
в выходных данных ссылается на тот же объект, на который теперь ссылается a
). Более простой пример циклической ссылки:>>> some_list = some_list[0] = [0]
>>> some_list
[[...]]
>>> some_list[0]
[[...]]
>>> some_list is some_list[0]
[[...]]
Аналогично вышеприведённому примеру (a[b][0]
это тот же объект, что и a
)
a, b = {}, 5
a[b] = a, b
И циклическая ссылка может быть оправдана тем, что a[b][0]
является тем же объектом, что и
a
>>> a[b][0] is a
True
Join()
— это строковая операция (string operation), а не операция списка (list operation). В первое время это выглядит неочевидным.join()
— это метод для строки, тогда он может оперировать любыми итерируемыми (списками, кортежами, итераторами). Если бы это был метод для списка, то он реализовывался бы каждым типом отдельно. Кроме того, нет смысла помещать предназначенный для строковых значений метод в API обычного объекта list
.[] = ()
(распаковывает пустой tuple
в пустой list
) 'a'[0][0][0][0][0]
также семантически корректно, потому что строки в Python итерируемые. 3 --0-- 5 == 8 и --5 == 5
семантически верны и вычисляются как True
. import dis
exec("""
def f():* """ + """
""".join(["X"+str(x)+"=" + str(x) for x in range(65539)]))
f()
print(dis.dis(f))
>>> some_list = [1, 2, 3, 4, 5]
>>> some_list[111:]
[]
Все патчи приветствуются! Только перед самой публикацией прошу сначала создавать тему [42]:) Подробности описаны в CONTRIBUTING.md [66].
• https://www.youtube.com/watch?v=sH4XF6pKKmk [67]
• https://www.reddit.com/r/Python/comments/3cu6ej/what_are_some_wtf_things_about_python [68]
• https://sopython.com/wiki/Common_Gotchas_In_Python [69]
• https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines [70]
• https://stackoverflow.com/questions/1011431/common-pitfalls-in-python [71] (В этой теме на StackOverflow приведены полезные рекомендации, что можно и что нельзя делать в Python.)
Автор: AloneCoder
Источник [72]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/263467
Ссылки в тексте:
[1] писали: https://habrahabr.ru/company/mailru/blog/335292/
[2] Структура примеров: https://habrahabr.ru/company/mailru/blog/337364/#struktura-primerov
[3] Использование: https://habrahabr.ru/company/mailru/blog/337364/#ispolzovanie
[4] Примеры: https://habrahabr.ru/company/mailru/blog/337364/#primery
[5] Пропуск строк?: https://habrahabr.ru/company/mailru/blog/337364/#propusk-strok
[6] Ну, как-то сомнительно...: https://habrahabr.ru/company/mailru/blog/337364/#nu-kak-to-somnitelno
[7] Время для хеш-пирожных!: https://habrahabr.ru/company/mailru/blog/337364/#vremya-dlya-hesh-pirozhnyh
[8] Несоответствие времени обработки: https://habrahabr.ru/company/mailru/blog/337364/#nesootvetstvie-vremeni-obrabotki
[9] Преобразование словаря во время его итерирования: https://habrahabr.ru/company/mailru/blog/337364/#preobrazovanie-slovarya-vo-vremya-ego-iterirovaniya
[10] Удаление элемента списка во время его итерирования: https://habrahabr.ru/company/mailru/blog/337364/#udalenie-elementa-spiska-vo-vremya-ego-iterirovaniya
[11] Обратные слеши в конце строки: https://habrahabr.ru/company/mailru/blog/337364/#obratnye-sleshi-v-konce-stroki
[12] Давайте сделаем гигантскую строку!: https://habrahabr.ru/company/mailru/blog/337364/#davayte-sdelaem-gigantskuyu-stroku
[13] Оптимизации интерпретатора конкатенации строк: https://habrahabr.ru/company/mailru/blog/337364/#optimizacii-interpretatora-konkatenacii-strok
[14] Да, оно существует! : https://habrahabr.ru/company/mailru/blog/337364/#da-ono-suschestvuet
[15] is
не то, что оно есть: https://habrahabr.ru/company/mailru/blog/337364/#is-ne-to-chto-ono-est
[16] is not ...
отличается от is (not ...)
: https://habrahabr.ru/company/mailru/blog/337364/#is-not-otlichaetsya-ot-is-not
[17] Функция внутри цикла выдаёт один и тот же результат: https://habrahabr.ru/company/mailru/blog/337364/#funkciya-vnutri-cikla-vydaet-odin-i-tot-zhe-rezultat
[18] Утечка переменных цикла из локальной области видимости: https://habrahabr.ru/company/mailru/blog/337364/#utechka-peremennyh-cikla-iz-lokalnoy-oblasti-vidimosti
[19] Крестики-нолики, где Х побеждает с первой попытки: https://habrahabr.ru/company/mailru/blog/337364/#krestiki-noliki-gde-h-pobezhdaet-s-pervoy-popytki
[20] Опасайтесь изменяемых аргументов по умолчанию: https://habrahabr.ru/company/mailru/blog/337364/#opasaytes-izmenyaemyh-argumentov-po-umolchaniyu
[21] Те же операнды, но другая история: https://habrahabr.ru/company/mailru/blog/337364/#te-%D1%8F%D1%80%D1%83-operandy-no-drugaya-istoriya
[22] Изменение неизменяемого: https://habrahabr.ru/company/mailru/blog/337364/#izmenenie-neizmenyaemogo
[23] Использование переменной, не определённой в области видимости: https://habrahabr.ru/company/mailru/blog/337364/#ispolzovanie-peremennoy-ne-opredelennoy-v-oblasti-vidimosti
[24] Исчезновение переменной из внешней области видимости: https://habrahabr.ru/company/mailru/blog/337364/#ischeznovenie-peremennoy-iz-vneshney-oblasti-vidimosti
[25] Return
возвращает везде: https://habrahabr.ru/company/mailru/blog/337364/#return-vozvraschaet-vezde
[26] Когда True на самом деле False : https://habrahabr.ru/company/mailru/blog/337364/#kogda-true-na-samom-dele-false
[27] Будьте осторожны с цепочками операций: https://habrahabr.ru/company/mailru/blog/337364/#budte-ostorozhny-s-cepochkami-operaciy
[28] Разрешение имён (name resolution) игнорирует область видимости класса: https://habrahabr.ru/company/mailru/blog/337364/#razreshenie-imen-ignoriruet-oblast-vidimosti-klassa
[29] От заполненности до None в одной инструкции: https://habrahabr.ru/company/mailru/blog/337364/#ot-zapolnennosti-do-none-v-odnoy-instrukcii
[30] Явное приведение типов в строковых значениях: https://habrahabr.ru/company/mailru/blog/337364/#yavnoe-privedenie-tipov-v-strokovyh-znacheniyah
[31] Атрибуты классов и экземпляров: https://habrahabr.ru/company/mailru/blog/337364/#atributy-klassov-i-ekzemplyarov
[32] Ловля исключений: https://habrahabr.ru/company/mailru/blog/337364/#lovlya-isklyucheniy
[33] Полночь не существует?: https://habrahabr.ru/company/mailru/blog/337364/#polnoch-ne-suschestvuet
[34] Что не так с булевыми значениями?: https://habrahabr.ru/company/mailru/blog/337364/#chto-ne-tak-s-bulevymi-znacheniyami
[35] Игла в стоге сена: https://habrahabr.ru/company/mailru/blog/337364/#igla-v-stoge-sena
[36] For что? : https://habrahabr.ru/company/mailru/blog/337364/#for-chto
[37] Узел not : https://habrahabr.ru/company/mailru/blog/337364/#uzel-not
[38] А вы могли такое предположить?: https://habrahabr.ru/company/mailru/blog/337364/#a-vy-mogli-takoe-predpolozhit
[39] Мелкие примеры: https://habrahabr.ru/company/mailru/blog/337364/#melkie-primery
[40] Внести свой вклад: https://habrahabr.ru/company/mailru/blog/337364/#vnesti-svoi-vklad
[41] Полезные ссылки: https://habrahabr.ru/company/mailru/blog/337364/#poleznye-ssylki
[42] здесь: https://github.com/satwikkansal/wtfPython
[43] mixed_tabs_and_spaces.py: https://github.com/satwikkansal/wtfpython/blob/master/mixed_tabs_and_spaces.py
[44] объясняется: https://stackoverflow.com/a/32211042/4354153
[45] генератора: https://wiki.python.org/moin/Generators
[46] разбирается: https://stackoverflow.com/questions/44763802/bug-in-python-dict
[47] объяснён: https://stackoverflow.com/questions/45877614/how-to-change-all-the-dictionary-keys-in-a-for-loop-with-d-items
[48] timeit: https://docs.python.org/3/library/timeit.html
[49] Почитать подробнее: https://stackoverflow.com/questions/24245324/about-the-changing-id-of-an-immutable-string
[50] https://docs.python.org/3/c-api/long.html: https://docs.python.org/3/c-api/long.html
[51] What’s New In Python 3.0: https://docs.python.org/3/whatsnew/3.0.html
[53] это: http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html
[54] Источник: https://docs.python.org/3/reference/compound_stmts.html#except
[56] отсюда: http://docs.python.org/2/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list
[57] описаны: https://stackoverflow.com/a/8169049/4354153
[58] Предложено: https://github.com/satwikkansal/wtfpython/issues/23
[59] учебнике Python: https://docs.python.org/3/reference/grammar.html
[60] предложен: https://github.com/satwikkansal/wtfPython/issues/11
[61] Предложено: https://github.com/satwikkansal/wtfPython/issues/1
[62] Предложено: https://github.com/satwikkansal/wtfPython/issues/9
[63] PiaFraus: https://habrahabr.ru/users/piafraus/
[64] языковой справке: https://docs.python.org/2/reference/simple_stmts.html#assignment-statements
[65] многопроцессорной обработки: https://docs.python.org/2/library/multiprocessing.html
[66] CONTRIBUTING.md: https://github.com/satwikkansal/wtfpython/blob/master/CONTRIBUTING.md
[67] https://www.youtube.com/watch?v=sH4XF6pKKmk: https://www.youtube.com/watch?v=sH4XF6pKKmk
[68] https://www.reddit.com/r/Python/comments/3cu6ej/what_are_some_wtf_things_about_python: https://www.reddit.com/r/Python/comments/3cu6ej/what_are_some_wtf_things_about_python
[69] https://sopython.com/wiki/Common_Gotchas_In_Python: https://sopython.com/wiki/Common_Gotchas_In_Python
[70] https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines: https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines
[71] https://stackoverflow.com/questions/1011431/common-pitfalls-in-python: https://stackoverflow.com/questions/1011431/common-pitfalls-in-python
[72] Источник: https://habrahabr.ru/post/337364/
Нажмите здесь для печати.