Home

Awesome

<p align="center"><img src="/images/logo.png" alt=""></p> <h1 align="center">What the f*ck Python! 😱</h1> <p align="center">Изучение и понимание Python с помощью нестандартного поведения и "магического" поведения.</p>

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

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

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

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

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

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

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

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

Table of Contents

<!-- Generated using "markdown-toc -i README.md --maxdepth 3"--> <!-- toc --> <!-- tocstop -->

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

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

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

# Код с приколдесами.
# Подготовка к магии...

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

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

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

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

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

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

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

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

Применение

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

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

$ pip install wtfpython -U
$ wtfpython

👀 Примеры

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

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

<!-- Example ID: d3d73936-3cf1-4632-b5ab-817981338863 --> <!-- read-only -->

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

1.

# Python version 3.8+

>>> a = "wtf_walrus"
>>> a
'wtf_walrus'

>>> a := "wtf_walrus"
File "<stdin>", 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 "<stdin>", 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):

5
5
5

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


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

<!-- Example ID: 30f1d3fc-e267-4b30-84ef-4d9e7091ac1a --->

1.

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

2.

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

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

3.

>>> 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 при вызове модуля!

4.

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

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

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

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


▶ Be careful with chained operations

<!-- Example ID: 07974979-9c86-4720-80bd-467aa19470d9 --->
>>> (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

<!-- Example ID: 230fa2ac-ab36-4ad1-b675-5f5a1c1a6217 --->

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

1.

>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False

2.

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

<!-- Example ID: eb17db53-49fd-4b61-85d6-345c5ca213ff --->

1.

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 повсюду?

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


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

<!-- Example ID: 8f99a35f-1736-43e2-920d-3b78ec35da9b --->
class WTF:
  pass

Вывод:

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

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


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

<!-- Example ID: 91bff1f8-541d-455a-9de4-6cd8ff00ea66 --->
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 "<stdin>", line 1, in <module>
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... *

<!-- Example ID: b4349443-e89f-4d25-a109-82616be9d41a --->
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 "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

>>> one_more_func()
Iteration 0

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


▶ Для чего?

<!-- Example ID: 64a9dccf-5083-4bc9-98aa-8aeecde4f210 --->
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'}

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


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

<!-- Example ID: 6aa11a4b-4cf1-467a-b43a-810731517e98 --->

1.

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]

2.

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]

3.

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

<!-- Example ID: b26fb1ed-0c7d-4b9c-8c6d-94a58a055c0d --->
>>> 'something' is not None
True
>>> 'something' is (not None)
False

💡 Пояснение


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

<!-- Example ID: 69329249-bdcb-424f-bd09-cca2e6705a7a --->
# 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', '', ''], ['', '', ''], ['', '', '']]

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

<!-- Example ID: 4dc42f77-94cb-4eb5-a120-8203d3ed7604 --->
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())

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

<!-- Example ID: 60730dc2-0d79-4416-8568-2a63323b3ce8 --->

1.

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

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

2.

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

3.

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

💡 Объяснение


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

<!-- Example ID: 9f6d8cf0-e1b5-42d0-84a0-4cfab25a0bc0 --->

Вывод:

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

<!-- Example ID: 94802911-48fe-4242-defa-728ae893fa32 --->
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 *

<!-- Example ID: dfe6d845-e452-48fe-a2da-0ed3869a8042 -->
>>> all([True, True, True])
True
>>> all([True, True, False])
False

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

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

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


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

<!-- Example ID: 31a819c8-ed73-4dcc-84eb-91bedbb51e58 --->

Вывод (< 3.6):

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

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

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


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

<!-- Example ID: 6ae622c3-6d99-4041-9b33-507bd1a4407b --->

Вывод:

>>> print("\"")
"

>>> print(r"\"")
\"

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

>>> r'\'' == "\\'"
True

💡 Пояснение


▶ not knot!

<!-- Example ID: 7034deb1-7443-417d-94ee-29a800524de8 --->
x = True
y = False

Результат:

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

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


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

<!-- Example ID: c55da3e2-1034-43b9-abeb-a7a970a2ad9e --->

Вывод:

