GitHub - frontdevops/wtfpython (original) (raw)

What the f*ck Python! 😱

Изучение и понимание Python с помощью нестандартного поведения и "магического" поведения.

Другие переводы: English Original | Russian Русский | Chinese 中文 | Vietnamese Tiếng Việt | Spanish Español | Korean 한국어 | Add translation

Еще способы попробовать: Interactive | CLI

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

Вот забавный проект, пытающийся объяснить, что именно происходит под капотом некоторых неинтуитивных сниппетов и менее известных возможностей Python.

Хотя некоторые из примеров, которые вы увидите ниже, возможно, не являются WTF в прямом смысле этого слова, но они раскроют некоторые интересные части Python, о которых вы могли не знать. Я считаю, что это хороший способ изучить внутреннее устройство языка программирования, и я верю, что вам это тоже покажется интересным!

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

PS: Если вы постоянный читатель, вы можете узнать о новых изменениях здесь (примеры, отмеченные звездочкой - это примеры, добавленные в последней основной редакции).

Ну чтож, начнем...

Table of Contents

Структура примеров

Все примеры имеют следующую структуру:

▶ Какой-то заголовок

Код с приколдесами.

Подготовка к магии...

Вывод (Python версия(и)):

triggering_statement Неожиданные результаты

(Опционально): Одна строка, описывающая неожиданный результат

💡 Объяснение:

Код

Дополнительные примеры для дальнейшего разъяснения (если необходимо)

Вывод (Python версия(и)):

trigger # какой-нибудь пример, позволяющий легко раскрыть магию

обоснованный вывод

Важно: Все примеры протестированы на интерактивном интерпретаторе Python 3.5.2, и они должны работать для всех версий Python, если это явно не указано перед выводом.

Применение

Хороший способ получить максимальную пользу от этих примеров, на мой взгляд, - читать их в последовательном порядке, причем для каждого примера:

PS: Вы также можете читать WTFPython в командной строке, используя pypi package,

$ pip install wtfpython -U $ wtfpython


👀 Примеры

Секция: Напряги мозги!

▶ Важное о главном!

По какой-то причине "моржовый оператор"(walrus) (:=) в Python 3.8 стал довольно популярным. Давайте проверим его,

Python version 3.8+

a = "wtf_walrus" a 'wtf_walrus'

a := "wtf_walrus" File "", line 1 a := "wtf_walrus" ^ SyntaxError: invalid syntax

(a := "wtf_walrus") # This works though 'wtf_walrus' a 'wtf_walrus'

2 .

Python version 3.8+

a = 6, 9 a (6, 9)

(a := 6, 9) (6, 9) a 6

a, b = 6, 9 # Typical unpacking a, b (6, 9) (a, b = 16, 19) # Oops File "", line 1 (a, b = 16, 19) ^ SyntaxError: invalid syntax

(a, b := 16, 19) # This prints out a weird 3-tuple (6, 16, 19)

a # a is still unchanged? 6

b 16

💡 Обьяснение

Быстрый разбор что такое "моржовый оператор" (walrus)

"Моржовый оператор" (:=) была введена в Python 3.8, она может быть полезна в ситуациях, когда вы хотите присвоить значения переменным в выражении.

def some_func(): # Assume some expensive computation here # time.sleep(1000) return 5

So instead of,

if some_func(): print(some_func()) # Which is bad practice since computation is happening twice

or

a = some_func() if a: print(a)

Now you can concisely write

if a := some_func(): print(a)

Вывод (> 3.8):

Это сэкономило одну строку кода и неявно предотвратило вызов some_func дважды.


▶ Строки иногда ведут себя непредсказуемо

a = "some_string" id(a) 140420665652016 id("some" + "_" + "string") # Notice that both the ids are same. 140420665652016

a = "wtf" b = "wtf" a is b True

a = "wtf!" b = "wtf!" a is b False

a, b = "wtf!", "wtf!" a is b # Все версии, кроме 3.7.x True

a = "wtf!"; b = "wtf!" a is b # Это выведет True или False в зависимости от того, где вы вызываете (python shell / ipython / as a script) False

This time in file some_file.py

a = "wtf!" b = "wtf!" print(a is b)

выводит True при вызове модуля!

Результат (< Python3.7 )

'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' True 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa' False

Логично, правда?

💡 Объяснение:


▶ Be careful with chained operations

(False == False) in [False] # makes sense False False == (False in [False]) # makes sense False False == False in [False] # now what? True

True is False == False False False is False is False True

1 > 0 < 1 True (1 > 0) < 1 False 1 > (0 < 1) False

💡 Объяснение:

