forked from satwikkansal/wtfpython
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathREADME.md
3526 lines (2810 loc) · 132 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">
<a href="https://github.com/satwikkansal/wtfpython">English</a>
| <a href="#">Tiếng Việt</a>
</p>
<p align="center">Cùng khám phá và tìm hiểu Python thông qua các đoạn mã khiến bạn bất ngờ.</p>
Các bản dịch tiếng nước ngoài khác: [Tiếng Trung 中文](https://github.com/leisurelicht/wtfpython-cn) | [Thêm bản dịch](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].)
Bạn có thể tham khảo các đoạn mã với: [Chế độ trực quan](https://colab.research.google.com/github/vuduclyunitn/wtfptyhon-vi/blob/master/irrelevant/wtf.ipynb) | [Giao diện dòng lệnh](https://pypi.python.org/pypi/wtfpython)
Python là một ngôn ngữ cấp cao, với các mã được thông dịch thay vì biên dịch như các ngôn ngữ khác như C hay Java. Python có rất nhiều các tính năng giúp việc lập trình dễ dàng, thuận tiện. Tuy nhiên, các đoạn mã viết bằng Python thỉnh thoảng cho ra kết quả không rõ ràng, gây khó hiểu khi mới nhìn vào.
wtfpython được tạo ra với mong muốn giải thích chính xác cách hoạt động của các đoạn mã thoạt nhìn khó hiểu và các tính năng ít được biết tới trong Python.
Một vài ví dụ có thể không làm bạn quá ngạc nhiên, tuy vậy bạn sẽ khám phá được những điều hay ho về Python mà có thể bạn chưa từng biết tới. Học lập trình thông qua những ví dụ như vậy giúp bạn hiểu sâu hơn những thứ nằm bên trong của một ngôn ngữ lập trình, khi đó bạn sẽ thấy hứng thú hơn trong quá trình học.
Nếu độc giả là một lập trình viên có thâm niên, hãy thử thức mình với các đoạn mã sắp tới, cố gắng làm đúng mỗi thử thách ngay trong lần đầu tiên. Độc giả có thể đã thử quả một vài trong số các bài toán trước đó, đọc và làm các bài toán dưới đây có thể giúp bạn ôn lại chúng.
PS: Nếu bạn đã đọc bài này trước đó, bạn có thể muốn xem những thay đổi mới [ở đây](https://github.com/satwikkansal/wtfpython/releases/).
Nào ta bắt đầu ...
# Những nội dung chính
<!-- Generated using "markdown-toc -i README.md --maxdepth 3"-->
<!-- toc -->
- [Cấu trúc của các ví dụ](#structure-of-the-examples)
+ [▶ Some fancy Title](#-some-fancy-title)
- [Cách sử dụng](#usage)
- [👀 Các ví dụ](#-examples)
* [Section: Strain your brain!](#section-strain-your-brain)
+ [▶ First things first! *](#-first-things-first-)
+ [▶ Strings can be tricky sometimes](#-strings-can-be-tricky-sometimes)
+ [▶ Be careful with chained operations](#-be-careful-with-chained-operations)
+ [▶ How not to use `is` operator](#-how-not-to-use-is-operator)
+ [▶ Hash brownies](#-hash-brownies)
+ [▶ Deep down, we're all the same.](#-deep-down-were-all-the-same)
+ [▶ Disorder within order *](#-disorder-within-order-)
+ [▶ Keep trying... *](#-keep-trying-)
+ [▶ For what?](#-for-what)
+ [▶ Evaluation time discrepancy](#-evaluation-time-discrepancy)
+ [▶ `is not ...` is not `is (not ...)`](#-is-not--is-not-is-not-)
+ [▶ A tic-tac-toe where X wins in the first attempt!](#-a-tic-tac-toe-where-x-wins-in-the-first-attempt)
+ [▶ The sticky output function](#-the-sticky-output-function)
+ [▶ The chicken-egg problem *](#-the-chicken-egg-problem-)
+ [▶ Subclass relationships](#-subclass-relationships)
+ [▶ All-true-ation *](#-all-true-ation-)
+ [▶ The surprising comma](#-the-surprising-comma)
+ [▶ Strings and the backslashes](#-strings-and-the-backslashes)
+ [▶ not knot!](#-not-knot)
+ [▶ Half triple-quoted strings](#-half-triple-quoted-strings)
+ [▶ What's wrong with booleans?](#-whats-wrong-with-booleans)
+ [▶ Class attributes and instance attributes](#-class-attributes-and-instance-attributes)
+ [▶ Non-reflexive class method *](#-non-reflexive-class-method-)
+ [▶ yielding None](#-yielding-none)
+ [▶ Yielding from... return! *](#-yielding-from-return-)
+ [▶ Nan-reflexivity *](#-nan-reflexivity-)
+ [▶ Mutating the immutable!](#-mutating-the-immutable)
+ [▶ The disappearing variable from outer scope](#-the-disappearing-variable-from-outer-scope)
+ [▶ The mysterious key type conversion](#-the-mysterious-key-type-conversion)
+ [▶ Let's see if you can guess this?](#-lets-see-if-you-can-guess-this)
* [Section: Slippery Slopes](#section-slippery-slopes)
+ [▶ Modifying a dictionary while iterating over it](#-modifying-a-dictionary-while-iterating-over-it)
+ [▶ Stubborn `del` operation](#-stubborn-del-operation)
+ [▶ The out of scope variable](#-the-out-of-scope-variable)
+ [▶ Deleting a list item while iterating](#-deleting-a-list-item-while-iterating)
+ [▶ Lossy zip of iterators *](#-lossy-zip-of-iterators-)
+ [▶ Loop variables leaking out!](#-loop-variables-leaking-out)
+ [▶ Beware of default mutable arguments!](#-beware-of-default-mutable-arguments)
+ [▶ Catching the Exceptions](#-catching-the-exceptions)
+ [▶ Same operands, different story!](#-same-operands-different-story)
+ [▶ Name resolution ignoring class scope](#-name-resolution-ignoring-class-scope)
+ [▶ Needles in a Haystack *](#-needles-in-a-haystack-)
+ [▶ Splitsies *](#-splitsies-)
+ [▶ Wild imports *](#-wild-imports-)
+ [▶ All sorted? *](#-all-sorted-)
+ [▶ Midnight time doesn't exist?](#-midnight-time-doesnt-exist)
* [Section: The Hidden treasures!](#section-the-hidden-treasures)
+ [▶ Okay Python, Can you make me fly?](#-okay-python-can-you-make-me-fly)
+ [▶ `goto`, but why?](#-goto-but-why)
+ [▶ Brace yourself!](#-brace-yourself)
+ [▶ Let's meet Friendly Language Uncle For Life](#-lets-meet-friendly-language-uncle-for-life)
+ [▶ Even Python understands that love is complicated](#-even-python-understands-that-love-is-complicated)
+ [▶ Yes, it exists!](#-yes-it-exists)
+ [▶ Ellipsis *](#-ellipsis-)
+ [▶ Inpinity](#-inpinity)
+ [▶ Let's mangle](#-lets-mangle)
* [Section: Appearances are deceptive!](#section-appearances-are-deceptive)
+ [▶ Skipping lines?](#-skipping-lines)
+ [▶ Teleportation](#-teleportation)
+ [▶ Well, something is fishy...](#-well-something-is-fishy)
* [Section: Miscellaneous](#section-miscellaneous)
+ [▶ `+=` is faster](#--is-faster)
+ [▶ Let's make a giant string!](#-lets-make-a-giant-string)
+ [▶ Minor Ones *](#-minor-ones-)
- [Contributing](#contributing)
- [Acknowledgements](#acknowledgements)
- [🎓 License](#-license)
* [Surprise your friends as well!](#surprise-your-friends-as-well)
* [More content like this?](#more-content-like-this)
<!-- tocstop -->
# Cấu trúc của các ví dụ
Tất cả các các ví dụ được trình bày với cấu trúc như sau:
> ### ▶ Một tiêu đề hấp dẫn
>
> ```py
> # Đoạn mã tạo dựng ví dụ.
> # Đoạn mã chủ thể cần khám phá...
> ```
>
> **Kết quả (Các phiên bản Python):**
>
> ```py
> >>> câu lệnh kích hoạt?
> Một vài kết quả bất ngờ, không như mong đợi
> ```
> (Có thể có hay không): Một dòng mô tả kết quả
>
>
> #### 💡 Giải thích:
>
> * Giải thích những điều đang diễn ra và tại sao.
> ```py
> # Đoạn mã tạo dựng ví dụ
> # Trong trường hợp cần thiết, chúng tôi liệt kê thêm nhiều ví dụ khác để giúp bạn hiểu rõ hơn
> ```
> **Kết quả (Các phiên bản Python):**
>
> ```py
> >>> trigger # some example that makes it easy to unveil the magic
> >>> trigger # Một vài ví dụ giúp bạn hiểu các đoạn mã
> # some justified output
> ```
**Lưu ý:** Tất cả các ví dụ đã được chứng minh chạy thành công trên trình thông dịch Python 3.5.2 chế độ tương tác, với các phiên bản Python khác các ví dụ sẽ vẫn chạy bình thường, ngoại trừ một số ví dụ chúng tôi sẽ lưu ý trước phần kết quả.
# Cách dùng các ví dụ
Theo tôi, để học các ví dụ trong bài, bạn nên đọc theo trình tự thời gian, và đối với mỗi ví dụ hãy:
- Đọc kĩ đoạn mã tạo dựng nên ví dụ. Nếu bạn đã lập trình lâu rồi, bạn sẽ đoán được những điều sắp tới ngay.
- Đọc kết quả của các ví dụ và thực hiện hai việc sau:
+ Kiểm tra xem kết quả có giống như bạn nghĩ hay không.
+ Một khi đọc xong, hãy hỏi chính bạn xem mình đã hiểu thông suốt lý do mà có kết quả như vậy chưa.
- Nếu câu trả lời là "chưa, tôi chưa hiểu" (không sao cả), hít một hơi thật sau, và đọc phần giải thích (nếu bạn vẫn chưa hiểu, hãy tạo một issue [ở dây](https://github.com/satwikkansal/wtfpython/issues/new)).
- Nếu câu trả lời là "có, tôi đã hiểu", bạn có thể đọc ví dụ tiếp theo.
PS: Bạn có thể đọc WTFPython dùng chế độ dòng lệnh sử dụng [pypi package](https://pypi.python.org/pypi/wtfpython),
```sh
$ pip install wtfpython -U
$ wtfpython
```
---
# 👀 Các ví dụ
## Chương 1: Hack não!
### ▶ Món khai vị! *
<!-- Example ID: d3d73936-3cf1-4632-b5ab-817981338863 -->
<!-- read-only -->
Kí hiệu "con hà mã" ("Walrus" operator), được giới thiệu trong phiên bản Python 3.8 đã trở nên khá phổ biến vì một vài lý do. Hãy thử qua nó xem
1\.
```py
# Phiên bản Python 3.8+
>>> a = "wtf_walrus"
>>> a
'wtf_walrus'
>>> a := "wtf_walrus"
File "<stdin>", line 1
a := "wtf_walrus"
^
SyntaxError: invalid syntax (Lỗi về cú pháp: Cú pháp không hợp lệ)
>>> (a := "wtf_walrus") # This works though
>>> a
'wtf_walrus'
```
2 \.
```py
# Phiên bản Python 3.8+
>>> a = 6, 9
>>> a
(6, 9)
>>> (a := 6, 9)
>>> a
6
>>> a, b = 6, 9 # Phân rã (unpacking) các giá trị, hay còn gọi là câu lệnh gán đa giá trị (multiple assignments)
>>> a, b
(6, 9)
>>> (a, b = 16, 19) # Có
File "<stdin>", line 1
(a, b = 6, 9)
^
SyntaxError: invalid syntax (Lỗi cú pháp: cú pháp không hợp lệ)
>>> (a, b := 16, 19) # Câu lệnh này in ra một tuple có 3 phần tử không như mong đợi (đáng lẽ là 2 phần tử 16 và 19)
(6, 16, 19)
>>> a # a được được gán lại giá trị trước đó, nhưng giá trị phía dưới vẫn giữ nguyên, là sao?
6
>>> b
16
```
#### 💡 Giải thích
**Ôn lại một chút về kí hiệu "con hà mã"**
Kí hiệu con hà mã (`:=`) lần đầu tiên được giới thiệu trong phiên bản Python 3.8, nó hữu dụng khi bạn muốn gán giá trị cho các biến bên trong một biểu diễn (expression).
```py
def some_func():
# Giả định rằng ta thực hiện một vài phép tính tốn nhiều tài nguyên (thời gian, I/O) ở đây
# time.sleep(1000)
return 5
# Thay vì thực hiện việc kiểm tra kết quả trả về của hàm trên,
if some_func():
print(some_func()) # Và gọi lại hàm đó trong thân điều kiện, nghĩa là thực hiện các tính toán trong hàm 2 lần.
# Hay tốt hơn, ta có thể tiết kiệm một lời gọi hàm thông qua việc lấy về giá trị trả về ở một lần gọi và thực hiện so sánh trên giá trị đó:
a = some_func()
if a:
print(a)
# Dùng kĩ hiệu con hà mã bạn có thể viết ngắn gọn hơn như dưới đây, phép gán được sử dụng như mệnh đề điều kiện và ta có thể sử dụng biến được gán giá trị trong thân câu điều kiện if:
if a := some_func():
print(a)
```
**Kết quả (> 3.8):**
```py
5
5
5
```
Sử dụng kí hiệu con hà mã giúp ta rút ngắn được đoạn mã đi một dòng và tránh được việc gọi `some_func` hai lần.
- Ta chỉ được sử dụng phép gán có kí hiệu hà mã ở cấp độ cao nhất do đó lỗi cú pháp (`SyntaxError`) trong câu lệnh `a := "wtf_walrus"` xảy ra. Khi ta cho phép gán này vào hai dấu ngoặc đơn `()` thì sẽ không bị lỗi nữa.
- Thông thường, câu lệnh có dấu bằng `=` sẽ không được phép đặt trong dấu ngoặc đơn. Do vậy câu lệnh `(a, b = 6, 9)` bị lỗi cú pháp.
- Cú pháp của kí hiệu gán con hà mã như sau: `NAME:= expr`, ở đó `NAME` là một tên biến hợp lệ, và `expr` là một biểu diễn hợp lệ. Do vậy, việc sử dụng các phép gộp (packing) hay phân rã trong trường hợp này sẽ không được hỗ trợ, nghãi là
- `(a := 6, 9)` tương đương với `((a := 6), 9)` và buổi diễn cuối cùng là `(a, 9) ` (ở đó giá trị của `a` là 6). Bạn có thể kiểm tra lại với các dòng lệnh dưới đây
```py
>>> (a := 6, 9) == ((a := 6), 9)
True # Biểu diễn bên trái bằng bên phải do phép phân rã không được phép (như đã giải thích phía trên)
>>> x = (a := 696, 9)
>>> x
(696, 9)
>>> x[0] is a # Cả x[0] và a cùng trỏ về chung một địa chỉ trong bộ nhớ
True
```
- Tương tự, `(a, b := 16, 19)` tương đương với `(a, (b := 16), 19)` khi ta có 3 giá trị trong một tuple.
---
### ▶ Strings thỉnh thoảng có thể khá oái oăm
<!-- Example ID: 30f1d3fc-e267-4b30-84ef-4d9e7091ac1a --->
1\.
```py
>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # Để ý rằng cả hai giá trị id đều giống nhau (140420665652016).
140420665652016
```
2\.
```py
>>> a = "wtf"
>>> b = "wtf"
>>> a is b
True # a và b cùng trỏ tới một địa chỉ trong bộ nhớ
>>> a = "wtf!"
>>> b = "wtf!"
>>> a is b
False # a và b không cùng trỏ tới một địa chỉ trong bộ nhớ
```
3\.
```py
>>> a, b = "wtf!", "wtf!"
>>> a is b # Áp dụng cho tất cả các phiên bản Python, ngoại trừ các phiên bản 3.7.x
True # a và b cùng trỏ tới một địa chỉ trong bộ nhớ
>>> a = "wtf!"; b = "wtf!"
>>> a is b # Kết quả là True hoặc False tuỳ thuộc vào môi trường bên chạy đoạn mã (python shell / ipython / as a script)
False
```
```py
# Tạo một file tên some_file.py, chứa ba dòng code dưới đây:
a = "wtf!"
b = "wtf!"
print(a is b)
# Khi file này được chạy kết quả in ra là True
```
4\.
**Kết quả (< Python3.7 )**
```py
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
```
Có gì đó sai sai?
#### 💡Giải thích:
+ Những điều xảy ra trong các đoạn mã đầu tiên và thứ hai đến từ một tối ưu của CPython (được gọi là string interning), tối ưu này cố gắng sử dụng các đối tượng không thể thay đổi giá trị hiện tồn tại trong một vài trường hợp thay vì việc tạo ra một đối tượng mới mỗi lần.
+ Sau khi được "interned," nhiều biến có thể tham chiếu tới cùng một đối tượng string trong bộ nhớ (do vậy mà tiết kiệm bộ nhớ).
+ Trong các đoạn mã phía trên, các strings được interned một cách ngầm. Quyết định ki nào thì intern một cách ngầm là phụ thuộc vào mỗi triển khai Python. Có một vài luật có thể được sử dụng để đoán biết một string sẽ được intern hay không:
* Tất cả strings có độ dài bằng 0 và 1 được intern.
* Các strings được intern tại thời điểm biên dịch (`'wtf'` sẽ được intern nhưng `''.join(['w', 't', 'f'])` sẽ không được intern).
* Các strings không được tạo thành từ các mẫu tự ASCII, số, hoặc dấu gạch chân, sẽ không được intern. Điều đó giải thích tại sao `'wtf!'` không được intern bởi vì kí tự `!`. Triển khai Cpython cho luật này có thể tìm thấy [ở đây](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19)
![image](/images/string-intern/string_intern.png)
+ Khi `a` and `b` được cho nhận giá trị `"wtf!"` trên cùng một dòng, trình thông dịch Python sẽ tạo một đối tượng mới, sau đó tham chiếu tới biến thứ hai tại cùng một thời điểm. Nếu bạn làm các phép gán trên các dòng riêng biệt, trình thông dịch sẽ không biết rằng đã có `"wtf!"` tồn tại như một đối tượng (bởi vì `"wtf!"` không được intern ngầm như điều được trình bày trên). Đây là một tối ưu tại thời điểm biên dịch. Tối ưu này không áp dụng cho các phiên bản CPython 3.7.x (xem thêm về vấn đề này [ở đây] (https://github.com/satwikkansal/wtfpython/issues/100)).
+ Một đơn vị biên dịch trong môi trường tương tác như IPython bao gồm chỉ một câu lệnh đơn, trái lại `a = "wtf!"; b = "wtf!"`là hai câu lệnh. Điều này giải thích tại sao các danh định (identities) lại khác nhau trong `a = "wtf!"; b = "wtf!"`, và cũng giải thích tại sao chúng lại giống nhau trong `some_file.py`.
+ Sự thay đổi đột ngột trong đoạn mã thứ tư là do một kĩ thuật [Tối ưu peephole](https://en.wikipedia.org/wiki/Peephole_optimization) hay còn được biết đến như sự gấp hằng số. Điều này có nghĩa là biểu diễn `'a'*20` được thay thế bở `'aaaaaaaaaaaaaaaaaaaa'` trong quá trình biên dịch tiết kiệm một vài chu kì đồng hồ trong suốt thời gian chạy . Sự gấp hằng số chỉ xảy ra đối với các strings có độ dài nhỏ hơn 20 (Tại sao? tưởng tượng kích thước của file `.pyc` sinh ra bởi biểu diễn `'a'*10**10`). [Link này](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288) dẫn tới triển khai của tối ưu được đề cập.
+ Chú ý rằng: trong Python 3.7, gấp hằng số được chuyển từ bộ tối ưu peephole sang bộ tối ưu mới AST với một vài thay đổi luận lý, nên đoạn mã thứ tư không chạy được trong Python 3.7. Bạn có thể đọc thêm về sự thay đổi này [ở ](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
```
#### 💡 Giải thích:
As per https://docs.python.org/2/reference/expressions.html#not-in
> Nếu a, b, c, ..., y, z là các biểu diễn (expressions) và op1, op2, ..., opN là các phép so sánh, khi đó op1 b op2 c ... y opN z tương đương với op1 b and b op2 c and ... y opN, ngoại trì việc mỗi biểu diễn được thực hiện hay đánh giá nhiều nhât một lần
Trong khi những điều ta thấy phía trên có thể hơi ngớ ngẩn đối với bạn, ta có thể làm những thứ thú vị hơn như `a == b == c` và `0 <= x <= 100`.
* `False is False is False` tương đương `(False is False) and (False is False)`
* `True is False == False` tương đương với `True is False and False == False` và do phần so sánh đầu tiên (`True is False`) cho ra kết quả `False`, do đó kết quả cuối cùng là `False`.
* `1 > 0 < 1` tương đương với `1 > 0 and 0 < 1` và cho ra kết quả là `True`.
* Biểu diễn `(1 > 0) < 1` tương đương với `True < 1` và
```py
>>> int(True)
1
>>> True + 1 #làm cho vui thôi, chứ không liên quan
2
```
do đó, `1 < 1` cho ra kết quả`False`
---
### ▶ How not to use `is` operator
<!-- Example ID: 230fa2ac-ab36-4ad1-b675-5f5a1c1a6217 --->
Ví dụ dươi đây rất nổi tiếng trên 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\.
**Kết quả**
```py
>>> a, b = 257, 257
>>> a is b
True
```
**Kết qủa (Chỉ áp dụng cho Python 3.7.x )**
```py
>>> a, b = 257, 257
>> a is b
False
```
#### 💡 Giải thích:
**Sự khác biệt giữa `is` và `==`**
* `is` kiểm tra xem cả hai phần tử so sánh có trỏ về cùng một đôi tượng (ví dụ, `is` kiểm tra định danh của cả hai thành phần được so sánh có khớp với nhau hay không)
* `==` so sánh giá trị của hai phần tử xem chúng có bằng nhau hay không
* Vì thế `is` được dùng cho việc so sánh tham chiếu và `==` được dùng cho so sánh tham trị. Hãy xem ví dụ dưới đây để hiểu rõ hơn
```py
>>> class A: pass
>>> A() is A() # Hai đối tượng rỗng nhưng nằm ở hai vị trí khác nhau trong bộ nhớ.
False
```
** `256` là một đối tượng hiện hữu nhưng `257` lại không phải là một đối tượng hiện hữu.
Khi bạn khởi chạy python các số từ `-5` tới `256` sẽ được cấp phát. Những sô nay được sử dụng rất nhiều, do đó việc cấp phát này là hợp lý.
Tham khảo từ https://docs.python.org/3/c-api/long.html
> Cách triển khai hiện hành của Python duy trì một mảng các đối tượng sô nguyên từ -5 tới 256, khi bạn tạo một số nguyên trong dải này bạn sẽ quay trở về lại một tham chiếu tới một đôi tượng tồn tại. Do vậy ta vẫn có thể thay đổi giá trị của 1.
```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 trình thông dịch không đủ thông minh khi thực thi `y = 257` và nhận ra rằng chúng ta đã tạo một số nguyên có giá trị là `257,` rồi, do đó nó tiếp tục tạo một đôi tượng khác trong bộ nhớ.
Một tối ưu tương tự áp dụng cho các đối tượng bất biến (**immutable**) khác như là các tuples. Bởi vì lists có thể biến đổi được, do đó ta hiểu tại sao `[] is []` sẽ trả về `False` và `() is ()` sẽ trả về `True`. Điều này giải thích đoạn mã thứ hai. Nào hãy cùng đi qua ví dụ thứ 3.
** Cả `a` và `b` đều trỏ về cùng một đối tượng khi được khởi tạo với cung một giá trị và trên cùng một dòng code**
**Kết quả**
```py
>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
140640774013392
>>> id(b)
140640774013488
```
* Trên cùng một dòng code nơi cả a va b được gán cho giá trị `257`, trinh thông dịch Python tạo một đôi tượng mới, sau đó trỏ tới biến thứ hai cùng một lúc. Nếu bạn thực hiện việc gán trên các dòng riêng biệt, Python sẽ không biết rằng đã có săn một đối tượng `257`
* Đây là một tối ưu của trình biên dịch, và áp dụng cụ thể cho môi trường tương tác (interactive environment). Khi bạn nhập hai dòng code trong phiên thôn dịch động, chúng được biên dịch riêng, do đó được tối ưu riêng. Nếu bạn thử ví dụ này trong một file `.py` bạn sẽ không thấy điều trên xảy ra do file code được biên dịch một lần. Tối ưu nay không chỉ giơi hạn cho các số nguyên, nó còn hoạt động được với các kiểu dữ liệu bất biến khác như strings (xem "Strings are tricky example") và floats.
```py
>>> a, b = 257.0, 257.0
>>> a is b
True
```
* Tại sao ví dụ nay lại không chạy được trên Python 3.7? Đại khái lý do là bởi vì các tối ưu của trình biên dịch áp dụng cho các trường hợp cụ thể (ví dụ. một cách tối ưu có thể thay đổi tuỳ theo phiên bản, hệ điều hành, vân vân). Tôi vân đang tìm hiểu các thay đổi cụ thể trong code triểu khai, bạn có thể xem thêm [tại đây](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"
```
**Kết quả:**
```py
>>> some_dict[5.5]
"JavaScript"
>>> some_dict[5.0] # "Python" chiếm lấy khoá (key) của "Ruby"?
"Python"
>>> some_dict[5]
"Python"
>>> complex_five = 5 + 0j
>>> type(complex_five)
complex
>>> some_dict[complex_five]
"Python"
```
Thế quái nào mà toàn in ra Python?
#### 💡 Giải thích
* Tính duy nhất của các khoá (keys) trong cấu trúc dữ liệu từ điển của được xác định bởi *sự tương đương*, chứ không phải dựa trên danh tính. Do đó dẫu cho `5`, `5.0`, và `5 + 0j` là các đối tượng riêng biệt có kiểu khác nhau, nhưng bởi vì chúng băng nhau nên không thể tồn tại như một khoá riêng của `dict` (hoặc `set`). Khi bạn thêm các khoá này vào từ điển sau đó tra giá trị của khoá đó dựa trên giả định về sự tương đương thì Python chỉ trả về giá trị của khoá được chèn vào ban đầu (thay vì trả lại lỗi về truy xuất khoá `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
```
* Nguyên lý trên cũng áp dụng khi bạn gán giá trị cho khoá. Khi bạn thực hiện phép gán `some_dict[5] = "Python"`, Python tìm phần tử có sẵn trong từ điển có khoá tương đương, trong trường hợp này nè `5.0 -> "Ruby"`, ghi đè lên giá trị của khoá này ngay, và lờ đi khoá tương đương mà bạn mới cung cấp.
```py
>>> some_dict
{5.0: 'Ruby'}
>>> some_dict[5] = "Python"
>>> some_dict
{5.0: 'Python'}
```
* Vậy làm sao để cập nhật khoá `5` vào từ điển (thay vì `5.0`)? Thực sự là chúng ta không thể làm điều đó vơi một thao tác, nhưng ta có thể xoá đi khoá cũ (`del some_dict[5.0]`), và sau đó thiết lập khoá mới(`some_dict[5]`) để có thể lấy được khoá `5` thay vì `5.0`, tuy nhiên cách này ít ai xài tới lắm.
* Làm thế nào Python tìm khoá `5` trong từ điển có chứa sẵn khoá `5.0`? Nó làm được điều đó mà không phải dò qua mọi phần tử trong từ điển thông qua sử dụng các hàm băm (hash functions), do đó tốn thời gian chạy hằng số (constant time). Khi Python tra khoá tên `foo` trong một tư điển, đầu tiên nó thực hiện hàm băm `hash(foo)` (với thời gian chạy hằng số). Bởi vì trong Python các đối tượng băng nhau khi chúng có chung một giá trị băm ([đọc thêm ở đây](https://docs.python.org/3/reference/datamodel.html#object.__hash__) here), `5`, `5.0`, and `5 + 0j` have the same hash value.
```py
>>> 5 == 5.0 == 5 + 0j
True
>>> hash(5) == hash(5.0) == hash(5 + 0j)
True
```
**Ghi chú:** Các đối tượng có giá trị băm băng nhau chưa chắc đã bằng nhau. (Vẫn đề này được biết tới với tên gọi [hash collision](https://en.wikipedia.org/wiki/Collision_(computer_science)), và giảm đi hiệu năng với thời gian hằng số qua việc dùng hàm băm.)
---
### ▶ Deep down, we're all the same.
<!-- Example ID: 8f99a35f-1736-43e2-920d-3b78ec35da9b --->
```py
class WTF:
pass
```
**Kết quả:**
```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
```
#### 💡 Giải thích:
* Khi `id` được gọi, Python tạo một đối tượng lớp `WTF` và truyền đối tượng này cho hàm `id`. Hàm `id` lấy `id` của đối tượng (vùng nhớ của đối tượng), và vứt đối tượng này đi. Do đó đối tượng bị tiêu huỷ.
* Khi chúng ta gọi `id` hai lần, Python cấp phát cùng một vùng nhơ cho đối tượng thư hai. Bởi vi (trong CPython) `id` sử dụng vùng nhớ cho id của đối tượng, id của hai đối tượng này là giống nhau.
* Vì vậy, id của đối tượng chỉ duy nhất trong vòng đời của đối tượng đó. Sau khi đối tượng bị tiêu hiểu, hay trước khi nó được tạo, những thứ khác có thể có cùng id với nó.
* Nhưng tại sao phép `is` lại cho ra kết quả `False`? Hãy nhìn ví dụ dưới đây
```py
class WTF(object):
def __init__(self): print("I")
def __del__(self): print("D")
```
**Kết quả:**
```py
>>> WTF() is WTF()
I
I
D
D
False
>>> id(WTF()) == id(WTF())
I
D
I
D
True
```
Như bạn quan sát thấy, có sự khác biệt ở thứ tự tiêu hiểu các đối tượng, và đó tạo ra sự khác biệt.
---
### ▶ Vô trật tự trong trật tự *
<!-- Example ID: 91bff1f8-541d-455a-9de4-6cd8ff00ea66 --->
```py
from collections import OrderedDict
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
```
**Kết quả**
```py
>>> dictionary == ordered_dict # Nếu a == b
True
>>> dictionary == another_ordered_dict # and b == c
True
>>> ordered_dict == another_ordered_dict # thế sao c != a ??
False
# ta biết răng set chỉ chứa các phần tử độc nhất,
# thử tạo một set chứa 3 từ điển phía trên xem sao...
>>> len({dictionary, ordered_dict, another_ordered_dict})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict' (Lỗi về kiểu: kiểu không thể hash được)
# Lỗi trên xảy ra là điều dê hiểu do từ điển không được tran bị __hash__,
# sử dụng các lớp bọc (wrapper classes) ta xây dựng phía trên thử xem.
>>> 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}) # xáo trộn thứ tự cá phần tử trong set
2
```
Cái quái gì đang xảy ra?
#### 💡 Giải thích:
- Lý do tại sao quy tắc so sánh bắc cầu không áp dụng được khi so sánh `dictionary`, `ordered_dict` và `another_ordered_dict` là do cách triển khai phương thức `__eq__` trong lớp `OrderedDict` . Xem thêm [tài liệu](https://docs.python.org/3/library/collections.html#ordereddict-objects)
> Các phép so sánh bằng giữa các đối tượng OrderedDict tuôn theo thư tự và được thực hiện như sau. Còn phép so sánh băng giữa cá đối tượng `OrderedDict` và các đối tượng ánh xạ khác (mapping objects) thì không tuân theo thứ tự như là các từ điển thông thường..
- Lý do ở đây là các đối tượng `OrderedDict` được cho phép bị thay thế trực tiếp tại bất cứ vị trí nao một từ điển thông thường được sử dụng.
- Vậy tại sao thay đổi thư tự của các từ điển lại ảnh hưởng tới đối tượng `set` được sinh ra? Câu trả lời là do thiếu sự so sánh nội đối tượng (intrasitive) . Do sets là các tập hợp không có thứ tự của các phần tử độc nhất, thứ tự các phần tử khi chèn vào không có nghĩa lý gì cả. Nhưng trong trường hợp này, nó lại có vấn đề. Hãy tìm hiểu thêm xem sao
```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
```
Sự bất nhất ở đây `another_ordered_dict in another_set` cho kết quả là `False` bởi vì `ordered_dict` đã tồn tại trong `another_set` trước đó, `ordered_dict == another_ordered_dict` trở thành `False`.
---
### ▶ Cố thêm chút nữa... *
<!-- 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)
```
**Kết quả:**
```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 (Ngoại lệ sinh ra bởi chia một số cho số 0)
>>> one_more_func()
Iteration 0
```
#### 💡 Giải thích:
- Khi câu lệnh `return`, `break` hay `continue` được thực hiện trong phần `try` của khối "try…finally", phần `finally` cũng được thực hiện.
- Giá trị trả về của một hàm được xác định bởi câu lệnh `return` cuối cung được thực hiện. Bởi vì phần `finally` luôn được thực hiện, câu lệnh `return` trong phần `finally` sẽ luôn là câu lệnh trả về gia trị cuối cùng.
- Tuy nhiên nếu phần finally thực hiện câu lệnh `return` hay `break` thì phần ngoại lệ tồn tại sẽ bị bỏ qua.
---
### ▶ For what?
<!-- Example ID: 64a9dccf-5083-4bc9-98aa-8aeecde4f210 --->
```py
some_string = "wtf"
some_dict = {}
for i, some_dict[i] in enumerate(some_string):
i = 10
```
**Kết quả:**
```py
>>> some_dict # An indexed dict appears.
{0: 'w', 1: 't', 2: 'f'}
```
#### 💡 Giải thích:
* Câu lệnh `for` được định nghĩa trong [ngữ pháp Python](https://docs.python.org/3/reference/grammar.html) như sau:
```
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
```
Ở đó `exprlist` là biến mục tiêu của phép gán. Điều đó co nghĩa là câu lệnh gán `{exprlist} = {next_value}` được **thực hiện đối với mỗi phần tử** trong đối tượng lặp (iterable)
An interesting example that illustrates this:
```py
for i in range(4):
print(i)
i = 10
```
**Kết quả:**
```
0
1
2
3
```
Bạn có nghĩ răng vòng lặp trên chỉ chạy có một lần?
**💡 Giải thích:**
- Câu lệnh gán `i = 10` không bao giờ ảnh hưởng tới các vòng lặp do cách thức hoạt động của vòng lặp for tron Python. Trước điểm khởi đầu của mỗi vòng lặp, phần tử tiếp theo được đưa ra bởi trình sinh (iterator, trong trường hợp nay là `range(4)`), phần tử này được giải nén ra (unpacked) và gán cho các biến chạy (trong trường hợp nay là `i`)
* Hàm `enumerate(some_string)` sinh ra giá trị mới `i` (biến đếm) và một kí tự từ `some_string` tại mỗi lần lặp. Sau đó nó gán khoá `i` của từ điển `some_dict` cho kí tự đó. Trình tự được thể hiện đơn giản như dưới đây:
```py
>>> i, some_dict[i] = (0, 'w')
>>> i, some_dict[i] = (1, 't')
>>> i, some_dict[i] = (2, 'f')
>>> some_dict
```
---
### ▶ Sự khác biệt đến từ thời điểm đánh giá
<!-- Example ID: 6aa11a4b-4cf1-467a-b43a-810731517e98 --->
1\.
```py
array = [1, 8, 15]
# Một biểu diễn generator thông thường
gen = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]
```
**Kết quả:**
```py
>>> print(list(gen)) # Các giá trị khác đi đâu mất rồi?
[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]
```
**Kết quả:**
```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]
```
**Kết quả:**
```py
>>> print(list(gen))
[401, 501, 601, 402, 502, 602, 403, 503, 603]
```
#### 💡 Lý giải
- Trong một biểu diễn [generator](https://wiki.python.org/moin/Generators), câu `in` được thực hiện tại thời điểm khai báo, nhưng câu điều kiện được thực hiện tại thời điểm chạy (runtime).
- Do trước thời điểm chạy, `array` được gán cho giá trị `[2, 8, 22]`, và trong ba số được gán trước đó `1`, `8` and `15`, chỉ có `8` có số lần xuất hiện trong mảng mới và do đó số lần xuât hiện lớn hơn `0`, nên generator chỉ cho ra số `8`.
- Sự khác biệt giữa kết quả của `g1` and `g2` trong phần thứ hai là do cách các biến `array_1` và `array_2` được gán lại các giá trị.
- Trong trường hợp đầu tiên, `array_1` được gán cho một đối tượng mới `[1,2,3,4,5]` và vì câu `in` được thực hiện tại thời điểm khai báo nên nó vẫn trỏ tới đối tượng cũ `[1,2,3,4]` (đối tượng này chưa bị mất đi).
- Trong trường hợp thư hai, phép gán lát cắt (slice assignment) `array_2` cập nhật đối tượng cũ `[1,2,3,4]` thành `[1,2,3,4,5]`. Hiển nhiên cả `g2` và `array_2` đều trỏ tới cung một đối tượng (đối tượng đã được cập nhật thành `[1,2,3,4,5]`).
- Okay, với những gì ta quan sát trên, co phải giá trị trả về từ `list(g)` trong phần thứ ba phải là `[11, 21, 31, 12, 22, 32, 13, 23, 33]`? (bởi vì `array_3` và `array_4` sẽ giống như `array_1`). Lý do chỉ `array_4` được cập nhật được giải thích ở đây [PEP-289](https://www.python.org/dev/peps/pep-0289/#the-details)
>Chỉ có vòng for ngoài cùng được thực hiện ngay lập tức, các lệnh khác được trì hoãn cho đến khi generator được chạy.
---
### ▶ `is not ...` không phải là `is (not ...)`
<!-- Example ID: b26fb1ed-0c7d-4b9c-8c6d-94a58a055c0d --->
```py
>>> 'something' is not None
True
>>> 'something' is (not None)
False
```
#### 💡 Giải thích
- `is not` là một toán tử nhị phân đơn, và khi thực hiện sẽ cho kết quả khác với sử dụng `is` và `not` riêng biệt.
- `is not` cho ra kết quả `False` khi các biến ở hai đầu của nó trỏ về cùng một đối tượng , và nếu hai biến này trỏ về khác đối tượng, kết quả sẽ là `True`. Cụ thể, `None` và `something` trỏ về hai biến khác nhau nên kết quả là `True`
- Trong ví dụ trên, `(not None)` sẽ cho ra kết quả `True` bởi vì khi sử dụng trong phép so sánh luận lý `None` tương đương với `False`, do đó dòng mã trên sẽ trở thành `'something' is True`. Cụ thể, do `something` và `True` trỏ về hai đối tượng khác nhau nên kết quả là `False`
---
### ▶ A tic-tac-toe where X wins in the first attempt!
<!-- Example ID: 69329249-bdcb-424f-bd09-cca2e6705a7a --->
```py
# Khởi tạo một hàng
row = [""] * 3 #row i['', '', '']
# Và tạo một bảng gồm các hàng
board = [row] * 3
```
**Kết quả:**
```py
>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]
```
Chúng ta đã gán `"X"` cho ba vị trí trong bảng này chăng?
#### 💡 Giải thích:
Khi chúng ta khởi tạoi biến `row`, hình mô phỏng dưới đây cho ta biết những gì diễn ra trong bộ nhớ
![image](/images/tic-tac-toe/after_row_initialized.png)
Khi `board` được khởi tạo băng việc nhân bản `row`, minh hoạ phía dưới mô tả những gì diên ra trong bộ nhớ (mỗi thành phần của bảng `board[0]`, `board[1]` và `board[2]` là một tham chiếu tới cung một danh sách trỏ bởi `row`)
![image](/images/tic-tac-toe/after_board_initialized.png)
Chúng ta có thể tránh điều trên xảy ra bằng cách không dùng biến `row` để sinh ra `board`. (Tim hiểu thêm tại (https://github.com/satwikkansal/wtfpython/issues/68) )
```py
>>> board = [['']*3 for _ in range(3)]
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['', '', ''], ['', '', '']]
```
---
### ▶ The sticky output function
<!-- Example ID: 4dc42f77-94cb-4eb5-a120-8203d3ed7604 --->