Repository navigation
Expand file tree
/
Copy pathREADME.md
More file actions
3817 lines (2990 loc) · 124 KB
/
Copy pathREADME.md
File metadata and controls
3817 lines (2990 loc) · 124 KB
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">Explorando y entendiendo Python a través de ejemplos sorprendentes.</p>
Traducciones: [Inglés English](https://github.com/satwikkansal/wtfpython) | [Chino 中文](https://github.com/leisurelicht/wtfpython-cn) | [Vietnamita Tiếng Việt](https://github.com/vuduclyunitn/wtfptyhon-vi)
Otras versiones: [Interactivo](https://colab.research.google.com/github/satwikkansal/wtfpython/blob/master/irrelevant/wtf.ipynb) | [CLI](https://pypi.python.org/pypi/wtfpython)
Python, al ser un lenguaje de programación basado en el intérprete y al estar diseñado en alto nivel, nos permite utilizar muchas funcionalidades para nuestra comodidad. Pero, a veces, los resultados de un código de Python pueden parecernos confusos al principio.
Este es un proyecto divertido que tiene como objetivo explicar exactamente qué está ocurriendo bajo el código de funciones confusas y no muy conocidas que vemos en Python.
A pesar de que algunos de los ejemplos que leas abajo no sean "WTF!", te enseñarán algunas partes interesantes del funcionamiento de Python que probablemente no conozcas. Creo que es una buena manera de aprender el funcionamiento interno de un lenguage de programación, ¡y creo que tu también pensarás lo mismo!
Si eres un programador de Python experto, puedes tomar este proyecto como una prueba e intentar adivinar el resultado al primer intento. Puede que ya hallas experimentado algunos de estos códigos antes, ¡y puede que revivan algunos de tus recuerdos! :sweat_smile:
PD: Si lees este proyecto con frecuencia, puedes enterarte de nuevas modificaciones [aquí](https://github.com/satwikkansal/wtfpython/releases/) (los ejemplos marcados con un asterisco son los añadidos en la última versión mayor).
Ahora si, vamos a comenzar...
# Contenido
<!-- Generated using "markdown-toc -i README.md --maxdepth 3"-->
<!-- toc -->
- [Estructura de los ejemplos](#estructura-de-los-ejemplos)
+ [▶ Título](#-título)
- [Uso](#Uso)
- [👀 Ejemplos](#-ejemplos)
* [Sección: ¡ejercita tu cerebro!](#sección-ejercita-tu-cerebro)
+ [▶ ¡Primero lo primero! *](#-primero-lo-primero-)
+ [▶ Los strings pueden ser confusos de vez en cuando](#-los-strings-pueden-ser-confusos-de-vez-en-cuando)
+ [▶ Ten cuidado con las operaciones en cadena](#-ten-cuidado-con-las-operaciones-en-cadena)
+ [▶ Como no utilizar el operador `is`](#-como-no-utilizar-el-operador-is)
+ [▶ Dulces hash](#-dulces-hash)
+ [▶ En el fondo, todos somos iguales](#-en-el-fondo-todos-somos-iguales)
+ [▶ Desorden en el orden *](#-desorden-en-el-orden-)
+ [▶ Sigue intentando... *](#-sigue-intentando-)
+ [▶ ¿Para qué?](#-para-qué)
+ [▶ Discrepancia de evaluación de tiempo](#-discrepancia-de-evaluación-de-tiempo)
+ [▶ `is not ...` no es `is (not ...)`](#-is-not--no-es-is-not-)
+ [▶ ¡Un tres en raya en donde la X gana la primera jugada!](#-un-tres-en-raya-en-donde-la-x-gana-la-primera-jugada)
+ [▶ La variable de Schrödinger *](#-la-variable-de-schrödinger-)
+ [▶ El problema del huevo de gallina *](#-el-problema-del-huevo-de-gallina-)
+ [▶ Relaciones entre subclases](#-relaciones-entre-subclases)
+ [▶ Equidad e identidad de métodos](#-equidad-e-identidad-de-métodos)
+ [▶ Siempre verdadero *](#-siempre-verdadero-)
+ [▶ La coma extraña](#-la-coma-extraña)
+ [▶ Strings y barras invertidas](#-strings-y-barras-invertidas)
+ [▶ ¡Ahora no!](#-ahora-no)
+ [▶ Strings con medias tres comillas](#-strings-con-medias-tres-comillas)
+ [▶ ¿Cuál es el problema con los booleanos?](#-cuál-es-el-problema-con-los-booleanos)
+ [▶ Atributos de clase y de instancia](#-atributos-de-clase-y-de-instancia)
+ [▶ Devolviendo `None`](#-devolviendo-none)
+ [▶ ¡Usando `yield` desde return! *](#-usando-yield-desde-return-)
+ [▶ Reflexividad NAN *](#-reflexividad-nan-)
+ [▶ ¡Mutando lo inmutable!](#-mutando-lo-inmutable)
+ [▶ La variable de alcance exterior que desaparece](#-la-variable-de-alcance-exterior-que-desaparece)
+ [▶ La misteriosa conversión de la llave](#-la-misteriosa-conversión-de-la-llave)
+ [▶ Veamos si puedes adivinar esto...](#-veamos-si-puedes-adivinar-esto)
* [Sección: pendientes resbaladizas](#sección-pendientes-resbaladizas)
+ [▶ Modificando un diccionario a la vez que iteramos por él](#-modificando-un-diccionario-a-la-vez-que-iteramos-por-él)
+ [▶ El rebelde operador `del`](#-el-rebelde-operador-del)
+ [▶ La variable fuera de alcance](#-la-variable-fuera-de-alcance)
+ [▶ Borrar un elemento de una lista al iterar sobre ella](#-borrar-un-elemento-de-una-lista-al-iterar-sobre-ella)
+ [▶ Pérdidas en los iteradores *](#-pérdidas-en-los-iteradores-)
+ [▶ ¡Fuga de variables en bucles!](#-fuga-de-variables-en-bucles)
+ [▶ ¡Ten cuidado con los argumentos mutables predeterminados!](#-ten-cuidado-con-los-argumentos-mutables-predeterminados)
+ [▶ Detectando las excepciones](#-detectando-las-excepciones)
+ [▶ ¡Mismos operandos, cuestiones diferentes!](#-mismos-operandos-cuestiones-diferentes)
+ [▶ Resolución de nombres ignorando el alcance de la clase](#-resolución-de-nombres-ignorando-el-alcance-de-la-clase)
+ [▶ Agujas en un pajar *](#-agujas-en-un-pajar-)
+ [▶ Separando valores *](#-separando-valores-)
+ [▶ Importes "wild" *](#-importes-wild-)
+ [▶ ¿Todo ordenado? *](#-todo-ordenado-)
+ [▶ ¿No existe la medianoche?](#-no-existe-la-medianoche)
* [Sección: ¡tesoros ocultos!](#sección-tesoros-ocultos)
+ [▶ Python, ¿puedes hacerme volar?](#-python-puedes-hacerme-volar)
+ [▶ `goto`, pero, ¿por qué?](#-goto-pero-por-qué)
+ [▶ ¡Prepárate!](#-prepárate)
+ [▶ Conozcamos a Lenguage Amistoso, tío de por vida](#-conozcamos-a-lenguaje-amistoso-tío-de-por-vida)
+ [▶ Incluso Python sabe que el amor es complicado](#-incluso-python-sabe-que-el-amor-es-complicado)
+ [▶ ¡Si, existe!](#-si-existe)
+ [▶ Elipsis *](#-elipsis-)
+ [▶ "Inpinity"](#-inpinity)
+ [▶ Vamos a mangonear](#-vamos-a-mangonear)
* [Sección: ¡las apariencias engañan!](#sección-las-apariencias-engañan)
+ [▶ ¿Saltando líneas?](#-saltando-líneas)
+ [▶ Teletransportación](#-teletransportación)
+ [▶ Hm... algo es sospechoso...](#-hm-algo-es-sospechoso)
* [Sección: varios](#sección-varios)
+ [▶`+=` es más rápido](#--es-más-rápido)
+ [▶ ¡Hagamos un string super largo!](#-hagamos-un-string-super-largo)
+ [▶ Ralentizando revisiones de `dict` *](#-ralentizando-revisiones-de-dict-)
+ [▶ Instancias de `dict` *](#-instancias-de-dict-)
+ [▶ Cambios menores *](#-cambios-menores-)
- [Contribuir](#contribuir)
- [Reconocimientos](#reconocimientos)
- [🎓 Licencia](#-licencia)
* [¡Sorprende a tus amigos!](#sorprende-a-tus-amigos)
* [¿Necesitas una versión en PDF?](#necesitas-una-versión-en-pdf)
<!-- tocstop -->
# Estructura de los ejemplos
Todos los ejemplos están estructurados de la siguiente manera:
> ### ▶ Título
>
> ```py
> # Comienzo del código.
> # Preparándose para la magia...
> ```
>
> **Output (version(es) de Python):**
>
> ```py
> >>> triggering_statement
> Output inesperado
> ```
> (Opcional): Una línea describiendo el output inesperado.
>
>
> #### 💡 Explicación:
>
> * Explicación corta de qué está ocurriendo y por qué está ocurriendo.
> ```py
> # Comienzo del código
> # Más ejemplos para entender mejor (si es necesario)
> ```
> **Output (version(es) de Python):**
>
> ```py
> >>> trigger # algún ejemplo que haga que sea fácil ver la magia
> # output justificado
> ```
**Nota:** Todos los ejemplos están probados en el intérprete interactivo en Python 3.5.2 y deberían funcionar en todas las otras versiones de Python a menos que se haya indicado explícitamente lo contrario en el output.
# Uso
Una buena manera de obtener el mayor conocimiento de estos ejemplos, en mi opinión, es leerlos cronológicamente, y para cada ejemplo hacer lo siguiente:
- Leer el código inicial del comienzo del código cuidadosamente. Si eres un programador de Python experimentado seguramente anticiparás correctamente lo que pasará luego.
- Lee el output y,
+ Revisa si el output es igual al que te imaginabas.
+ Asegúrate de que conoces la razón exacta de que el output sea ese.
- Si la respuesta es "no" (lo cual está bien), toma un suspiro y lee la explicación de nuevo. Si aun no entiendes, ¡no te preocupes! puedes crear un Issue [aquí](https://github.com/JoseDeFreitas/wtfpython-es/issues/new).
- Si la respuesta es "si", puedes darte una palmadita en la espalda y leer el siguiente ejemplo.
PD: También puedes leer WTFPython en la línea de comandos usando el [paquete pypi](https://pypi.python.org/pypi/wtfpython),
```sh
$ pip install wtfpython -U
$ wtfpython
```
---
---
# 👀 Ejemplos
## Sección: ¡ejercita tu cerebro!
### ▶ ¡Primero lo primero! *
<!-- Example ID: d3d73936-3cf1-4632-b5ab-817981338863 -->
<!-- read-only -->
Por alguna razón, el operador "Walrus" de Python 3.8+ (`:=`) se ha vuelto popular. Vamos a verlo,
1\.
```py
# versión de Python: 3.8+
>>> a = "wtf_walrus"
>>> a
'wtf_walrus'
>>> a := "wtf_walrus"
File "<stdin>", line 1
a := "wtf_walrus"
^
SyntaxError: invalid syntax
>>> (a := "wtf_walrus") # Esto sí funciona
'wtf_walrus'
>>> a
'wtf_walrus'
```
2 \.
```py
# versión de Python: 3.8+
>>> a = 6, 9
>>> a
(6, 9)
>>> (a := 6, 9)
(6, 9)
>>> a
6
>>> a, b = 6, 9 # Desempaquetado común
>>> a, b
(6, 9)
>>> (a, b = 16, 19) # Oops
File "<stdin>", line 1
(a, b = 6, 9)
^
SyntaxError: invalid syntax
>>> (a, b := 16, 19) # Esto devuelve un tuple extraño de 3 elementos
(6, 16, 19)
>>> a # ¿permanece "a" sin cambios todavía?
6
>>> b
16
```
#### 💡 Explicación:
**Resumen rápido del operador Walrus**
El operador Walrus (`:=`) fue introducido en Python 3.8, y puede ser útil cuando quieras asignar valores a variables dentro de una expresión.
```py
def some_func():
# Asume una computación grande aquí
# time.sleep(1000)
return 5
# Así que, en vez de,
if some_func():
print(some_func()) # Lo cual es una mala práctica porque la operación está ocurriendo dos veces
# o
a = some_func()
if a:
print(a)
# Ahora puedes escribir con seguridad
if a := some_func():
print(a)
```
**Output (> 3.8):**
```py
5
5
5
```
Esto nos salvó una línea de código e implícitamente previno la invocación de `some_func` dos veces.
- Una expresión de asignación sin parénteris (uso del operador Walrus) está restringida al nivel superior, por eso se devuelve el error `SyntaxError` en la declaración `a := "wtf_walrus"` en el primer código. Al poner paréntesis a la declaración hacemos que funcione correctamente y se asigne el valor a la variable `a`.
- Como siempre, al ponerle paréntesis a una expresión que contiene el operador `=` no está permitido. Es por eso que se devuelve el error en `(a, b = 6, 9)`.
- La sintaxis del operador Walrus está en la forma `NOMBRE := expresión`, donde `NOMBRE` es un identificador válido y `expresión` es una expresión válida. Por eso, el empaquetamiento y desempaquetamiento no está permitido, lo que significa que,
- `(a := 6, 9)` es equivalente a `((a := 6), 9)` y en consecuencia a `(a, 9)` (donde el valor de `a` es 6)
```py
>>> (a := 6, 9) == ((a := 6), 9)
True
>>> x = (a := 696, 9)
>>> x
(696, 9)
>>> x[0] is a # Ambos hacen referencia al mismo espacio en memoria
True
```
- Similarmente, `(a, b := 16, 19)` es equivalente a `(a, (b := 16), 19)` el cual no es más que un tuple de 3 elementos.
---
### ▶ Los strings pueden ser confusos de vez en cuando
<!-- Example ID: 30f1d3fc-e267-4b30-84ef-4d9e7091ac1a --->
1\.
```py
>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # Ambas IDs son las mismas
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 # Todas las versiones excepto 3.7.x
True
>>> a = "wtf!"; b = "wtf!"
>>> a is b # Esto devolverá True o False dependiendo de en dónde lo estés invocando (consola Python / iPython / en un archivo)
False
```
```py
# Ahora en un archivo llamado some_file.py
a = "wtf!"
b = "wtf!"
print(a is b)
# ¡Devuelve True cuando el módulo es invocado!
```
4\.
**Output (< Python3.7)**
```py
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
```
Tiene sentido, ¿cierto?
#### 💡 Explicación:
+ El comportamiento en la primera y segunda porción de código es debido a una optimización de CPython (llamada "string interning (internado)") que intenta usar objetos inmutables en algunos casos en vez de crear un nuevo objeto cada vez.
+ Luego de ser "internado" muchas variables pueden hacer referencia al mismo objeto string en memoria (salvando así memoria).
+ En las porciones de código de arriba, los strings son internados implícitamente. La decisión de cuando internar un string implícitamente depende de la implementación. Hay algunas reglas que se pueden usar para averiguar si un string será internado o no:
* Todos los strings con longitud 0 y 1 son internados.
* Los strings son internados en el tiempo de compilación (`wtf` será internado pero `''.join(['w', 't', 'f'])` no será internado).
* Los strings que no están compuestos de letras ASCII, dígitos o pisos bajos no son internados. Esto explica porqué `'wtf!'` no fue internado debido al `!`. La implementación de esta regla en CPython puede ser encontrada [aquí](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19).

+ Cuando `a` y `b` son asignadas a `"wtf!` en la misma línea, el intérprete de Python crea un nuevo objeto, para luego hacer referencia a la segunda variable al mismo tiempo. Si lo haces en líneas separadas, Python "no sabe" que ya existe `"wft!"` como un objeto (porque `"wtf!"` no está implícitamente internado debido a lo mencionado arriba). Es una optimización en el tiempo de compilación. Esta optimización no aplica a las versiones 3.7.x de CPython (clickea este [Issue])(https://github.com/satwikkansal/wtfpython/issues/100) para ver una discusión sobre esto).
+ Una unidad de compilación en un ambiente interactivo (como IPython) consiste en una sola declaración, donde consiste en un módulo entero en caso de módulos. `a, b = "wtf!", "wtf!"` es una sola declaración, mientras que `a = "wtf!"; b = "wtf!"` son dos declaraciones en una misma línea. Esto explica por qué las identidades son diferentes en `a = "wtf!"; b = "wtf!"`, y también explica por qué son las mismas cuando son invocadas en `some_file.py`.
+ El cambio abrupto en el output de la cuarta porción de código es debido a la [optimización peephole](https://en.wikipedia.org/wiki/Peephole_optimization), técnica conocida como "Constant folding". Esto significa que la expresión `'a'*20` es reemplazada por `'aaaaaaaaaaaaaaaaaaaa'` durante la compilación para salvar algunos ciclos de reloj durante el tiempo de ejecución. "Constant folding" solo ocurre en strings que tienen una longitud menor a 21. (¿Por qué? Imagina el tamaño del archivo `.pyc` generado como resultado de la expresión `'a'*10**10`). [Aquí](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288) está la implementación para esto mismo.
+ Nota: En Python 3.7, "Constant folding" fue movido del optimizador peephole al optimizador AST con algún cambio en la lógica, haciendo que la cuarta porción de código no funcione en Python 3.7. Puedes leer más acerca de este cambio [aquí](https://bugs.python.org/issue11549).
---
### ▶ Ten cuidado con las operaciones en cadena
<!-- Example ID: 07974979-9c86-4720-80bd-467aa19470d9 --->
```py
>>> (False == False) in [False] # tiene sentido
False
>>> False == (False in [False]) # tiene sentido
False
>>> False == False in [False] # ¿ahora qué?
True
>>> True is False == False
False
>>> False is False is False
True
>>> 1 > 0 < 1
True
>>> (1 > 0) < 1
False
>>> 1 > (0 < 1)
False
```
#### 💡 Explicación:
Según https://docs.python.org/3/reference/expressions.html#membership-test-operations
> Formalmente, si a, b, c, ..., y, z son expresiones y op1, op2, ..., opN son operadores de comparación, entonces op1 b op2 c ... y opN z es equivalente a a op1 b y b op2 c y ... y opN z, excepto que cada expresión es evaluada al menos una vez.
Aunque puedas considerar tonto este comportamiento en los ejemplso de arriba, es fantástico cuando se aplica a cosas como `a == b == c` y `0 <= x <= 100`.
* `False is False is False` es equivalente a `(False is False) and (False is False)`
* `True is False == False` es equivalente a `True is False and False == False` y debido a que la primera parte de la declaración (`True is False`) devuelve `False`, la expresión completa devuelve `False`.
* `1 > 0 < 1` es equivalente a `1 > 0 and 0 < 1`, la cual devuelve `True`.
* La expresión `(1 > 0) < 1` es equivalente a `True < 1` y
```py
>>> int(True)
1
>>> True + 1 # no es relevante para este ejemplo
2
```
So, `1 < 1` devuelve `False`
---
### ▶ Como no utilizar el operador `is`
<!-- Example ID: 230fa2ac-ab36-4ad1-b675-5f5a1c1a6217 --->
El código siguiente es un ejemplo muy famoso en el internet.
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\.
**Output**
```py
>>> a, b = 257, 257
>>> a is b
True
```
**Output (Python 3.7.x específicamente)**
```py
>>> a, b = 257, 257
>> a is b
False
```
#### 💡 Explicación:
**La diferencia entre `is` y `==`**
* El operador `is` revisa si ambos operandos hacen referencia al mismo objeto (revisa si la identidad de los operandos combinan o no).
* El operador `==` compara los valores de ambos operandos y prueba si son iguales.
* Entonces, `is` es para la equidad de referencia y `==` para la equidad de valor. A continuación un ejemplo para hacer más clara la explicación,
```py
>>> class A: pass
>>> A() is A() # Estos son dos objetos diferentes en dos espacios de memoria diferentes.
False
```
**`256` es un objeto existente, pero `257` no lo es**
Cuando abres Python, los números desde el `-5` hasta el `256` son alocados. Estos números son usados un montón; es por esto que Python los prepara al comienzo.
Nota de https://docs.python.org/3/c-api/long.html
> La implementación actual mantiene un array de objetos de enteros para todos los enteros entre -5 y 256. Cuando creas un entero en ese rango, simplemente obtienes la referencia a ese objeto existente. Así que, debería ser posible cambiar el valor de 1. Creo que el comportamiento de Python en este caso es indefinido. :-)
```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
```
Aquí el intérprete no es tan inteligente al ejecutar `y = 257` para reconocer que ya hemos creado un entero con el valor `257` y crea un objeto nuevo y lo aloja en la memoria.
Una optimización similar ocurre a otros objetos **inmutables**, como tuples vacíos. Ya que las listas son mutables, `[] is []` devolverá `False` y `() is ()` devolverá `True`. Esto explica la segunda porción del código.
Ahora, veamos la tercera,
**Tanto `a` como `b` hacen referencia al mismo objeto cuando son inicializadas con el mismo valor en la misma línea.**
**Output**
```py
>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
140640774013392
>>> id(b)
140640774013488
```
* Cuando se le asigna el valor `257` a `a` y `b` en la misma línea el intérprete de Python crea un nuevo objeto y hace referencia a la segunda variable al mismo tiempo. Si lo haces en líneas diferentes, el intérprete "no sabe" que ya existe `257` como un objeto.
* Es una optimización del compilador y específicamente aplica al ambiente interactivo. Cuando escriber dos líneas en un intérprete vivo, estas son compiladas por separado, lo que hace que sean optimizadas por separado. Si intentas escribir este ejemplo en un archivo `.py` verás que el comportamiento no es el mismo, ya las líneas en los archivos son compiladas de una sola vez. Esta optimización no se limita a enteros, también funciona para otros tipos de datos inmutables como strings (lee "Los strings son confusos") y tuples,
```py
>>> a, b = 257.0, 257.0
>>> a is b
True
```
* ¿Por qué no funciona en Python 3.7? La razón abstracta es que estas optimizaciones del compilador son específicas a la implementación (ej: varían según la versión, el sistema operativo, etc.). Todavía estoy investigando qué implementación causa este error. Puedes ver este [Issue](https://github.com/satwikkansal/wtfpython/issues/100) para más información.
---
### ▶ Dulces hash
<!-- 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"
```
**Output:**
```py
>>> some_dict[5.5]
"JavaScript"
>>> some_dict[5.0] # ¿"Python" destruyó la existencia de "Ruby"?
"Python"
>>> some_dict[5]
"Python"
>>> complex_five = 5 + 0j
>>> type(complex_five)
complex
>>> some_dict[complex_five]
"Python"
```
¿Por qué está "Python" por todos lados?
#### 💡 Explicación:
* Las llaves en los diccionarios de Python funcionan por *equivalencia*, no por identidad. Aunque `5`, `5.0` y `5 + 0j` son objetos diferentes de tipos diferentes, ya que son iguales, no pueden estar en el mismo `dict` (o `set`). Cuando insertes cualquiera de ellos, al intentar tomar una llave distinta pero equivalente tomará en realidad un valor mapeado (en vez de devolver un `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
```
* Esto también aplica al declarar un elemento. Cuando escribes `some_dict[5] = "Python"`, Python encuentra el elemento existente con una llave equivalente `5.0 -> "Ruby"`, los reemplaza en el lugar y deja la llave original sin tocar.
```py
>>> some_dict
{5.0: 'Ruby'}
>>> some_dict[5] = "Python"
>>> some_dict
{5.0: 'Python'}
```
* Entonces, ¿cómo podemos actualizar la llave a `5` (en vez de `5.0`)? No podemos hacer esto en el lugar, pero lo que podemos hacer es: primero, borrar la llave (`del some_dict[5.0]`); luego, actualizarla (`some_dict[5]`) para obtener el entero `5` como la llave en vez del decimal `5.0` (aun así, esto no será necesario en la mayoría de los casos).
* ¿Cómo Python encontró `5` en un diccionario que contiene `5.0`? Python hace esto constantemente sin la necesidad de escanear cada uno de los elementos usando funciones hash. Por ejemplo, cuando Python busca una llave llamada `foo` en un diccionario, primero ejecuta `hash(foo)` (constantemente). Ya que en Python para que los objetos que se comparan por igual necesitan tener el mismo valor hash ([documentación](https://docs.python.org/es/3/reference/datamodel.html#object.__hash__)), `5`, `5.0` y `5 + 0j` tienen el mismo valor hash.
```py
>>> 5 == 5.0 == 5 + 0j
True
>>> hash(5) == hash(5.0) == hash(5 + 0j)
True
```
**Nota:** lo contrario no es necesariamente cierto: los objetos con el mismo valor hash pueden ser desiguales. (Esto causa lo que es conocido como una [colisión hash](https://es.wikipedia.org/wiki/Colisi%C3%B3n_(hash))) y degrada el tiempo de rendimiento que el hash provee).
---
### ▶ En el fondo, todos somos iguales
<!-- Example ID: 8f99a35f-1736-43e2-920d-3b78ec35da9b --->
```py
class WTF:
pass
```
**Output:**
```py
>>> WTF() == WTF() # dos instancias diferentes no pueden ser iguales
False
>>> WTF() is WTF() # las identidades también son diferentes
False
>>> hash(WTF()) == hash(WTF()) # los hash también deberían ser diferentes
True
>>> id(WTF()) == id(WTF())
True
```
#### 💡 Explicación:
* Cuando `id` es llamada, Python crea un objeto de la clase `WTF` y la pasa a la función `id`. La función `id` toma su `id` (su espacio en memoria) y desecha el objeto (lo destruye).
* Cuando hacemos estos dos veces en una sucesión, Python asigna el mismo espacio en memoria al segundo objeto también. Ya que `id` usa el espacio de memoria como la identificación del objeto (en CPython), la identificación de ambos objetos es la misma.
* Es por esto que la identificación del objeto es única solo mientras el objeto existe. Cuando el objeto es destruido o antes de que sea creado, cualquier otro objeto puede tener la misma identificación.
* Pero, ¿por qué el operador `is` devolvió `False`? Veamos la respuesta en esta porción de código.
```py
class WTF(object):
def __init__(self): print("I")
def __del__(self): print("D")
```
**Output:**
```py
>>> WTF() is WTF()
I
I
D
D
False
>>> id(WTF()) == id(WTF())
I
D
I
D
True
```
Como puedes ver, el orden en el que los objetos son destruidos es la causa de la diferencia ocurrida.
---
### ▶ Desorden en el orden *
<!-- 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):
"""
Un diccionario que también implementa la magia de __hash__.
"""
__hash__ = lambda self: 0
class OrderedDictWithHash(OrderedDict):
"""
Un diccionario ordenado (OrderedDict) que también implementa la magia de __hash__.
"""
__hash__ = lambda self: 0
```
**Output**
```py
>>> dictionary == ordered_dict # Si a == b
True
>>> dictionary == another_ordered_dict # y b == c
True
>>> ordered_dict == another_ordered_dict # entonces, ¿por qué c no es igual a a (c == a)?
False
# Sabemos que un set consiste en elementos únicos.
# Intentemos crear un set de estos diccionarios a ver qué ocurre...
>>> len({dictionary, ordered_dict, another_ordered_dict})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
# Tiene sentido, ya que "dict" no tiene __hash__ implementado. Usemos
# nuestras clases anidadas.
>>> 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}) # cambiando el orden
2
```
¿Qué está ocurriendo aquí?
#### 💡 Explicación:
- La razón por la cual la equidad intransitiva no es soportada entre `dictionary`, `ordered_dict` y `another_ordered_dict` radica en la forma en que el método `__eq__` está implementado en la clase `OrderedDict`. Información de la [documentación](https://docs.python.org/es/3/library/collections.html#ordereddict-objects).
> Las pruebas de equidad entre los objetos OrderedDict dependen del orden y son implementados como `list(od1.items())==list(od2.items())`. Estas pruebas aplicadas a estos objetos (al igual que a otros objetos de mapeo) no dependen del orden (al igual que los diccionarios regulares).
- La razón de esta equidad en el comportamiento permite a los objetos `OrderedDict` a ser sustituidos directamente en cualquier lugar del código en donde un diccionario regular sea usado.
- Entonces, ¿por qué al cambiar el orden se afecta la longitud del objeto `set` generado? La respuesta es que solo está presenta la equidad intransitiva. Ya que los sets son colecciones "desordenadas" de elementos únicos, el orden en el que los elementos son insertados no debería importar. Pero, en este caso si importa. Veamos el porqué,
```py
>>> some_set = set()
>>> some_set.add(dictionary) # estos son los objetos de mapeo de las porciones de código de arriba
>>> 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
```
La inconsistencia se debe a que `another_ordered_dict in another_set` devuelve `False` porque `ordered_dict` ya estaba presente en `another_set` y, como vimos anteriormente, `ordered_dict == another_ordered_dict` es `False`.
---
### ▶ Sigue intentando... *
<!-- 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(): # ¡Lo tenemos!
try:
for i in range(3):
try:
1 / i
except ZeroDivisionError:
# Vamos a dejar esto aquí y manejarlo fuera del loop
raise ZeroDivisionError("A trivial divide by zero error")
finally:
print("Iteration", i)
break
except ZeroDivisionError as e:
print("Zero division error occurred", e)
```
**Output:**
```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
```
#### 💡 Explicación:
- Cuando una declaración `return`, `break` o `continue` es ejecutada en `try` dentro de una declaración "try...finally", la cláusula `finally` también está siendo ejecutada al terminar la operación.
- El valor que devuelve una función es determinado por la última declaración `return` ejecutada. Ya que la cláusula `finally` siempre se ejecuta, una declaración `return` ejecutada en una cláusula `finally` siempre será la última en ser ejecutada.
- El problema aquí es que si la cláusula `finally` ejecuta una declaración `return` o `break`, la excepción temporal guardada es descartada.
---
### ▶ ¿Para qué?
<!-- Example ID: 64a9dccf-5083-4bc9-98aa-8aeecde4f210 --->
```py
some_string = "wtf"
some_dict = {}
for i, some_dict[i] in enumerate(some_string):
i = 10
```
**Output:**
```py
>>> some_dict # Un diccionario indexado aparece.
{0: 'w', 1: 't', 2: 'f'}
```
#### 💡 Explicación:
* La declaración `for` está definida en la [gramática de Python](https://docs.python.org/es/3/reference/grammar.html) como:
```
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
```
Donde `exprlist` es la asignación. Esto significa que el equivalente de `{exprlist} = {next_value}` es **ejecutado por cada elemento** en el bucle.
Este es un ejemplo interesante que muestra lo anterior escrito:
```py
for i in range(4):
print(i)
i = 10
```
**Output:**
```
0
1
2
3
```
¿Esperabas que el bucle iterara solo una vez?
**💡 Explicación:**
- La declaración de asignación `i = 10` nunca afecta a las iteraciones del bucle debido a la forma en la que los bucles funcionan en Python. Antes de comenzar cada iteración, el siguiente elemento seleccionado por el bucle (`range(4)` en este caso) es desempaquetado y asignado a las variables (`i` en este caso).
* La función `enumerate(some_string)` devuelve un nuevo valor `i` (un contador que incrementa) y un caracter de `some_string` en cada iteración. Luego asigna la llave `i` del diccionario `some_dict` a ese caracter. El bucle puede ser simplificado como:
```py
>>> i, some_dict[i] = (0, 'w')
>>> i, some_dict[i] = (1, 't')
>>> i, some_dict[i] = (2, 'f')
>>> some_dict
```
---
### ▶ Discrepancia de evaluación de tiempo
<!-- Example ID: 6aa11a4b-4cf1-467a-b43a-810731517e98 --->
1\.
```py
array = [1, 8, 15]
# Una expresión generadora típica
gen = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]
```
**Output:**
```py
>>> print(list(gen)) # ¿A dónde fueron los otros valores?
[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]
```
**Output:**
```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]
```
**Output:**
```py
>>> print(list(gen))
[401, 501, 601, 402, 502, 602, 403, 503, 603]
```
#### 💡 Explicación:
- En un [generador](https://wiki.python.org/moin/Generators), la cláusula `in` es evaluada cuando es declarada, pero la cláusula condicional es evaluada cuando se ejecuta el archivo.
- Antes de la ejecución, `array` es reasignado a la lista `[2, 8, 22]` y debido a que entre los valores `1`, `8` y `15` solo la cuenta del `8` es mayor a `0`, el generador solo devuelve `8`.
- La diferencia en el output de `g1` y `g2` de la segunda parte se debe a la forma en la que las variables `array_1` y `array_2` son reasignadas.
- En el primer caso, `array_1` es agregado al nuevo objeto `[1,2,3,4,5]` y como la cláusula `in` es evaluada cuando es declarada todavía hace referencia al objeto antiguo `[1,2,3,4]` (el cual no es destuido).
- En el segundo caso, la asignación de `array_2` actualiza el mismo objeto antiguo `[1,2,3,4]` a `[1,2,3,4,5]`. Es por esto que ambos `g2` y `array_2` todavía hacen referencia al mismo objeto (el cual ha sido actualizado a `[1,2,3,4,5]`).
- Siguiendo la lógica que hemos estado discutiendo hasta ahora, te preguntarás, ¿no debería ser el valor de `list(g)` en la tercera porción de código ser `[11, 21, 31, 12, 22, 32, 13, 23, 33]`? (porque `array_3` y `array_4` se comportarán al igual que `array_1`). La razón por la cual solo se actualizaron los valores de `array_4` se explica en [PEP-289](https://www.python.org/dev/peps/pep-0289/#the-details)
> Solo la expresión `for` del exterior será evaluada inmediatamente. Las otras expresiones son aplazafas hasta que el generador sea ejecutado.
---
### ▶ `is not ...` no es `is (not ...)`
<!-- Example ID: b26fb1ed-0c7d-4b9c-8c6d-94a58a055c0d --->
```py
>>> 'something' is not None
True
>>> 'something' is (not None)
False
```
#### 💡 Explicación:
- `is not` es un operador binario singular y su comportamiento es diferente que al usar `is` y `not` separados.
- `is not` devuelve `False` si las variables de cualquiera de los lados hacen referencia al mismo objeto. Lo contrario devuelve `True`.
- En el ejemplo, `(not None)` devuelve `True` debido a que el valor `None` es `False` en un contexto boleano. Entonces, la expresión se vuelve `'something' is True`.
---
### ▶ ¡Un tres en raya en donde la X gana la primera jugada!
<!-- Example ID: 69329249-bdcb-424f-bd09-cca2e6705a7a --->
```py
# Inicializamos una fila
row = [""] * 3 # fila i['', '', '']
# Hacemos una tabla
board = [row] * 3
```
**Output:**
```py
>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]
```
No asignamos tres `"X"`, ¿cierto?
#### 💡 Explicación:
Cuando inicializamos la variable `row`, ocurre esto en la memoria (explicado en la ilustración):

Y cuando `board` es inicializada multiplicando `row`, esto es lo que ocurre dentro de la memoria (cada elemento `board[0]`, `board[1]` y `board[2]` hacen referencia a la misma lista referida por `row`).

Podemos evitar este escenario si no utilizamos la variable `row` para generar `board`. (Preguntado en [este]((https://github.com/satwikkansal/wtfpython/issues/68)) Issue).
```py
>>> board = [['']*3 for _ in range(3)]
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['', '', ''], ['', '', '']]
```
---
### ▶ La variable de Schrödinger *
<!-- Example ID: 4dc42f77-94cb-4eb5-a120-8203d3ed7604 --->