У языка Python, как и у любого другого, есть совершенно непонятные моменты, которые неплохо бы разобрать «по кирпичикам». Этим и займемся.
Хорошим способом получить максимальную отдачу от приведенных здесь примеров будет их прочтение в хронологическом порядке:
- Внимательно просмотрите исходный код примера. Если вы опытный программист на Python, то обязательно попробуете предсказать, что будет на выходе.
- Взгляните на результат и:
- Проверьте, совпадает ли он с тем, что вы ожидали.
- Попробуйте подумать, что именно стало причиной такого результата?
- Если не смогли, прочтите объяснение.
- Если смогли, скажите себе, что вы молодец, и переходите следующему примеру.
P.S. Вы также можете воспользоваться командной строкой, установив wtfpython:
$ npm install -g wtfpython
Теперь просто запустите wtfpython в командной строке, которая откроет эту коллекцию в выбранном $PAGER.
Примеры странностей языка Python
Ну, что-то сомнительное...
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
Для языка Python нет невозможного, но... Разве здесь не должно быть 100?
Примечание. Если вы не можете воспроизвести это, попробуйте запустить файл mixed_tabs_and_spaces.py через оболочку.
Объяснение:
- Не смешивайте табы и отступы! Символ, предшествующий return является «табом», а в другом месте код имеет отступ в «4 пробела».
- Так Python обрабатывает табы: табы заменяются на один-восемь пробелов.
- Таким образом, «таб» в последней строке заменяется на восемь пробелов, что создает цикл.
- Python 3 достаточно хорош, чтобы автоматически выдавать ошибку для таких случаев.
Выход (Python 3.x):
TabError: inconsistent use of tabs and spaces in indentation
Время для некоторых «хашбраунов»!
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? «Python» уничтожил «JavaScript»?
Объяснение:
5 (тип int) неявно преобразован в 5.0 (тип float) перед вычислением хеша Python.
>>> hash(5) == hash(5.0)
True
Этот ответ из StackOverflow прекрасно объясняет причины.
Изменение словаря во время итерации по нему
x = {0: None}
for i in x:
del x[i]
x[i+1] = None
print(i)
Выход:
0
1
2
3
4
5
6
7
Объяснение:
- Итерация по словарю, который вы редактируете, не поддерживается.
- Эта штука срабатывает только восемь раз, ведь именно это та точка, в которой словарь должен бы изменить размер, чтобы удержать больше ключей (стандартно есть только восемь записей, поэтому меняйте размер, если нужно). Это фактически деталь реализации.
- См. ответ StackOverflow, объясняющий аналогичный пример.
Удаление элемента списка в процессе итерации
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 для языка Python:
- remove удаляет первое совпадающее значение, а не определенный индекс, вызывает ValueError, если значение не найдено.
- del удаляет определенный индекс (поэтому первый list_1 не был затронут), вызывает IndexError, если указан недопустимый индекс.
- pop удаляет элемент по определенному индексу и возвращает его, вызывает IndexError, если указан недопустимый индекс.
Почему на выходе [2, 4]?
- Когда мы удаляем 1 из list_2 или list_4, содержимое списков становится [2, 3, 4]. Остальные элементы сдвинуты вниз, т. е. 2 находится в индексе 0, а 3 – в индексе 1. Так как следующая итерация будет искать индекс 1 (который является 3), то 2 полностью пропускается. Аналогичная вещь будет происходить с каждым альтернативным элементом в последовательности списка.
- См. этот ответ на StackOverflow с разбором аналогичного примера, связанного со словарями в Python.
Обратные косые черты в конце строки
Выход:
>>> 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
Объяснение:
Интерпретатор просто меняет поведение обратных косых черт, поэтому они проходят через себя и следующий символ. Вот почему обратная косая черта не работает в конце строки.
Да, он существует!
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)
Exists!
>>> does_exists_num(some_list, -1)
Does not exist
else в обработке исключений. Пример:
try:
pass
except:
print("Exception occurred!!!")
else:
print("Try block executed successfully...")
Выход:
Try block executed successfully...
Объяснение:
- else после цикла выполняется только тогда, когда нет явного прерывания после всех итераций.
- else после блока try также называется «предложением завершения», поскольку достижение else в try означает, что блок try действительно успешно завершен.
is is not what it 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 [] # Это два пустых списка в двух разных ячейках памяти.
False
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]
Даже когда значения x были разными на каждой итерации до добавления some_func в funcs, все функции возвращали 6.
//ИЛИ
>>> 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]
Остерегайтесь измененных аргументов по умолчанию!
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']
Объяснение:
Измененные по умолчанию аргументы функций в Python на самом деле не инициализируются каждый раз, когда вы вызываете функцию. Вместо этого в качестве значения по умолчанию используется последнее присвоенное им значение. Когда мы явно передали [] в 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 = 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
Объяснение:
- Когда вы назначаете переменную в области видимости, она становится локальной. Таким образом, переменная a становится локальной для области another_func, но ранее не была инициализирована в той же области, которая вызывает ошибку.
- Дополнительно прочтите это краткое руководство.
- Чтобы изменить внешнюю переменную области a в another_func, используйте глобальное ключевое слово:
def another_func()
global a
a += 1
return a
Выход:
>>> another_func()
2
Return return everywhere!
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, всегда будет последним.
Комментарии