>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # The following statements raise `SyntaxError`
>>> # print('''wtfpython')
>>> # print("""wtfpython")
  File "<input>", line 3
    print("""wtfpython")
                        ^
SyntaxError: EOF while scanning triple-quoted string literal

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


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

<!-- Example ID: 0bba5fa7-9e6d-4cd2-8b94-952d061af5dd --->

1.

# 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

2.

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

3.

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!

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


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

<!-- Example ID: 6f332208-33bd-482d-8106-42863b739ed9 --->

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

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

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


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

<!-- Example ID: 5a40c241-2c30-40d0-8ba9-cf7e097b3b53 --->
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 <listcomp> 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! *

<!-- Example ID: 5626d8ef-8802-49c2-adbc-7cda5c550816 --->

1.

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

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

>>> list(some_func(3))
[]

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

2.

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 *

<!-- Example ID: 59bee91a-36e0-47a4-8c7d-aa89bf1d3976 --->

1.

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

2.

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

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


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

<!-- Example ID: 15a9e782-1695-43ea-817a-a9208f6bb33d --->

Это может показаться тривиальным, если вы знаете, как работают ссылки в 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])

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

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


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

<!-- Example ID: 7f1e71b6-cb3e-44fb-aa47-87ef1b7decc8 --->
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

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


▶ The mysterious key type conversion

<!-- Example ID: 00f42dd0-b9ef-408d-9e39-1bc209ce3f36 --->
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

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


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

<!-- Example ID: 81aa9fbe-bd63-4283-b56d-6fdd14c9105e --->
a, b = a[b] = {}, 5

Результат:

>>> a
{5: ({...}, 5)}

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

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

<!-- Example ID: b4e5cdfb-c3a8-4112-bd38-e2356d801c41 --->
x = {0: None}

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

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

0
1
2
3
4
5
6
7

Yes, it runs for exactly eight times and stops.

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


▶ Stubborn del operation

<!-- Example ID: 777ed4fd-3a2d-466f-95e7-c4058e61d78e ---> <!-- read-only -->
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.

2.

>>> 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 :confused:

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


▶ The out of scope variable

<!-- Example ID: 75c03015-7be9-4289-9e22-4f5fdda056f7 --->

1.

a = 1
def some_func():
    return a

def another_func():
    a += 1
    return a

2.

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

<!-- Example ID: 4cc52d4e-d42b-4e09-b25f-fbf5699b7d4e --->
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 *

<!-- Example ID: c28ed154-e59f-4070-8eb6-8967a4acac6d --->
>>> 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!

<!-- Example ID: ccec7bf6-7679-4963-907a-1cd8587be9ea --->

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

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

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.

Результат (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!

<!-- Example ID: 7d42dade-e20d-4a7b-9ed7-16fb58505fe9 --->
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

<!-- Example ID: b5ca5e6a-47b9-4f69-9375-cda0f8c6755d --->
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

💡 Объяснение


▶ Same operands, different story!

<!-- Example ID: ca052cdf-dd2d-4105-b936-65c28adc18a0 --->

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]

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


▶ Name resolution ignoring class scope

<!-- Example ID: 03f73d96-151c-4929-b0a8-f74430788324 --->

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

💡 Объяснение


▶ 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 *

<!-- Example ID: 52a199b1-989a-4b28-8910-dff562cebba9 --->

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

1.

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

Результат:

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

2.

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

3.

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

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

6.

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 *

<!-- Example ID: ec3168ba-a81a-4482-afb0-691f1cc8d65a --->
>>> 'a'.split()
['a']

# is same as
>>> 'a'.split(' ')
['a']

# but
>>> len(''.split())
0

# isn't the same as
>>> len(''.split(' '))
1

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


▶ Wild imports *

<!-- Example ID: 83deb561-bd55-4461-bb5e-77dd7f411e1c ---> <!-- read-only -->
# 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 "<stdin>", line 1, in <module>
NameError: name '_another_weird_name_func' is not defined

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


▶ All sorted? *

<!-- Example ID: e5ff1eaf-8823-4738-b4ce-b73f7c9d5511 -->
>>> 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?

<!-- Example ID: 1bce8294-5619-4d70-8ce3-fe0bade690d1 --->
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?

<!-- Example ID: a92f3645-1899-4d50-9721-0031be4aec3f --->

Well, here you go

import antigravity

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

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


goto, but why?

<!-- Example ID: 2aff961e-7fa5-4986-a18a-9e5894bd89fe --->
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!

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


▶ Держитесь!

<!-- Example ID: 5c0c75f2-ddd9-4da3-ba49-c4be7ec39acf --->

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

from __future__ import braces

Результат:

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

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

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


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

<!-- Example ID: 6427fae6-e959-462d-85da-ce4c94ce41be --->

Результат (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 понимает, что любовь - это сложно.

<!-- Example ID: b93cad9e-d341-45d1-999c-fcdce65bed25 --->
import this

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

Результат:

Дзен 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!

<!-- Example ID: 4286db3d-1ea7-47c9-8fb6-a9a04cac6e49 --->

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 *

<!-- Example ID: 969b7100-ab3d-4a7d-ad7d-a6be16181b2b --->
def some_func():
    Ellipsis

Результат

>>> some_func()
# No output, No Error

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

>>> Ellipsis
Ellipsis

💡 Объяснение


▶ Inpinity

<!-- Example ID: ff473ea8-a3b1-4876-a6f0-4378aff790c1 --->

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

<!-- Example ID: 37146d2d-9e67-43a9-8729-3c17934b910c --->

1.

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

2.

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 "<stdin>", line 1, in <module>
AttributeError: 'Yo' object has no attribute '_Yo__honey__'

Why did Yo()._Yo__honey work?

3.

_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 "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute '__variable'

>>> A().some_func()
'Some value'

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



Section: Appearances are deceptive!

▶ Skipping lines?

<!-- Example ID: d50bbde1-fb9d-4735-9633-3444b9d2f417 --->

Результат:

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

<!-- Example ID: edafe923-0c20-4315-b6e1-0c31abfc38f5 --->
# `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...

<!-- Example ID: cb6a37c5-74f7-44ca-b58c-3b902419b362 --->
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):

>>> square(10)
10

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

<!-- Example ID: bfd19c60-a807-4a26-9598-4912b86ddb36 --->
# 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!

<!-- Example ID: c7a07424-63fe-4504-9842-8f3d334f28fc --->
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) == 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

Результат:

# 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 *

<!-- Example ID: c9c26ce6-df0c-47f7-af0b-966b9386d4c3 --->
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 "<stdin>", line 1, in <module>
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 *

<!-- Example ID: fe706ab4-1615-c0ba-a078-76c98cbe3f48 --->
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 *

<!-- Example ID: f885cb82-f1e4-4daa-9ff3-972b14cb1324 --->

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.

Some nice Links!

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