-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathREADME.md
3839 lines (3068 loc) · 149 KB
/
README.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<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](https://github.com/satwikkansal/wtfpython) | [Russian Русский](https://github.com/frontdevops/wtfpython) | [Chinese 中文](https://github.com/robertparley/wtfpython-cn) | [Vietnamese Tiếng Việt](https://github.com/vuduclyunitn/wtfptyhon-vi) | [Spanish Español](https://web.archive.org/web/20220511161045/https://github.com/JoseDeFreitas/wtfpython-es) | [Korean 한국어](https://github.com/buttercrab/wtfpython-ko) | [Add translation](https://github.com/satwikkansal/wtfpython/issues/new?title=Add%20translation%20for%20[LANGUAGE]&body=Expected%20time%20to%20finish:%20[X]%20weeks.%20I%27ll%20start%20working%20on%20it%20from%20[Y].)
Еще способы попробовать: [Interactive](https://colab.research.google.com/github/satwikkansal/wtfpython/blob/master/irrelevant/wtf.ipynb) | [CLI](https://pypi.python.org/pypi/wtfpython)
Python, будучи прекрасно разработанным языком программирования высокого уровня с интерпретатором, предоставляет нам множество возможностей для удобства программиста. Но иногда результаты работы фрагмента Python могут показаться неочевидными на первый взгляд.
Вот забавный проект, пытающийся объяснить, что именно происходит под капотом некоторых неинтуитивных сниппетов и менее известных возможностей Python.
Хотя некоторые из примеров, которые вы увидите ниже, возможно, не являются WTF в прямом смысле этого слова, но они раскроют некоторые интересные части Python, о которых вы могли не знать. Я считаю, что это хороший способ изучить внутреннее устройство языка программирования, и я верю, что вам это тоже покажется интересным!
Если вы опытный программист на Python, вы можете принять это как вызов, чтобы получить большинство из них правильно с первой попытки. Возможно, вы уже сталкивались с некоторыми из них раньше, и я смогу оживить ваши старые добрые воспоминания! :sweat_smile:
PS: Если вы постоянный читатель, вы можете узнать о новых изменениях [здесь](https://github.com/satwikkansal/wtfpython/releases/) (примеры, отмеченные звездочкой - это примеры, добавленные в последней основной редакции).
Ну чтож, начнем...
# Table of Contents
<!-- Generated using "markdown-toc -i README.md --maxdepth 3"-->
<!-- toc -->
- [Структура примеров](#structure-of-the-examples)
+ [▶ Какое-то модное название](#-some-fancy-title)
- [Применение](#usage)
- [👀 Примеры](#-examples)
* [Раздел: Напряги мозги!](#section-strain-your-brain)
+ [▶ Перво-наперво! *](#-first-things-first-)
+ [▶ Иногда строки могут быть хитрыми](#-strings-can-be-tricky-sometimes)
+ [▶ Осторожнее с цепочками операций](#-be-careful-with-chained-operations)
+ [▶ Как не надо использовать оператор `is`](#-how-not-to-use-is-operator)
+ [▶ Мистика хэширования](#-hash-brownies)
+ [▶ В глубине души мы все одинаковы](#-deep-down-were-all-the-same)
+ [▶ Беспорядок внутри порядка *](#-disorder-within-order-)
+ [▶ Продолжай пытаться... *](#-keep-trying-)
+ [▶ Для чего?](#-for-what)
+ [▶ Расхождение во времени оценки](#-evaluation-time-discrepancy)
+ [▶ `is not ...` не является `is(not ...)`](#-is-not--is-not-is-not-)
+ [▶ Крестики-нолики, где X побеждает в первой попытке!](#-a-tic-tac-toe-where-x-wins-in-the-first-attempt)
+ [▶ Переменная Шредингера](#-schrödingers-variable-)
+ [▶ The chicken-egg problem *](#-the-chicken-egg-problem-)
+ [▶ Отношения подклассов](#-subclass-relationships)
+ [▶ Методы равенства и тождества](#-methods-equality-and-identity)
+ [▶ All-true-ation *](#-all-true-ation-)
+ [▶ Удивительная запятая](#-the-surprising-comma)
+ [▶ Строки и обратные слеши](#-strings-and-the-backslashes)
+ [▶ not knot!](#-not-knot)
+ [▶ Строки с половиной тройных кавычек](#-half-triple-quoted-strings)
+ [▶ What's wrong with booleans?](#-whats-wrong-with-booleans)
+ [▶ Атрибуты класса и атрибуты экземпляра](#-class-attributes-and-instance-attributes)
+ [▶ yielding None](#-yielding-none)
+ [▶ Yielding из... возврата! *](#-yielding-from-return-)
+ [▶ NaN-рефлексивность *](#-nan-reflexivity-)
+ [▶ Мутация неизменного!](#-mutating-the-immutable)
+ [▶ Исчезающая переменная из внешней области видимости](#-the-disappearing-variable-from-outer-scope)
+ [▶ Загадочное преобразование типа ключа](#-the-mysterious-key-type-conversion)
+ [▶ Посмотрим, сможете ли вы угадать это?](#-lets-see-if-you-can-guess-this)
+ [▶ Exceeds the limit for integer string conversion](#-exceeds-the-limit-for-integer-string-conversion)
* [Раздел: Скользкие склоны](#section-slippery-slopes)
+ [▶ Изменение словаря во время итерации по нему](#-modifying-a-dictionary-while-iterating-over-it)
+ [▶ Упрямая операция `del`](#-stubborn-del-operation)
+ [▶ Переменная вне области видимости](#-the-out-of-scope-variable)
+ [▶ Удаление элемента списка во время итерации](#-deleting-a-list-item-while-iterating)
+ [▶ zip итераторов с потерями *](#-lossy-zip-of-iterators-)
+ [▶ Loop variables leak out!](#-loop-variables-leaking-out)
+ [▶ Остерегайтесь мутабельных аргументов по умолчанию!](#-beware-of-default-mutable-arguments)
+ [▶ Catching the Exceptions](#-catching-the-exceptions)
+ [▶ Same operands, different story!](#-same-operands-different-story)
+ [▶ Разрешение имен, игнорирующее область действия класса](#-name-resolution-ignoring-class-scope)
+ [▶ Округление как у банкира *](#-rounding-like-a-banker-)
+ [▶ Needles in a Haystack *](#-needles-in-a-haystack-)
+ [▶ Splitsies *](#-splitsies-)
+ [▶ Дикий импорт *](#-wild-imports-)
+ [▶ Все отсортировано? *](#-all-sorted-)
+ [▶ Полуночное время не существует?](#-midnight-time-doesnt-exist)
* [Раздел: Скрытые сокровища!](#section-the-hidden-treasures)
+ [▶ О'кей Питон, ты можешь заставить меня летать?](#-okay-python-can-you-make-me-fly)
+ [▶ `goto`, но почему?](#-goto-but-why)
+ [▶ Держитесь!](#-brace-yourself)
+ [▶ Давайте встретимся с дружелюбным языковым дядей на всю жизнь](#-lets-meet-friendly-language-uncle-for-life)
+ [▶ Даже Python понимает, что любовь - это сложно](#-even-python-understands-that-love-is-complicated)
+ [▶ Да, это существует!](#-yes-it-exists)
+ [▶ Многоточие *](#-ellipsis-)
+ [▶ Inpinity](#-inpinity)
+ [▶ Let's mangle](#-lets-mangle)
* [Раздел: Внешность обманчива!](#section-appearances-are-deceptive)
+ [▶ Пропускаете строки?](#-skipping-lines)
+ [▶ Телепортация](#-teleportation)
+ [▶ Ну, что-то тут нечисто...](#-well-something-is-fishy)
* [Раздел: Разное](#section-miscellaneous)
+ [▶ `+=` быстрее](#--is-faster)
+ [▶ Давайте создадим гигантскую строку!](#-lets-make-a-giant-string)
+ [▶ Замедление поиска `dict` *](#-slowing-down-dict-lookups-)
+ [▶ Раздувание экземпляров словарей *](#-bloating-instance-dicts-)
+ [▶ Minor Ones *](#-minor-ones-)
- [Contributing](#contributing)
- [Благодарности](#acknowledgements)
- [🎓 Лицензия](#-license)
* [Удивите и своих друзей!](#surprise-your-friends-as-well)
* [Больше подобных материалов?](#more-content-like-this)
<!-- tocstop -->
# Структура примеров
Все примеры имеют следующую структуру:
> ### ▶ Какой-то заголовок
>
> ```py
> # Код с приколдесами.
> # Подготовка к магии...
> ```
>
> **Вывод (Python версия(и)):**
>
> ```py
> >>> triggering_statement
> Неожиданные результаты
> ```
> (Опционально): Одна строка, описывающая неожиданный результат
>
>
> #### 💡 Объяснение:
>
> * Краткое объяснение того, что происходит и почему это происходит.
> ```py
> # Код
> # Дополнительные примеры для дальнейшего разъяснения (если необходимо)
> ```
> **Вывод (Python версия(и)):**
>
> ```py
> >>> trigger # какой-нибудь пример, позволяющий легко раскрыть магию
> # обоснованный вывод
> ```
**Важно:** Все примеры протестированы на интерактивном интерпретаторе Python 3.5.2, и они должны работать для всех версий Python, если это явно не указано перед выводом.
# Применение
Хороший способ получить максимальную пользу от этих примеров, на мой взгляд, - читать их в последовательном порядке, причем для каждого примера:
- Внимательно прочитайте исходный код для настройки примера. Если вы опытный программист на Python, то в большинстве случаев вы сможете предугадать, что произойдет дальше.
- Прочитайте фрагменты вывода и,
+ Проверьте, совпадают ли выходные данные с вашими ожиданиями.
+ Убедитесь, что вы знаете точную причину, по которой вывод получился именно таким.
- Если ответ отрицательный (что совершенно нормально), сделайте глубокий вдох и прочитайте объяснение (а если вы все еще не понимаете, крикните! и создайте проблему [здесь](https://github.com/satwikkansal/wtfpython/issues/new)).
- Если "да", похлопайте себя по спине и переходите к следующему примеру.
PS: Вы также можете читать WTFPython в командной строке, используя [pypi package](https://pypi.python.org/pypi/wtfpython),
```sh
$ pip install wtfpython -U
$ wtfpython
```
---
# 👀 Примеры
## Секция: Напряги мозги!
### ▶ Важное о главном!
<!-- Example ID: d3d73936-3cf1-4632-b5ab-817981338863 -->
<!-- read-only -->
По какой-то причине "моржовый оператор"(walrus) (`:=`) в Python 3.8 стал довольно популярным. Давайте проверим его,
1\.
```py
# 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 \.
```py
# 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, она может быть полезна в ситуациях, когда вы хотите присвоить значения переменным в выражении.
```py
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):**
```py
5
5
5
```
Это сэкономило одну строку кода и неявно предотвратило вызов `some_func` дважды.
- Непарентезированное "выражение присваивания" (использование моржового оператора), ограничено на верхнем уровне, отсюда `SyntaxError` в выражении `a := "wtf_walrus"` первого фрагмента. Расстановка парентез сработала, как и ожидалось, и присвоила `a`.
- Как обычно, выделение скобками выражения, содержащего оператор `=`, не допускается. Отсюда синтаксическая ошибка в `(a, b = 6, 9)`.
- Синтаксис моржового оператора имеет вид `NAME:= expr`, где `NAME` - допустимый идентификатор, а `expr` - допустимое выражение. Следовательно, упаковка и распаковка итерабельных выражений не поддерживается, что означает,
- `(a := 6, 9)` is equivalent to `((a := 6), 9)` and ultimately `(a, 9) ` (where `a`'s value is 6')
```py
>>> (a := 6, 9) == ((a := 6), 9)
True
>>> x = (a := 696, 9)
>>> x
(696, 9)
>>> x[0] is a # Both reference same memory location
True
```
- Similarly, `(a, b := 16, 19)` is equivalent to `(a, (b := 16), 19)` which is nothing but a 3-tuple.
---
### ▶ Строки иногда ведут себя непредсказуемо
<!-- Example ID: 30f1d3fc-e267-4b30-84ef-4d9e7091ac1a --->
1\.
```py
>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # Notice that both the ids are same.
140420665652016
```
2\.
```py
>>> a = "wtf"
>>> b = "wtf"
>>> a is b
True
>>> a = "wtf!"
>>> b = "wtf!"
>>> a is b
False
```
3\.
```py
>>> 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
```
```py
# This time in file some_file.py
a = "wtf!"
b = "wtf!"
print(a is b)
# выводит True при вызове модуля!
```
4\.
**Результат (< Python3.7 )**
```py
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
```
Логично, правда?
#### 💡 Объяснение:
+ Поведение в первом и втором фрагментах связано с оптимизацией CPython (называемой интернированием строк), которая пытается использовать существующие неизменяемые объекты в некоторых случаях вместо того, чтобы каждый раз создавать новый объект.
+ После "интернирования" многие переменные могут ссылаться на один и тот же строковый объект в памяти (тем самым экономя память).
+ В приведенных выше фрагментах строки неявно интернированы. Решение о том, когда неявно интернировать строку, зависит от реализации. Существуют некоторые правила, по которым можно определить, будет ли строка интернирована или нет:
* Все строки длины 0 и длины 1 интернируются.
* Строки интернируются во время компиляции (`'wtf'' будет интернирована, но `''.join(['w'', 't'', 'f'])` не будет интернирована)
* Строки, не состоящие из букв ASCII, цифр или знаков подчеркивания, не интернализируются. Это объясняет, почему `'wtf!'` не интернируется из-за `!`. Реализацию этого правила в CPython можно найти [здесь](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19)

+ Когда `a` и `b` имеют значение `"wtf!"` в одной строке, интерпретатор Python создает новый объект, а затем одновременно ссылается на вторую переменную. Если вы делаете это в отдельных строках, он не "знает", что уже существует `"wtf!"` как объект (потому что `"wtf!"` не является неявно интернированным в соответствии с фактами, упомянутыми выше). Это оптимизация времени компиляции. Эта оптимизация не применяется к версиям CPython 3.7.x (более подробное обсуждение смотрите здесь [issue](https://github.com/satwikkansal/wtfpython/issues/100)).
+ Единица компиляции в интерактивной среде, такой как IPython, состоит из одного оператора, тогда как в случае модулей она состоит из всего модуля. `a, b = "wtf!", "wtf!"` - это одно утверждение, тогда как `a = "wtf!"; b = "wtf!"` - это два утверждения в одной строке. Это объясняет, почему тождества различны в `a = "wtf!"; b = "wtf!"`, а также объясняет, почему они одинаковы при вызове в `some_file.py`.
+ Резкое изменение в выводе четвертого фрагмента связано с [peephole optimization](https://en.wikipedia.org/wiki/Peephole_optimization) техникой, известной как Constant folding. Это означает, что выражение `'a'*20` заменяется на `'aaaaaaaaaaaaaaaaaaaa'` во время компиляции, чтобы сэкономить несколько тактов во время выполнения. Складывание констант происходит только для строк длиной менее 21. (Почему? Представьте себе размер файла `.pyc`, созданного в результате выражения `'a'*10**10`). [Вот](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288) исходный текст реализации для этого.
+ Примечание: В Python 3.7 складывание констант было перенесено из оптимизатора peephole в новый оптимизатор AST с некоторыми изменениями в логике, поэтому четвертый фрагмент не работает в Python 3.7. Подробнее об изменении можно прочитать [здесь](https://bugs.python.org/issue11549).
---
### ▶ Be careful with chained operations
<!-- Example ID: 07974979-9c86-4720-80bd-467aa19470d9 --->
```py
>>> (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`.
* `False is False is False` эквивалентно `(False is False) и (False is False)`.
* ``True is False == False`` эквивалентно ``(True is False) and (False == False)`` и так как первая часть высказывания (`True is False``) оценивается в `False``, то все выражение оценивается в `False``.
* `1 > 0 < 1` эквивалентно `(1 > 0) и (0 < 1)`, которое оценивается в `True`.
* Выражение `(1 > 0) < 1` эквивалентно `True < 1` и
```py
>>> int(True)
1
>>> True + 1 # не имеет значения для данного примера, но просто для интереса
2
```
В итоге, `1 < 1` выполняется и дает результат `False`
---
### ▶ Как не надо использовать оператор `is`
<!-- Example ID: 230fa2ac-ab36-4ad1-b675-5f5a1c1a6217 --->
Ниже приведен очень известный пример, представленный во всем Интернете.
1\.
```py
>>> a = 256
>>> b = 256
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a is b
False
```
2\.
```py
>>> a = []
>>> b = []
>>> a is b
False
>>> a = tuple()
>>> b = tuple()
>>> a is b
True
```
3\.
**Результат**
```py
>>> a, b = 257, 257
>>> a is b
True
```
**Вывод (Python 3.7.x specifically)**
```py
>>> a, b = 257, 257
>>> a is b
False
```
#### 💡 Объяснение:
**Разница между `is` и `==`**.
* Оператор `is` проверяет, ссылаются ли оба операнда на один и тот же объект (т.е. проверяет, совпадают ли идентификаторы операндов или нет).
* Оператор `==` сравнивает значения обоих операндов и проверяет, одинаковы ли они.
* Таким образом, оператор `is` предназначен для равенства ссылок, а `==` - для равенства значений. Пример, чтобы прояснить ситуацию,
```py
>>> class A: pass
>>> A() is A() # These are two empty objects at two different memory locations.
False
```
**`256` - существующий объект, а `257` - нет**.
При запуске python будут выделены числа от `-5` до `256`. Эти числа используются часто, поэтому имеет смысл просто иметь их наготове.
Цитирую по https://docs.python.org/3/c-api/long.html
> Текущая реализация хранит массив целочисленных объектов для всех целых чисел от -5 до 256, когда вы создаете int в этом диапазоне, вы просто получаете обратно ссылку на существующий объект. Поэтому должно быть возможно изменить значение 1. Я подозреваю, что поведение Python в этом случае не определено. :-)
```py
>>> 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` ссылаются на один и тот же объект при инициализации одним и тем же значением в одной и той же строке*.
**Вывод**
```py
>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
140640774013392
>>> id(b)
140640774013488
```
* Когда a и b устанавливаются в `257` в одной строке, интерпретатор Python создает новый объект, а затем одновременно ссылается на вторую переменную. Если вы делаете это в отдельных строках, он не "знает", что объект `257` уже существует.
* Это оптимизация компилятора и относится именно к интерактивной среде. Когда вы вводите две строки в живом интерпретаторе, они компилируются отдельно, поэтому оптимизируются отдельно. Если бы вы попробовали этот пример в файле `.py', вы бы не увидели такого же поведения, потому что файл компилируется весь сразу. Эта оптимизация не ограничивается целыми числами, она работает и для других неизменяемых типов данных, таких как строки (проверьте пример "Строки - это сложно") и плавающие числа,
```py
>>> a, b = 257.0, 257.0
>>> a is b
True
```
* Почему это не сработало в Python 3.7? Абстрактная причина в том, что такие оптимизации компилятора зависят от реализации (т.е. могут меняться в зависимости от версии, ОС и т.д.). Я все еще выясняю, какое именно изменение реализации вызвало проблему, вы можете проверить этот [issue](https://github.com/satwikkansal/wtfpython/issues/100) для получения обновлений.
---
### ▶ Hash brownies
<!-- Example ID: eb17db53-49fd-4b61-85d6-345c5ca213ff --->
1\.
```py
some_dict = {}
some_dict[5.5] = "JavaScript"
some_dict[5.0] = "Ruby"
some_dict[5] = "Python"
```
**Вывод:**
```py
>>> 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 повсюду?
#### 💡 Объяснение.
* Уникальность ключей в словаре Python определяется *эквивалентностью*, а не тождеством. Поэтому, даже если `5`, `5.0` и `5 + 0j` являются различными объектами разных типов, поскольку они равны, они не могут находиться в одном и том же `дикте` (или `наборе`). Как только вы вставите любой из них, попытка поиска по любому другому, но эквивалентному ключу будет успешной с исходным сопоставленным значением (а не завершится ошибкой `KeyError`):
```py
>>> 5 == 5.0 == 5 + 0j
True
>>> 5 is not 5.0 is not 5 + 0j
True
>>> some_dict = {}
>>> some_dict[5.0] = "Ruby"
>>> 5.0 in some_dict
True
>>> (5 in some_dict) and (5 + 0j in some_dict)
True
```
* Это применимо и при установке элемента. Поэтому, когда вы делаете `some_dict[5] = "Python"`, Python находит существующий элемент с эквивалентным ключом `5.0 -> "Ruby"`, перезаписывает его значение на место, а исходный ключ оставляет в покое.
```py
>>> some_dict
{5.0: 'Ruby'}
>>> some_dict[5] = "Python"
>>> some_dict
{5.0: 'Python'}
```
* Итак, как мы можем обновить ключ до `5` (вместо `5.0`)? На самом деле мы не можем сделать это обновление на месте, но что мы можем сделать, так это сначала удалить ключ (`del some_dict[5.0]`), а затем установить его (`some_dict[5]`), чтобы получить целое число `5` в качестве ключа вместо плавающего `5.0`, хотя это нужно в редких случаях.
* Как Python нашел `5` в словаре, содержащем `5.0`? Python делает это за постоянное время без необходимости сканирования каждого элемента, используя хэш-функции. Когда Python ищет ключ `foo` в словаре, он сначала вычисляет `hash(foo)` (что выполняется в постоянном времени). Поскольку в Python требуется, чтобы объекты, которые сравниваются одинаково, имели одинаковое хэш-значение ([docs](https://docs.python.org/3/reference/datamodel.html#object.__hash__) здесь), `5`, `5.0` и `5 + 0j` имеют одинаковое хэш-значение.
```py
>>> 5 == 5.0 == 5 + 0j
True
>>> hash(5) == hash(5.0) == hash(5 + 0j)
True
```
**Примечание:** Обратное не обязательно верно: Объекты с одинаковыми хэш-значениями сами могут быть неравными. (Это вызывает так называемую [хэш-коллизию](https://en.wikipedia.org/wiki/Collision_(computer_science)) и ухудшает производительность постоянного времени, которую обычно обеспечивает хэширование).
---
### ▶ В глубине души мы все одинаковы.
<!-- Example ID: 8f99a35f-1736-43e2-920d-3b78ec35da9b --->
```py
class WTF:
pass
```
**Вывод:**
```py
>>> 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
```
#### 💡 Объяснение:
* При вызове `id` Python создал объект класса `WTF` и передал его функции `id`. Функция `id` забирает свой `id` (местоположение в памяти) и выбрасывает объект. Объект уничтожается.
* Когда мы делаем это дважды подряд, Python выделяет ту же самую область памяти и для второго объекта. Поскольку (в CPython) `id` использует участок памяти в качестве идентификатора объекта, идентификатор двух объектов одинаков.
* Таким образом, id объекта уникален только в течение жизни объекта. После уничтожения объекта или до его создания, что-то другое может иметь такой же id.
* Но почему оператор `is` имеет значение `False`? Давайте посмотрим с помощью этого фрагмента.
```py
class WTF(object):
def __init__(self): print("I")
def __del__(self): print("D")
```
**Вывод:**
```py
>>> WTF() is WTF()
I
I
D
D
False
>>> id(WTF()) == id(WTF())
I
D
I
D
True
```
Как вы можете заметить, порядок, в котором уничтожаются объекты, имеет значение.
---
### ▶ Нарушение в пределах порядка *
<!-- Example ID: 91bff1f8-541d-455a-9de4-6cd8ff00ea66 --->
```py
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
```
**Вывод**
```py
>>> 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
```
Что здесь происходит?
#### 💡 Объяснение:
- Причина, по которой не выполняется транзитивное равенство между `dictionary`, `ordered_dict` и `another_ordered_dict`, заключается в том, как реализован метод `__eq__` в классе `OrderedDict`. Из [docs](https://docs.python.org/3/library/collections.html#ordereddict-objects)
> Тесты равенства между объектами OrderedDict чувствительны к порядку и реализуются как `list(od1.items())==list(od2.items())`. Тесты на равенство между объектами `OrderedDict` и другими объектами Mapping нечувствительны к порядку, как обычные словари.
- Причина такого поведения равенства в том, что оно позволяет напрямую подставлять объекты `OrderedDict` везде, где используется обычный словарь.
- Итак, почему изменение порядка влияет на длину генерируемого объекта `set`? Ответ заключается только в отсутствии интранзитивного равенства. Поскольку множества являются "неупорядоченными" коллекциями уникальных элементов, порядок вставки элементов не должен иметь значения. Но в данном случае он имеет значение. Давайте немного разберемся в этом,
```py
>>> some_set = set()
>>> some_set.add(dictionary) # these are the mapping objects from the snippets above
>>> ordered_dict in some_set
True
>>> some_set.add(ordered_dict)
>>> len(some_set)
1
>>> another_ordered_dict in some_set
True
>>> some_set.add(another_ordered_dict)
>>> len(some_set)
1
>>> another_set = set()
>>> another_set.add(ordered_dict)
>>> another_ordered_dict in another_set
False
>>> another_set.add(another_ordered_dict)
>>> len(another_set)
2
>>> dictionary in another_set
True
>>> another_set.add(another_ordered_dict)
>>> len(another_set)
2
```
Таким образом, несоответствие связано с тем, что `another_ordered_dict в another_set` является `False`, потому что `ordered_dict` уже присутствовал в `another_set` и, как было замечено ранее, `ordered_dict == another_ordered_dict` является `False`.
---
### ▶ Keep trying... *
<!-- Example ID: b4349443-e89f-4d25-a109-82616be9d41a --->
```py
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)
```
**Результат:**
```py
>>> 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
```
#### 💡 Объяснение:
- Когда оператор `return`, `break` или `continue` выполняется в наборе `try` оператора "try...finally", на выходе также выполняется пункт `finally`.
- Возвращаемое значение функции определяется последним выполненным оператором `return`. Поскольку предложение `finally` выполняется всегда, оператор `return`, выполненный в предложении `finally`, всегда будет последним.
- Оговорка заключается в том, что если в предложении finally выполняется оператор `return` или `break`, то временно сохраненное исключение отбрасывается.
---
### ▶ Для чего?
<!-- Example ID: 64a9dccf-5083-4bc9-98aa-8aeecde4f210 --->
```py
some_string = "wtf"
some_dict = {}
for i, some_dict[i] in enumerate(some_string):
i = 10
```
**Вывод:**
```py
>>> some_dict # An indexed dict appears.
{0: 'w', 1: 't', 2: 'f'}
```
#### 💡 Объяснение:
* Оператор `for` определяется в [грамматике Python](https://docs.python.org/3/reference/grammar.html) как:
```
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
```
Где `exprlist` - цель присваивания. Это означает, что эквивалент `{exprlist} = {next_value}` **выполняется для каждого элемента** в итерабле.
Интересный пример, иллюстрирующий это:
```py
for i in range(4):
print(i)
i = 10
```
**Результат:**
```
0
1
2
3
```
Ожидали ли вы, что цикл будет запущен только один раз?
**💡 Объяснение:**.
- Оператор присваивания `i = 10` никогда не влияет на итерации цикла из-за того, как циклы for работают в Python. Перед началом каждой итерации следующий элемент, предоставляемый итератором (в данном случае `range(4)`), распаковывается и присваивается переменной целевого списка (в данном случае `i`).
* Функция `enumerate(some_string)` на каждой итерации выдает новое значение `i` (счетчик, идущий вверх) и символ из `some_string`. Затем она устанавливает (только что присвоенный) ключ `i` словаря `some_dict` на этот символ. Развертывание цикла можно упростить следующим образом:
```py
>>> i, some_dict[i] = (0, 'w')
>>> i, some_dict[i] = (1, 't')
>>> i, some_dict[i] = (2, 'f')
>>> some_dict
```
---
### ▶ Несоответствие времени оценки
<!-- Example ID: 6aa11a4b-4cf1-467a-b43a-810731517e98 --->
1\.
```py
array = [1, 8, 15]
# A typical generator expression
gen = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]
```
**Вывод:**
```py
>>> print(list(gen)) # Where did the other values go?
[8]
```
2\.
```py
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]
```
**Вывод:**
```py
>>> print(list(gen_1))
[1, 2, 3, 4]
>>> print(list(gen_2))
[1, 2, 3, 4, 5]
```
3\.
```py
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]
```
**Вывод:**
```py
>>> print(list(gen))
[401, 501, 601, 402, 502, 602, 403, 503, 603]
```
#### 💡 Пояснение
- В выражении [generator](https://wiki.python.org/moin/Generators) условие `in` оценивается во время объявления, но условное условие оценивается во время выполнения.
- Поэтому перед выполнением `array` переназначается на список `[2, 8, 22]`, а поскольку из `1`, `8` и `15` только счетчик `8` больше `0`, генератор выдает только `8`.
- Различия в выводе `g1` и `g2` во второй части связаны с тем, как переменным `array_1` и `array_2` присваиваются новые значения.
- В первом случае `array_1` привязывается к новому объекту `[1,2,3,4,5]`, а поскольку предложение `in` оценивается во время объявления, оно по-прежнему ссылается на старый объект `[1,2,3,4]` (который не уничтожается).
- Во втором случае присвоение среза `array_2` обновляет тот же старый объект `[1,2,3,4]` до `[1,2,3,4,5]`. Следовательно, и `g2`, и `array_2` по-прежнему имеют ссылку на один и тот же объект (который теперь обновлен до `[1,2,3,4,5]`).
- Хорошо, следуя логике, рассмотренной до сих пор, не должно ли значение `list(gen)` в третьем фрагменте быть `[11, 21, 31, 12, 22, 32, 13, 23, 33]`? (потому что `array_3` и `array_4` будут вести себя так же, как `array_1`). Причина, по которой (только) значения `array_4` обновляются, объясняется в [PEP-289](https://www.python.org/dev/peps/pep-0289/#the-details)
> Только крайнее for-выражение оценивается немедленно, остальные выражения откладываются до запуска генератора.
---
### ▶ `is not ...` is not `is (not ...)`
<!-- Example ID: b26fb1ed-0c7d-4b9c-8c6d-94a58a055c0d --->
```py
>>> 'something' is not None
True
>>> 'something' is (not None)
False
```
#### 💡 Пояснение
- `is not` является единым бинарным оператором, и его поведение отличается от раздельного использования `is` и `not`.
- `is not` имеет значение `False`, если переменные по обе стороны оператора указывают на один и тот же объект, и `True` в противном случае.
- В примере `(not None)` оценивается в `True`, поскольку значение `None` является `False` в булевом контексте, поэтому выражение становится `'something' is True`.
---
### ▶ Крестики-нолики, где X побеждает с первой попытки!
<!-- Example ID: 69329249-bdcb-424f-bd09-cca2e6705a7a --->
```py
# Let's initialize a row
row = [""] * 3 #row i['', '', '']
# Let's make a board
board = [row] * 3
```
**Результат:**
```py
>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]
```
Мы же не назначили три ``Х``?
#### 💡 Объяснение:
Когда мы инициализируем переменную `row`, эта визуализация объясняет, что происходит в памяти

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

Мы можем избежать этого сценария, не используя переменную `row` для генерации `board`. (Вопрос задан в [этом](https://github.com/satwikkansal/wtfpython/issues/68) выпуске).
```py
>>> board = [['']*3 for _ in range(3)]
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['', '', ''], ['', '', '']]
```
---
### ▶ Переменная Шредингера *
<!-- Example ID: 4dc42f77-94cb-4eb5-a120-8203d3ed7604 --->
```py
funcs = []
results = []
for x in range(7):
def some_func():
return x
funcs.append(some_func)