As per https://docs.python.org/3/reference/expressions.html#comparisons

Формально, если a, b, c, ..., y, z - выражения, а op1, op2, ..., opN - операторы сравнения, то a op1 b op2 c ... y opN z эквивалентно a op1 b и b op2 c и ... y opN z, за исключением того, что каждое выражение оценивается не более одного раза.

Хотя такое поведение может показаться вам глупым в приведенных выше примерах, оно просто фантастично для таких вещей, как a == b == c и 0 <= x <= 100.


▶ Как не надо использовать оператор is

Ниже приведен очень известный пример, представленный во всем Интернете.

a = 256 b = 256 a is b True

a = 257 b = 257 a is b False

a = [] b = [] a is b False

a = tuple() b = tuple() a is b True

3.Результат

a, b = 257, 257 a is b True

Вывод (Python 3.7.x specifically)

a, b = 257, 257 a is b False

💡 Объяснение:

Разница между is и ==.

256 - существующий объект, а 257 - нет.

При запуске python будут выделены числа от -5 до 256. Эти числа используются часто, поэтому имеет смысл просто иметь их наготове.

Цитирую по https://docs.python.org/3/c-api/long.html

Текущая реализация хранит массив целочисленных объектов для всех целых чисел от -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, и поэтому он продолжает создавать другой объект в памяти.

Подобная оптимизация применима и к другим изменяемым объектам, таким как пустые кортежи. Поскольку списки являются изменяемыми, поэтому [] is [] вернет False, а () is () вернет True. Это объясняет наш второй фрагмент. Перейдем к третьему,

*И a, и b ссылаются на один и тот же объект при инициализации одним и тем же значением в одной и той же строке.

Вывод

a, b = 257, 257 id(a) 140640774013296 id(b) 140640774013296 a = 257 b = 257 id(a) 140640774013392 id(b) 140640774013488


▶ Hash brownies

some_dict = {} some_dict[5.5] = "JavaScript" some_dict[5.0] = "Ruby" some_dict[5] = "Python"

Вывод:

some_dict[5.5] "JavaScript" some_dict[5.0] # "Python" destroyed the existence of "Ruby"? "Python" some_dict[5] "Python"

complex_five = 5 + 0j type(complex_five) complex some_dict[complex_five] "Python"

Так почему же Python повсюду?

💡 Объяснение.


▶ В глубине души мы все одинаковы.

Вывод:

WTF() == WTF() # two different instances can't be equal False WTF() is WTF() # identities are also different False hash(WTF()) == hash(WTF()) # hashes should be different as well True id(WTF()) == id(WTF()) True

💡 Объяснение:


▶ Нарушение в пределах порядка *

from collections import OrderedDict

dictionary = dict() dictionary[1] = 'a'; dictionary[2] = 'b';

ordered_dict = OrderedDict() ordered_dict[1] = 'a'; ordered_dict[2] = 'b';

another_ordered_dict = OrderedDict() another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';

class DictWithHash(dict): """ A dict that also implements hash magic. """ hash = lambda self: 0

class OrderedDictWithHash(OrderedDict): """ An OrderedDict that also implements hash magic. """ hash = lambda self: 0

Вывод

dictionary == ordered_dict # If a == b True dictionary == another_ordered_dict # and b == c True ordered_dict == another_ordered_dict # then why isn't c == a ?? False

Мы все знаем, что множество состоит только из уникальных элементов,

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

len({dictionary, ordered_dict, another_ordered_dict}) Traceback (most recent call last): File "", line 1, in TypeError: unhashable type: 'dict'

Имеет смысл, поскольку в словаре не реализовано свойство hash, ну чтож давайте использовать

наши классы-обертки.

dictionary = DictWithHash() dictionary[1] = 'a'; dictionary[2] = 'b'; ordered_dict = OrderedDictWithHash() ordered_dict[1] = 'a'; ordered_dict[2] = 'b'; another_ordered_dict = OrderedDictWithHash() another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a'; len({dictionary, ordered_dict, another_ordered_dict}) 1 len({ordered_dict, another_ordered_dict, dictionary}) # changing the order 2

Что здесь происходит?

💡 Объяснение:


▶ Keep trying... *

def some_func(): try: return 'from_try' finally: return 'from_finally'

def another_func(): for _ in range(3): try: continue finally: print("Finally!")

def one_more_func(): # A gotcha! try: for i in range(3): try: 1 / i except ZeroDivisionError: # Let's throw it here and handle it outside for loop raise ZeroDivisionError("A trivial divide by zero error") finally: print("Iteration", i) break except ZeroDivisionError as e: print("Zero division error occurred", e)

Результат:

some_func() 'from_finally'

another_func() Finally! Finally! Finally!

1 / 0 Traceback (most recent call last): File "", line 1, in ZeroDivisionError: division by zero

one_more_func() Iteration 0

💡 Объяснение:


▶ Для чего?

some_string = "wtf" some_dict = {} for i, some_dict[i] in enumerate(some_string): i = 10

Вывод:

some_dict # An indexed dict appears. {0: 'w', 1: 't', 2: 'f'}

💡 Объяснение:

for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]  

Где exprlist - цель присваивания. Это означает, что эквивалент {exprlist} = {next_value} выполняется для каждого элемента в итерабле. Интересный пример, иллюстрирующий это:
for i in range(4):
print(i)
i = 10
Результат:
Ожидали ли вы, что цикл будет запущен только один раз?
💡 Объяснение:.


▶ Несоответствие времени оценки

array = [1, 8, 15]

A typical generator expression

gen = (x for x in array if array.count(x) > 0) array = [2, 8, 22]

Вывод:

print(list(gen)) # Where did the other values go? [8]

array_1 = [1,2,3,4] gen_1 = (x for x in array_1) array_1 = [1,2,3,4,5]

array_2 = [1,2,3,4] gen_2 = (x for x in array_2) array_2[:] = [1,2,3,4,5]

Вывод:

print(list(gen_1)) [1, 2, 3, 4]

print(list(gen_2)) [1, 2, 3, 4, 5]

array_3 = [1, 2, 3] array_4 = [10, 20, 30] gen = (i + j for i in array_3 for j in array_4)

array_3 = [4, 5, 6] array_4 = [400, 500, 600]

Вывод:

print(list(gen)) [401, 501, 601, 402, 502, 602, 403, 503, 603]

💡 Пояснение


is not ... is not is (not ...)

'something' is not None True 'something' is (not None) False

💡 Пояснение


▶ Крестики-нолики, где X побеждает с первой попытки!

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

Мы же не назначили три Х?

💡 Объяснение:

Когда мы инициализируем переменную row, эта визуализация объясняет, что происходит в памяти

image

А когда board инициализируется путем умножения row, вот что происходит в памяти (каждый из элементов board[0], board[1] и board[2] является ссылкой на тот же список, на который ссылается row)

image

Мы можем избежать этого сценария, не используя переменную row для генерации board. (Вопрос задан в этом выпуске).

board = [['']*3 for _ in range(3)] board[0][0] = "X" board [['X', '', ''], ['', '', ''], ['', '', '']]


▶ Переменная Шредингера *

funcs = [] results = [] for x in range(7): def some_func(): return x funcs.append(some_func) results.append(some_func()) # note the function call here

funcs_results = [func() for func in funcs]

Вывод (Python version):

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]

💡 Объяснение:

import inspect inspect.getclosurevars(funcs[0]) ClosureVars(nonlocals={}, globals={'x': 6}, builtins={}, unbound=set())

Since x is a global value, we can change the value that the funcs will lookup and return by updating x:

x = 42 [func() for func in funcs] [42, 42, 42, 42, 42, 42, 42]

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]

x больше не и спользуется в глобальной области видимости

inspect.getclosurevars(funcs[0]) ClosureVars(nonlocals={}, globals={}, builtins={}, unbound=set())


▶ Проблема курицы и яйца *

isinstance(3, int) True isinstance(type, object) True isinstance(object, type) True

Так какой же базовый класс является "окончательным"? Кстати, это еще не все,

class A: pass isinstance(A, A) False isinstance(type, type) True isinstance(object, object) True

issubclass(int, object) True issubclass(type, object) True issubclass(object, type) False

💡 Объяснение


▶ Отношения между подклассами

Вывод:

from collections import Hashable issubclass(list, object) True issubclass(object, Hashable) True issubclass(list, Hashable) False

Предполагается, что отношения подклассов должны быть транзитивными, верно? (т.е. если A является подклассом B, а B является подклассом C, то A должен быть подклассом C)

💡 Пояснение:


▶ Methods equality and identity

class SomeClass: def method(self): pass

@classmethod
def classm(cls):
    pass

@staticmethod
def staticm():
    pass

Результат:

print(SomeClass.method is SomeClass.method) True print(SomeClass.classm is SomeClass.classm) False print(SomeClass.classm == SomeClass.classm) True print(SomeClass.staticm is SomeClass.staticm) True

Обращаясь к classm дважды, мы получаем одинаковый объект, но не одинаковый? Давайте посмотрим, что происходит с экземплярами SomeClass:

o1 = SomeClass() o2 = SomeClass()

Вывод:

print(o1.method == o2.method) False print(o1.method == o1.method) True print(o1.method is o1.method) False print(o1.classm is o1.classm) False print(o1.classm == o1.classm == o2.classm == SomeClass.classm) True print(o1.staticm is o1.staticm is o2.staticm is SomeClass.staticm) True

Двойной доступ к классу или методу создает одинаковые, но не одинаковые объекты для одного и того же экземпляра какого-либо класса.

💡 Пояснение.

o1.method <bound method SomeClass.method of <__main__.SomeClass object at ...>>

SomeClass.method <function SomeClass.method at ...>

o1.classm <bound method SomeClass.classm of <class '__main__.SomeClass'>>

SomeClass.classm <bound method SomeClass.classm of <class '__main__.SomeClass'>>

o1.staticm <function SomeClass.staticm at ...> SomeClass.staticm <function SomeClass.staticm at ...>

▶ All-true-ation *

all([True, True, True]) True all([True, True, False]) False

all([]) True all([[]]) False all([[[]]]) True

Почему это изменение True-False?

💡 Объяснение:


▶ Неожиданная запятая

Вывод (< 3.6):

def f(x, y,): ... print(x, y) ... def g(x=4, y=5,): ... print(x, y) ... def h(x, **kwargs,): File "", line 1 def h(x, **kwargs,): ^ SyntaxError: invalid syntax

def h(*args,): File "", line 1 def h(*args,): ^ SyntaxError: invalid syntax

💡 Объяснение:


▶ Строки и обратные слэши

Вывод:

print(""") "

print(r""") "

print(r"") File "", line 1 print(r"") ^ SyntaxError: EOL while scanning string literal

r''' == "\'" True

💡 Пояснение


▶ not knot!

Результат:

not x == y True x == not y File "", line 1 x == not y ^ SyntaxError: invalid syntax

💡 Объяснение:


▶ Половина строк в тройных кавычках

Вывод:

print('wtfpython''') wtfpython print("wtfpython""") wtfpython

The following statements raise SyntaxError

print('''wtfpython')

print("""wtfpython")

File "", line 3 print("""wtfpython") ^ SyntaxError: EOF while scanning triple-quoted string literal

💡 Объяснение:

>>> print("wtf" "python")  
wtfpython  
>>> print("wtf" "") # or "wtf"""  
wtf  

▶ Что не так с логическими значениями?

A simple example to count the number of booleans 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

Результат:

integers_found_so_far 4 booleans_found_so_far 0

some_bool = True "wtf" * some_bool 'wtf' some_bool = False "wtf" * some_bool ''

def tell_truth(): True = False if True == False: print("I have lost faith in truth!")

Результат (< 3.x):

tell_truth() I have lost faith in truth!

💡 Объяснение:


▶ Атрибуты класса и экземпляра

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 # C.x changed, but B.x didn't (3, 2, 3) a = A() a.x, A.x (3, 3) a.x += 1 a.x, A.x (4, 3)

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

💡 Объяснение:


▶ Возврат None из генератора (yielding None)

some_iterable = ('a', 'b')

def some_func(val): return "something"

Результат (<= 3.7.x):

[x for x in some_iterable] ['a', 'b'] [(yield x) for x in some_iterable] <generator object at 0x7f70b0a4ad58> list([(yield x) for x in some_iterable]) ['a', 'b'] list((yield x) for x in some_iterable) ['a', None, 'b', None] list(some_func((yield x)) for x in some_iterable) ['a', 'something', 'b', 'something']

💡 Объяснение:


▶ Yielding from... return! *

def some_func(x): if x == 3: return ["wtf"] else: yield from range(x)

Результат (> 3.3):

list(some_func(3)) []

Куда исчезло "wtf"? Это связано с каким-то особым эффектом yield from? Давайте проверим это. То же самое, это тоже не сработало.

def some_func(x): if x == 3: return ["wtf"] else: for i in range(x): yield i

Результат:

list(some_func(3)) []

То же самое, это тоже не сработало. Что происходит?

💡 Объяснение:

"... return expr в генераторе вызывает исключение StopIteration(expr) при выходе из генератора."


▶ Nan-reflexivity *

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

x = float('nan') y = x / x y is y # identity holds True y == y # equality fails of y False [y] == [y] # but the equality succeeds for the list containing y True

💡 Объяснение:


▶ Мутируем немутируемое!

Это может показаться тривиальным, если вы знаете, как работают ссылки в Python. Но если вы не знаете, то это может быть немного удивительно.

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

Но я думал, что кортежи неизменяемы... Что происходит?

💡 Объяснение:


▶ Исчезновение переменной из внешней области видимости

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

💡 Объяснение:

This means the exception must be assigned to a different name to be able to refer to it after the except clause. Exceptions are cleared because, with the traceback attached to them, they form a reference cycle with the stack frame, keeping all locals in that frame alive until the next garbage collection occurs.

Nothing is printed!


▶ The mysterious key type conversion

class SomeClass(str): pass

some_dict = {'s': 42}

Результат:

type(list(some_dict.keys())[0]) str s = SomeClass('s') some_dict[s] = 40 some_dict # expected: Two different keys-value pairs {'s': 40} type(list(some_dict.keys())[0]) str

💡 Объяснение:


▶ Посмотрим, сможете ли вы угадать что здесь?

Результат:

💡 Объяснение:

(target_list "=")+ (expression_list | yield_expression)  

and

An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.

▶ Exceeds the limit for integer string conversion

Python 3.10.6

int("2" * 5432)

Python 3.10.8

int("2" * 5432)

Вывод:

Python 3.10.6

222222222222222222222222222222222222222222222222222222222222222...

Python 3.10.8 and Python 3.11

Traceback (most recent call last): ... ValueError: Exceeds the limit (4300) for integer string conversion: value has 5432 digits; use sys.set_int_max_str_digits() to increase the limit.

💡 Объяснение:

Этот вызов int() прекрасно работает в Python 3.10.6 и вызывает ошибку ValueError в Python 3.10.8, 3.11. Обратите внимание, что Python все еще может работать с большими целыми числами. Ошибка возникает только при преобразовании между целыми числами и строками. К счастью, вы можете увеличить предел допустимого количества цифр, если ожидаете, что операция превысит его. Для этого можно воспользоваться одним из следующих способов:

Смотри документацию для получения более подробной информации об изменении лимита по умолчанию, если вы ожидаете, что ваш код превысит это значение.


Section: Slippery Slopes

▶ Modifying a dictionary while iterating over it

x = {0: None}

for i in x: del x[i] x[i+1] = None print(i)

Результат (Python 2.7- Python 3.5):

Yes, it runs for exactly eight times and stops.

💡 Объяснение:


▶ Stubborn del operation

class SomeClass: def del(self): print("Deleted!")

**Результат:**1.

x = SomeClass() y = x del x # this should print "Deleted!" del y Deleted!

Phew, deleted at last. You might have guessed what saved __del__ from being called in our first attempt to delete x. Let's add more twists to the example.

x = SomeClass() y = x del x y # check if y exists <__main__.SomeClass instance at 0x7f98a1a67fc8> del y # Like previously, this should print "Deleted!" globals() # oh, it didn't. Let's check all our global variables and confirm Deleted! {'builtins': <module '__builtin__' (built-in)>, 'SomeClass': <class __main__.SomeClass at 0x7f98a1a5f668>, 'package': None, 'name': 'main', 'doc': None}

Okay, now it's deleted 😕

💡 Объяснение:


▶ The out of scope variable

a = 1 def some_func(): return a

def another_func(): a += 1 return a

def some_closure_func(): a = 1 def some_inner_func(): return a return some_inner_func()

def another_closure_func(): a = 1 def another_inner_func(): a += 1 return a return another_inner_func()

Результат:

some_func() 1 another_func() UnboundLocalError: local variable 'a' referenced before assignment

some_closure_func() 1 another_closure_func() UnboundLocalError: local variable 'a' referenced before assignment

💡 Объяснение:


▶ Deleting a list item while iterating

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]

Can you guess why the output is [2, 4]?

💡 Объяснение:

Difference between del, remove, and pop:

Why the output is [2, 4]?


▶ Lossy zip of iterators *

numbers = list(range(7)) numbers [0, 1, 2, 3, 4, 5, 6] first_three, remaining = numbers[:3], numbers[3:] first_three, remaining ([0, 1, 2], [3, 4, 5, 6]) numbers_iter = iter(numbers) list(zip(numbers_iter, first_three)) [(0, 0), (1, 1), (2, 2)]

so far so good, let's zip the remaining

list(zip(numbers_iter, remaining)) [(4, 3), (5, 4), (6, 5)]

Where did element 3 go from the numbers list?

💡 Объяснение:


▶ Loop variables leaking out!

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

But x was never defined outside the scope of for loop...

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

Результат (Python 2.x):

x = 1 print([x for x in range(5)]) [0, 1, 2, 3, 4] print(x) 4

Результат (Python 3.x):

x = 1 print([x for x in range(5)]) [0, 1, 2, 3, 4] print(x) 1

💡 Объяснение:


▶ Beware of default mutable arguments!

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

💡 Объяснение:


▶ Catching the Exceptions

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 "", line 3 except IndexError, ValueError: ^ SyntaxError: invalid syntax

💡 Объяснение

Caught again!  
list.remove(x): x not in list  

Результат (Python 3.x):
File "", line 4
except (IndexError, ValueError), e:
^
IndentationError: unindent does not match any outer indentation level

Caught again!  
list.remove(x): x not in list  

▶ Same operands, different story!

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]

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]

💡 Объяснение:


▶ Name resolution ignoring class scope

x = 5 class SomeClass: x = 17 y = (x for i in range(10))

Результат:

list(SomeClass.y)[0] 5

x = 5 class SomeClass: x = 17 y = [x for i in range(10)]

Результат (Python 2.x):

Результат (Python 3.x):

💡 Объяснение


▶ Rounding like a banker *

Let's implement a naive function to get the middle element of a list:

def get_middle(some_list): mid_index = round(len(some_list) / 2) return some_list[mid_index - 1]

Python 3.x:

get_middle([1]) # looks good 1 get_middle([1,2,3]) # looks good 2 get_middle([1,2,3,4,5]) # huh? 2 len([1,2,3,4,5]) / 2 # good 2.5 round(len([1,2,3,4,5]) / 2) # why? 2

It seems as though Python rounded 2.5 to 2.

💡 Объяснение:

round(0.5) 0 round(1.5) 2 round(2.5) 2 import numpy # numpy does the same numpy.round(0.5) 0.0 numpy.round(1.5) 2.0 numpy.round(2.5) 2.0


▶ Needles in a Haystack *

I haven't met even a single experience Pythonist till date who has not come across one or more of the following scenarios,

x, y = (0, 1) if True else None, None

Результат:

x, y # expected (0, 1) ((0, 1), None)

t = ('one', 'two') for i in t: print(i)

t = ('one') for i in t: print(i)

t = () print(t)

Результат:

ten_words_list = [
    "some",
    "very",
    "big",
    "list",
    "that"
    "consists",
    "of",
    "exactly",
    "ten",
    "words"
]

Результат

len(ten_words_list) 9

4. Not asserting strongly enough

a = "python" b = "javascript"

Результат:

An assert statement with an assertion failure message.

assert(a == b, "Both languages are different")

No AssertionError is raised

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

def some_recursive_func(a): if a[0] == 0: return a[0] -= 1 some_recursive_func(a) return a

def similar_recursive_func(a): if a == 0: return a a -= 1 similar_recursive_func(a) return a

Результат:

some_recursive_func([5, 0]) [0, 0] similar_recursive_func(5) 4

💡 Объяснение:


▶ Splitsies *

'a'.split() ['a']

is same as

'a'.split(' ') ['a']

but

len(''.split()) 0

isn't the same as

len(''.split(' ')) 1

💡 Объяснение:


▶ Wild imports *

File: module.py

def some_weird_name_func_(): print("works!")

def _another_weird_name_func(): print("works!")

Результат

from module import * some_weird_name_func_() "works!" _another_weird_name_func() Traceback (most recent call last): File "", line 1, in NameError: name '_another_weird_name_func' is not defined

💡 Объяснение:


▶ All sorted? *

x = 7, 8, 9 sorted(x) == x False sorted(x) == sorted(x) True

y = reversed(x) sorted(y) == sorted(y) False

💡 Объяснение:


▶ Midnight time doesn't exist?

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)

Результат (< 3.5):

('Time at noon is', datetime.time(12, 0))

The midnight time is not printed.

💡 Объяснение:

Before Python 3.5, the boolean value for datetime.time object was considered to be False if it represented midnight in UTC. It is error-prone when using the if obj: syntax to check if the obj is null or some equivalent of "empty."



Section: The Hidden treasures!

This section contains a few lesser-known and interesting things about Python that most beginners like me are unaware of (well, not anymore).

▶ Okay Python, Can you make me fly?

Well, here you go

**Результат:**Sshh... It's a super-secret.

💡 Объяснение:


goto, but why?

from goto import goto, label for i in range(9): for j in range(9): for k in range(9): print("I am trapped, please rescue!") if k == 2: goto .breakout # breaking out from a deeply nested loop label .breakout print("Freedom!")

Результат (Python 2.3):

I am trapped, please rescue! I am trapped, please rescue! Freedom!

💡 Объяснение:


▶ Держитесь!

Если вы относитесь к тем людям, которым не нравится использование пробелов в Python для обозначения диапазонов, вы можете использовать C-стиль {} импортировав это,

from future import braces

Результат:

File "some_file.py", line 1 from future import braces SyntaxError: not a chance

Скобочки? Ни за что! Если это разочаровывало вас, используйте PHP :). Хорошо, еще одна удивительная вещь, можете ли вы найти ошибкуSyntaxError которая вызвана в модуле __future__ code?

💡 Объяснение:


▶ Давайте познакомимся с дружелюбным Дядей Барри

Результат (Python 3.x)

from future import barry_as_FLUFL "Ruby" != "Python" # there's no doubt about it File "some_file.py", line 1 "Ruby" != "Python" ^ SyntaxError: invalid syntax

"Ruby" <> "Python" True

Вот так просто.

💡 Объяснение:


▶ Даже Python понимает, что любовь - это сложно.

Подождите, что это (this) такое? Это любовь! ❤️

Результат:

Дзен Python, от Тима Петерса

Красивое лучше, чем уродливое.
Явное лучше, чем неявное.
Простое лучше, чем сложное.
Сложное лучше, чем запутанное.
Плоское лучше, чем вложенное.
Разреженное лучше, чем плотное.
Читаемость имеет значение.
Особые случаи не настолько особые, чтобы нарушать правила.
При этом практичность важнее безупречности.
Ошибки никогда не должны замалчиваться.
Если они не замалчиваются явно.
Встретив двусмысленность, отбрось искушение угадать.
Должен существовать один и, желательно, только один очевидный способ сделать это.
Хотя он поначалу может быть и не очевиден, если вы не голландец [^1].
Сейчас лучше, чем никогда.
Хотя никогда зачастую лучше, чем прямо сейчас.
Если реализацию сложно объяснить — идея плоха.
Если реализацию легко объяснить — идея, возможно, хороша.
Пространства имён — отличная штука! Будем делать их больше!

Это Дзен Python!

love = this this is love True love is True False love is False False love is not True or False True love is not True or False; love is love # Love is complicated True

💡 Объяснение:


▶ Yes, it exists!

The else clause for loops. One typical example might be:

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

The else clause in exception handling. An example,

try: pass except: print("Exception occurred!!!") else: print("Try block executed successfully...")

Результат:

Try block executed successfully...

💡 Объяснение:


▶ Ellipsis *

def some_func(): Ellipsis

Результат

some_func()

No output, No Error

SomeRandomString Traceback (most recent call last): File "", line 1, in NameError: name 'SomeRandomString' is not defined

Ellipsis Ellipsis

💡 Объяснение


▶ Inpinity

The spelling is intended. Please, don't submit a patch for this.

Результат (Python 3.x):

infinity = float('infinity') hash(infinity) 314159 hash(float('-inf')) -314159

💡 Объяснение:


▶ Let's mangle

class Yo(object): def init(self): self.__honey = True self.bro = True

Результат:

Yo().bro True Yo().__honey AttributeError: 'Yo' object has no attribute '__honey' Yo()._Yo__honey True

class Yo(object): def init(self): # Let's try something symmetrical this time self.honey = True self.bro = True

Результат:

Yo().bro True

Yo().Yo__honey_ Traceback (most recent call last): File "", line 1, in AttributeError: 'Yo' object has no attribute 'Yo__honey_'

Why did Yo()._Yo__honey work?

_A__variable = "Some value"

class A(object): def some_func(self): return __variable # not initialized anywhere yet

Результат:

A().__variable Traceback (most recent call last): File "", line 1, in AttributeError: 'A' object has no attribute '__variable'

A().some_func() 'Some value'

💡 Объяснение:



Section: Appearances are deceptive!

▶ Skipping lines?

Результат:

value = 11 valuе = 32 value 11

Wut?

Note: The easiest way to reproduce this is to simply copy the statements from the above snippet and paste them into your file/shell.

💡 Объяснение

Some non-Western characters look identical to letters in the English alphabet but are considered distinct by the interpreter.

ord('е') # cyrillic 'e' (Ye) 1077 ord('e') # latin 'e', as used in English and typed using standard keyboard 101 'е' == 'e' False

value = 42 # latin e valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a SyntaxError here value 42

The built-in ord() function returns a character's Unicode code point, and different code positions of Cyrillic 'e' and Latin 'e' justify the behavior of the above example.


▶ Teleportation

pip install numpy first.

import numpy as np

def energy_send(x): # Initializing a numpy array np.array([float(x)])

def energy_receive(): # Return an empty numpy array return np.empty((), dtype=np.float).tolist()

Результат:

energy_send(123.456) energy_receive() 123.456

Where's the Nobel Prize?

💡 Объяснение:


▶ Well, something is fishy...

def square(x): """ A simple function to calculate the square of a number by addition. """ sum_so_far = 0 for counter in range(x): sum_so_far = sum_so_far + x return sum_so_far

Результат (Python 2.x):

Shouldn't that be 100?

Note: If you're not able to reproduce this, try running the file mixed_tabs_and_spaces.py via the shell.

💡 Объяснение



Section: Miscellaneous

+= is faster

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

💡 Объяснение:


▶ Let's make a giant string!

def add_string_with_plus(iters): s = "" for i in range(iters): s += "xyz" assert len(s) == 3*iters

def add_bytes_with_plus(iters): s = b"" for i in range(iters): s += b"xyz" assert len(s) == 3*iters

def add_string_with_format(iters): fs = "{}"iters s = fs.format((["xyz"]iters)) assert len(s) == 3iters

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

Результат:

Executed in ipython shell using %timeit for better readability of results.

You can also use the timeit module in normal python shell/scriptm=, example usage below

timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())

NUM_ITERS = 1000 %timeit -n1000 add_string_with_plus(NUM_ITERS) 124 µs ± 4.73 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit -n1000 add_bytes_with_plus(NUM_ITERS) 211 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n1000 add_string_with_format(NUM_ITERS) 61 µs ± 2.18 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n1000 add_string_with_join(NUM_ITERS) 117 µs ± 3.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) l = ["xyz"]*NUM_ITERS %timeit -n1000 convert_list_to_string(l, NUM_ITERS) 10.1 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Let's increase the number of iterations by a factor of 10.

NUM_ITERS = 10000 %timeit -n1000 add_string_with_plus(NUM_ITERS) # Linear increase in execution time 1.26 ms ± 76.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n1000 add_bytes_with_plus(NUM_ITERS) # Quadratic increase 6.82 ms ± 134 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n1000 add_string_with_format(NUM_ITERS) # Linear increase 645 µs ± 24.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n1000 add_string_with_join(NUM_ITERS) # Linear increase 1.17 ms ± 7.25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) l = ["xyz"]*NUM_ITERS %timeit -n1000 convert_list_to_string(l, NUM_ITERS) # Linear increase 86.3 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

💡 Объяснение


▶ Slowing down dict lookups *

some_dict = {str(i): 1 for i in range(1_000_000)} another_dict = {str(i): 1 for i in range(1_000_000)}

Результат:

%timeit some_dict['5'] 28.6 ns ± 0.115 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) some_dict[1] = 1 %timeit some_dict['5'] 37.2 ns ± 0.265 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%timeit another_dict['5'] 28.5 ns ± 0.142 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) another_dict[1] # Trying to access a key that doesn't exist Traceback (most recent call last): File "", line 1, in KeyError: 1 %timeit another_dict['5'] 38.5 ns ± 0.0913 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Why are same lookups becoming slower?

💡 Объяснение:

▶ Bloating instance dicts *

import sys

class SomeClass: def init(self): self.some_attr1 = 1 self.some_attr2 = 2 self.some_attr3 = 3 self.some_attr4 = 4

def dict_size(o): return sys.getsizeof(o.dict)

Результат: (Python 3.8, other Python 3 versions may vary a little)

o1 = SomeClass() o2 = SomeClass() dict_size(o1) 104 dict_size(o2) 104 del o1.some_attr1 o3 = SomeClass() dict_size(o3) 232 dict_size(o1) 232

Let's try again... In a new interpreter:

o1 = SomeClass() o2 = SomeClass() dict_size(o1) 104 # as expected o1.some_attr5 = 5 o1.some_attr6 = 6 dict_size(o1) 360 dict_size(o2) 272 o3 = SomeClass() dict_size(o3) 232

What makes those dictionaries become bloated? And why are newly created objects bloated as well?

💡 Объяснение:

▶ Minor Ones *

File some_file.py

import time
print("wtfpython", end="_")
time.sleep(3)
This will print the wtfpython after 3 seconds due to the end argument because the output buffer is flushed either after encountering \n or when the program finishes execution. We can force the buffer to flush by passing flush=True argument.



Contributing

A few ways in which you can contribute to wtfpython,

Please see CONTRIBUTING.md for more details. Feel free to create a new issue to discuss things.

PS: Please don't reach out with backlinking requests, no links will be added unless they're highly relevant to the project.

Acknowledgements

The idea and design for this collection were initially inspired by Denys Dovhan's awesome project wtfjs. The overwhelming support by Pythonistas gave it the shape it is in right now.

🎓 License

WTFPL 2.0

© Satwik Kansal

Surprise your friends as well!

If you like wtfpython, you can use these quick links to share it with your friends,

Twitter | Linkedin | Facebook

Need a pdf version?

I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details here to get them as soon as they are finished.

That's all folks! For upcoming content like this, you can add your email here.