summaryrefslogtreecommitdiff
path: root/PYTHON/PYTHON_tuturial.md
blob: f223a475c7e4639cd069626b6222bce4e3f5e51b (plain)
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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910

https://habr.com/ru/articles/728568/
# Ядро планеты Python. Интерактивный учебник

Туториал

Добрый день! Меня зовут Михаил Емельянов, недавно я опубликовал на «Хабре» небольшую [статью](https://habr.com/ru/articles/697760/) с примерным путеводителем начинающего Python-разработчика. Пользуясь этим материалом как своего рода оглавлением книги, я написал первые четыре главы мини-учебника «Ядро планеты Python», где постарался коротко, но достаточно ёмко раскрыть специфику, удобство, красоту и силу этого прекрасного языка.

  

Оригинал учебника лежит на [GitHub](https://github.com/amaargiru/pycore), вы вольны сколько угодно дополнять и переделывать его. Самое главное — учебник написан на Jupiter Notebook, а это значит, что вы можете интерактивно редактировать код, мгновенно добавляя новые сущности или проясняя непонятные моменты.

  

![Core of the planet Python](https://habrastorage.org/r/w1560/getpro/habr/post_images/58f/2c5/a2c/58f2c5a2c6142c8bee312e39ec2f4f00.jpg)

  

Погружаясь в Python, не забывайте про прекрасную официальную документацию [docs.python.org](https://docs.python.org/). Изучив её, хотя бы по диагонали, и постепенно углубляясь в нужные разделы, вы сможете убедиться, что многие «хаки», «открытия» и прочие неочевидные вещи уже давно разжеваны, описаны и имеют подробные примеры применения.

  

Также я бы рекомендовал для изучения базового синтаксиса Python на полную катушку использовать [leetcode.com](https://leetcode.com/problemset/all/?difficulty=EASY&page=1&status=NOT_STARTED). Если отфильтровать задачи по уровню «Easy», а потом добавить дополнительную сортировку по столбцу «Acceptance», то перед вами предстанет не волчий оскал соревновательной платформы, а ванильный букварь с плавно нарастающим уровнем задачек.

  

## 1. Структуры данных

  

![Data structures](https://habrastorage.org/r/w1560/getpro/habr/post_images/190/f06/61d/190f0661deb5799fa48e2b28385d98a4.png)

  

Как известно, программирование = структуры данных + алгоритмы (у Никлауса Вирта даже книжка такая есть). Начнем с данных, а потом плавненько перейдем к методам их обработки.

  

### Список (list)

  

Список — самая универсальная и популярная структура данных в Python. Если вы пока точно не определились, какая структура понадобится в вашем проекте, просто возьмите список, с него достаточно просто мигрировать на что-нибудь более специализированное.  
Список представляет собой упорядоченную изменяемую коллекцию объектов произвольных типов. Внутреннее строение списка — массив (точнее, vector) указателей, т. е. список является динамическим массивом.

  

```
a = []  # Создаем пустой список
a: list[int] = [10, 20]
b: list[int] = [30, 40]
a.append(50)  # Добавляем значение в конец спискаb.insert(2, 60)  # Вставляем значение по определенному индексу
print(a, b)

a += bprint(f"Add: {a}")
a.reverse()b = list(reversed(a))  # reversed() возвращает итератор, а не списокprint(f"Reverse: {a}, {b}")

b = sorted(a)  # Возвращает новый отсортированный список
a.sort()  # Модифицирует исходный список и не возвращает ничего
print(f"Sort: {a}, {b}")


s: str = "A whole string"list_of_chars: list = list(s)print(list_of_chars)list_of_words: list = s.split()print(list_of_words)i: int = list_of_chars.index("w")  # Возвращает индекс первого вхождения искомого элемента или вызывает исключение ValueErrorprint(i)list_of_chars.remove("w")  # Удаляет первое вхождение искомого элемента или вызывает исключение ValueErrore = list_of_chars.pop(9)  # Удаляет и возвращает значение, расположенное по индексу. pop() (без аргумента) удалит и вернет последний элемент спискаprint(list_of_chars, e)a.clear()  # Очистка списка
```

```
[10, 20, 50] [30, 40, 60]
Add: [10, 20, 50, 30, 40, 60]
Reverse: [60, 40, 30, 50, 20, 10], [10, 20, 50, 30, 40, 60]
Sort: [10, 20, 30, 40, 50, 60], [10, 20, 30, 40, 50, 60]
['A', ' ', 'w', 'h', 'o', 'l', 'e', ' ', 's', 't', 'r', 'i', 'n', 'g']
['A', 'whole', 'string']
2
['A', ' ', 'h', 'o', 'l', 'e', ' ', 's', 't', 'i', 'n', 'g'] r
```

### Кортеж (tuple)

Кортеж — тоже список, только неизменяемый (immutable) и хэшируемый (hashable). Кортеж, содержащий те же данные, что и список, занимает меньше места:

```
a = [2, 3, "Boson", "Higgs", 1.56e-22]b = (2, 3, "Boson", "Higgs", 1.56e-22)print(f"List: {a.__sizeof__()} bytes")print(f"Tuple: {b.__sizeof__()} bytes")
```

```
List: 104 bytes
Tuple: 64 bytes
```

### Именованный кортеж (named tuple)

В соответствии с названием, имеет именованные поля. Удобно!

```
from collections import namedtuplerectangle = namedtuple('rectangle', 'length width')r = rectangle(length = 1, width = 2)print(r)print(r.length)print(r.width)print(r._fields)
```

```
rectangle(length=1, width=2)
1
2
('length', 'width')
```

### Словарь (dict)

Словарь — вторая по частоте использования структура данных в Python. dict — реализация хеш-таблицы, поэтому в качестве ключа нельзя брать нехешируемый объект, например, список (тут-то нам и может пригодиться кортеж). Ключом словаря может быть любой неизменяемый объект: число, строка, datetime и даже функция. Такие объекты имеют метод **__hash__()**, который однозначно сопоставляет объект с некоторым числом. По этому числу словарь ищет значение для ключа.

Списки, словари и множества (которые мы рассмотрим чуть ниже) изменяемы и не имеют метода хеширования, при попытке подставить их в словарь возникнет ошибка.

```
d = {}  # Создаем пустой словарьd: dict[str, str] = {"Italy": "Pizza", "US": "Hot-Dog", "China": "Dim Sum"}  # Непосредственное создание словаряk = ["Italy", "US", "China"]v = ["Pizza", "Hot-Dog", "Dim Sum"]d = dict(zip(k, v))  # Создание словаря из двух коллекций при помощи zipk = d.keys()  # Коллекция ключей. Отражает изменения в основном словареv = d.values()  # Коллекция значений. Тоже отражает изменения в основном словареk_v = d.items()  # Кортежи ключ-значение, которые тоже отражают изменения в основном словареprint(d)print(k)print(v)print(k_v)print(f"Mapping: {k.mapping['Italy']}")d.update({"China": "Dumplings"})  # Добавление значение. При совпадении ключа старое значение будет перезаписаноprint(f"Replace item: {d}")c = d["China"]  # Читаем значениеprint(f"Read item: {c}")try:    v = d.pop("Spain")  # Удаляет значение или вызывает исключение KeyErrorexcept KeyError:    print("Dictionary key doesn't exist")# Примеры dict comprehension (более подробно comprehension будет рассмотрено ниже)b = {k: v for k, v in d.items() if "a" in k}  # Вернет новый словарь, отфильтрованный по значению ключаprint(b)c = {k: v for k, v in d.items() if len(v) >= 7}  # Вернет новый словарь, отфильтрованный по длине значенийprint(c)d.clear() # Очистка словаря
```

  

```
{'Italy': 'Pizza', 'US': 'Hot-Dog', 'China': 'Dim Sum'}
dict_keys(['Italy', 'US', 'China'])
dict_values(['Pizza', 'Hot-Dog', 'Dim Sum'])
dict_items([('Italy', 'Pizza'), ('US', 'Hot-Dog'), ('China', 'Dim Sum')])
Mapping: Pizza
Replace item: {'Italy': 'Pizza', 'US': 'Hot-Dog', 'China': 'Dumplings'}
Read item: Dumplings
Dictionary key doesn't exist
{'Italy': 'Pizza', 'China': 'Dumplings'}
{'US': 'Hot-Dog', 'China': 'Dumplings'}
```

### Решение проблемы вычисления хеша при работе со словарем

Любая хеш-таблица, в том числе и питоновский словарь, должна уметь решать проблему вычисления хеша. Для этого используются техники **open addressing** или **chaining**. Python [использует](https://stackoverflow.com/questions/9010222/why-can-a-python-dict-have-multiple-keys-with-the-same-hash) open addressing.

Новый словарь инициализируется с 8 пустыми слотами.

Интерпретатор сначала пытается добавить новую запись по адресу, зависящему от хеша ключа.

```
addr = hash(key) & mask,
```

где

```
mask = PyDictMINSIZE - 1
```

Если этот адрес занят, то интерпретатор проверяет (при помощи ==) хеш и ключ. Если оба совпадают, то, значит, запись уже существует. Тогда начинается зондирование свободных слотов, которое идет в псевдослучайном порядке (порядок зависит от значения ключа). Новая запись будет добавлена по первому свободному адресу.

Чтение из словаря происходит аналогично, интерпретатор начинает поиск с позиции addr и идет по тому же псевдослучайному пути, пока не прочитает нужную запись.

### Defaultdict

Если попытаться прочитать из обычного словаря значение ключа, которого там нет, то будет выброшено исключение KeyError (исключения будут рассмотрены ниже). Defaultdict позволяет не писать обработчик исключений, а просто воспринимает чтение несуществующего ключа как команду записать в этот ключ и вернуть значение по умолчанию; например, defaultdict(int) вернет 0.

```
from collections import defaultdictdd = defaultdict(int)print(dd[10])  # Печать int, будет выведен ноль, значение по умолчаниюdd = {}  # "Обычный" пустой словарь# print(dd[10])  # вызовет исключение KeyError
```

```
0
```
  

### Счетчик (counter)

Счетчик подсчитывает передаваемые ему объекты. Иногда очень удобно просто бухнуть в счетчик какой-нибудь список и сразу получить структуру данных с подсчитанными элементами.

```
from collections import Countershirts_colors = ["red", "white", "blue", "white", "white", "black", "black"]c = Counter(shirts_colors)print(c)c["blue"] += 1print(f"After shopping: {c}")
```

```
Counter({'white': 3, 'black': 2, 'red': 1, 'blue': 1})
After shopping: Counter({'white': 3, 'blue': 2, 'black': 2, 'red': 1})
```

Объяснение работы Counter() при помощи defaultdict():

```
from collections import defaultdictshirts_colors = ["red", "white", "blue", "white", "white", "black", "black"]d = defaultdict(int)for shirt in shirts_colors:    d[shirt] += 1print(d)
```

  

```
defaultdict(<class 'int'>, {'red': 1, 'white': 3, 'blue': 1, 'black': 2})
```

  

### Множество (set)

  

Третья по распространенности питоновская структура данных. Когда-то, когда Python был молод, множества представляли собой несколько редуцированные словари, но со временем их судьбы (и реализации) стали расходиться. Однако, множество всё-таки является хеш-таблицей с соответствующим быстродействием на разных типах операций.

  

```
big_cities: set["str"] = {"New-York", "Los Angeles", "Ottawa"}american_cities: set["str"] = {"Chicago", "New-York", "Los Angeles"}big_cities |= {"Sydney"}  # Добавить значение (или add())american_cities |= {"Salt Lake City", "Seattle"}  # Сложить множества (или update())print(big_cities, american_cities)union_cities: set["str"] = big_cities | american_cities  # Или union()intersected_cities: set["str"] = big_cities & american_cities  # Или intersection()dif_cities: set["str"] = big_cities - american_cities  # Или difference()symdif_cities: set["str"] = big_cities ^ american_cities  # Или symmetric_difference()issub: bool = big_cities <= union_cities  # Или issubset()issuper: bool = american_cities >= dif_cities  # Или issuperset()print(union_cities)print(intersected_cities)print(dif_cities)print(symdif_cities)print(issub, issuper)big_cities.add("London")big_cities.remove("Ottawa")  # Удаляет значение, если оно имеется или выбрасывает KeyErrorbig_cities.discard("Los Angeles")  # Удаляет значение без выбрасывания KeyErrorbig_cities.pop()  # Возвращает и удаляет случайное значение (порядок в set неопределен) или выбрасывает KeyErrorbig_cities.clear()  # Очищает множество
```

  

```
{'New-York', 'Los Angeles', 'Sydney', 'Ottawa'} {'New-York', 'Seattle', 'Chicago', 'Los Angeles', 'Salt Lake City'}
{'Ottawa', 'Salt Lake City', 'Chicago', 'New-York', 'Seattle', 'Sydney', 'Los Angeles'}
{'New-York', 'Los Angeles'}
{'Ottawa', 'Sydney'}
{'Seattle', 'Ottawa', 'Chicago', 'Salt Lake City', 'Sydney'}
True False
```

  

### Иммутабельное множество (frozen set)

  

Frozen set — то же множество, только иммутабельное и хешируемое. Напоминает разницу между списком и кортежем, не правда ли?

  

```
a = frozenset({"New-York", "Los Angeles", "Ottawa"})
```

  

### Массив (array, bytes, bytearray)

  

Я перешел на Python с языков, более приближенных к «железу» (C, C#, даже на ассемблере когда-то писал за деньги :) и сначала немного удивлялся, что обычный массив, в котором всё так удобно лежит на своих местах, используется относительно редко. Массив в Python не является структурой данных, выбираемой по умолчанию и используется только в случаях, когда решающую роль начинают играть размер структуры и скорость её обработки. Но, с другой стороны, если вы смотрите в сторону NumPy и Pandas (немного затронуты ниже), то массивы — ваше всё.

  

Массив хранит переменные определенного типа, поэтому, в отличие от списка, не требует создания нового объекта для каждой новой переменной и выигрывает у списка в размерах и скорости доступа. Можно сказать, что это тонкая обёртка над Си-массивами.

  

Следует различать array («просто» массив), bytes (иммутабельный массив, содержащий только байты, наследие str из Python 2) и bytearray (мутабельный байтовый массив).

  

```
from array import arraya1 = array("l", [1, 2, 3, -4])a2 = array("b", b"1234567890")b = bytes(a2)print(a1)print(a2[0])print(b)print(a1.index(-4))  # Возвращает индекс элементы или выбрасывает ValueError
```

  

```
array('l', [1, 2, 3, -4])
49
b'1234567890'
3
```

  

```
# Созданиеb1 = bytes([1, 2, 3, 4])  # Целые числа должны быть в диапазоне от 0 to 255b2 = "The String".encode('utf-8')b3 = (-1024).to_bytes(4, byteorder='big', signed=True)  # byteorder = "big"/"little"/"sys.byteorder", signed = False/Trueb4 = bytes.fromhex('FEADCA')  # Для большей читаемости hex-значения могут быть разделены пробеламиb5 = bytes(range(10,30,2))print(b1, b2, b3, b4, b5)# Преобразованиеc: list = list(b"\xfc\x00\x00\x00\x00\x01")s: str = b'The String'.decode("utf-8")b: int = int.from_bytes(b"\xfc\x00", byteorder='big', signed=False)  # byteorder = "big"/"little"/"sys.byteorder", signed = False/Trues2: str = b"\xfc\x00\x00\x00\x00\x01".hex(" ")print(c, s, b, s2)with open("1.bin", "wb") as file:  # Байтовая запись в файл    file.write(b1)with open("1.bin", "rb") as file:  # Чтение из файла    b6 = file.read()print(b6)
```

  

```
b'\x01\x02\x03\x04' b'The String' b'\xff\xff\xfc\x00' b'\xfe\xad\xca' b'\n\x0c\x0e\x10\x12\x14\x16\x18\x1a\x1c'
[252, 0, 0, 0, 0, 1] The String 64512 fc 00 00 00 00 01
b'\x01\x02\x03\x04'
```

  

### Односвязный список

  

[Односвязный список](https://en.wikipedia.org/wiki/Linked_list#Singly_linked_list) представляет набор связанных узлов, каждый из которых хранит собственные данные и ссылку на следующий узел. В практике применим редко, но его любят использовать интервьюеры на собеседованиях, чтобы кандидат мог блеснуть своими алгоритмическими знаниями. В Python встроенной реализации не имеет, можно или использовать deque (в основе которого лежит двусвязный список), или написать свою реализацию.

  

### Двусвязный список (Deque)

  

Ссылки в каждом узле [двусвязного списка](https://en.wikipedia.org/wiki/Linked_list#Doubly_linked_list) указывают на предыдущий и на последующий узел в списке. Можно или использовать deque, или написать свою реализацию.

  

```
from collections import dequed = deque([1, 2, 3, 4], maxlen=1000)d.append(5)  # Add element to the right side of the dequed.appendleft(0)  # Add element to the left side of the deque by appending elements from iterabled.extend([6, 7])  # Extend the right side of the dequed.extendleft([-1, -2])  # Extend the left side of the dequeprint(d)a = d.pop()  # Remove and return an element from the right side of the deque. Can raise an IndexErrorb = d.popleft()  # Remove and return an element from the left side of the deque. Can raise an IndexErrorprint(a, b)print(d)
```

  

```
deque([-2, -1, 0, 1, 2, 3, 4, 5, 6, 7], maxlen=1000)
7 -2
deque([-1, 0, 1, 2, 3, 4, 5, 6], maxlen=1000)
```

  

### Queue

  

Queue реализует FIFO со множественными поставщиками данных и множественными потребителями. Может быть особенно полезен при многопоточности, позволяя корректно обмениваться информацией между потоками. Также существуют LifoQueue для реализации LIFO и PriorityQueue для реализации очереди с приоритетом.

  

```
from queue import Queueq = Queue(maxsize=1000)q.put("eat", block=True, timeout=10)q.put("sleep")  # По умолчанию block=True, timeout=Noneq.put("code")q.put_nowait("repeat")  # Эквивалент put("repeat", block=False). Если свободный слот не будет предоставлен немедленно, будет вызвано исключение queue.Fullprint(q.queue)a = q.get(block=True, timeout=10)  # Удалить и возвратить элемент из FIFOb = q.get()  # По умолчанию block=True, timeout=Nonec = q.get_nowait()  # Эквивалент get(False)print(a, b, c, q.queue)
```

  

```
deque(['eat', 'sleep', 'code', 'repeat'])
eat sleep code deque(['repeat'])
```

  

### Бинарное дерево (binary tree)

  

Иерархическая структура данных, в которой каждый узел имеет не более двух потомков. Встроенной реализации не имеет, нужно писать свою. Как правило, используются деревья с дополнительными свойствами, рассмотренные ниже.

  

### Куча (heap)

  

Бинарное дерево, удовлетворяющее свойство кучи: если B является узлом-потомком узла A, то ключ(A) ≥ ключ(B). Куча является максимально эффективной реализацией абстрактного типа данных, который называется очередью с приоритетом и поддерживающего две обязательные операции — добавить элемент и извлечь минимум (или максимум, в зависимости от реализации).

  

В Python min-куча (наименьшее значение всегда лежит в корне) реализована на базе списка при помощи встроенного модуля heapq. Если вам нужна max-куча, с максимальным значением в корне, можете воспользоваться советами со [Stackoverflow](https://stackoverflow.com/questions/2501457/what-do-i-use-for-a-max-heap-implementation-in-python).

  

```
import heapqh = [211, 1, 43, 79, 12, 5, -10, 0]heapq.heapify(h)  # Превращаем список в кучуprint(h)heapq.heappush(h, 2)  # Добавляем элементprint(h)m = heapq.heappop(h)  # Извлекаем минимальный элементprint(h, m)
```

  

```
[-10, 0, 5, 1, 12, 211, 43, 79]
[-10, 0, 5, 1, 12, 211, 43, 79, 2]
[0, 1, 5, 2, 12, 211, 43, 79] -10
```

  

Пробежимся коротенько по остальным структурам данных, которые в Python не имеют встроенной реализации, но, тем не менее, могут весьма пригодиться в реальном проекте.

  

### Би-дерево (B-tree)

  

Сбалансированное дерево, оптимизированное для доступа к относительно медленным элементам памяти (например, дисковым структурам или индексам баз данных); как ветви, так и листья представляют собой списки (для того, чтобы можно было считать такой список в один проход для дальнейшего быстрого разбора в ОЗУ). Нужно писать свою реализацию. Либо — воспользоваться встроенной в Python поддержкой базы данных sqlite3, эта БД как раз реализована на би-дереве.

  

### Красно-черное дерево

  

Самобалансирующееся двоичное дерево поиска, позволяющее быстро выполнять основные операции: добавление, удаление и поиск узла. Сбалансированность достигается за счёт введения дополнительного признака узла дерева — «цвета». Этот атрибут может принимать одно из двух возможных значений — «чёрный» или «красный». Листовые узлы КЧ деревьев не содержат данных, поэтому не требуют выделения памяти — достаточно просто записать в узле-предке нулевой указатель на потомка. Нужно писать свою реализацию.  
Возможно, вы читали о том, что при собеседовании в FAANG претендентов «заставляют крутить красно-черное дерево на доске». Это «кружение» и есть балансировка, после операции вставки или удаления элемента дерево нужно отбалансировать, с примерным объемом кода вы можете ознакомиться [здесь](https://blog.boot.dev/python/red-black-tree-python/) или [здесь](https://codereview.stackexchange.com/questions/244971/red-black-tree-implementation-in-python).

  

### АВЛ-дерево

  

В АВЛ-деревьях операции вставки и удаления работают медленнее, чем в красно-черных деревьях (при том же количестве листьев красно-чёрное дерево может быть выше АВЛ-дерева, но не более чем в 1,388 раза). Поиск же в АВЛ-дереве выполняется быстрее (максимальная разница в скорости поиска составляет 39 %). Нужно писать свою реализацию.

  

### Префиксное дерево

  

[Префиксное дерево](https://en.wikipedia.org/wiki/Trie) (или trie) — структура данных, позволяющая хранить ассоциативный массив, ключами которого являются строки. Нужно писать свою реализацию.

  

### Таблица выбора структуры данных

  

В квадратных скобках показан худший случай.

  

|Структура|Реализация|Применение|Индексация|Поиск|Вставка|Удаление|Память|
|---|---|---|---|---|---|---|---|
|Динамический массив|list||1|n|n|n|n|
|Хэш таблица|dict, set|||1  <br>[n]|1  <br>[n]|1  <br>[n]|n|
|Массив|array, bytes, bytearray|Для хранения однотипных данных|1|n|n|n|n|
|Односвязный список|— (~deque)||n|n|1|1|n|
|Двусвязный список|deque|FIFO, LIFO|n|n|1|1|n|
|Бинарное дерево|-||logn  <br>[n]|logn  <br>[n]|logn  <br>[n]|logn  <br>[n]|n|
|Куча|heapq|Очередь с приоритетом||1  <br>(find min)|logn|logn  <br>(del min)|n|
|B-tree (Би-дерево)|~sqlite|Для памяти с медленным доступом|logn|logn|logn|logn|n|
|КЧ дерево|-||logn|logn|logn|logn|n|
|АВЛ дерево|-||logn|logn|logn|logn|n|
|Префиксное дерево|-|T9,  <br>алгоритм [Ахо–Корасик](https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm),  <br>алгоритм [LZW](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch)||key|key|key||

  

### Перечисление (Enum, IntEnum)

  

Удобные конструкции для определения заранее известных перечислений.

  

```
from enum import Enum, autoimport randomclass Currency(Enum):    euro = 1    us_dollar = 2    yuan = auto()local_currency = Currency.us_dollarprint(local_currency)local_currency = Currency["us_dollar"]  # Может вызвать исключение KeyErrorprint(local_currency)local_currency = Currency(2)  # Может вызвать исключение ValueErrorprint(local_currency)print(local_currency.name)print(local_currency.value)list_of_members = list(Currency)member_names    = [e.name for e in Currency]member_values   = [e.value for e in Currency]random_member   = random.choice(list(Currency))print(list_of_members, "\n",      member_names, "\n",      member_values, "\n",      random_member)
```

  

```
Currency.us_dollar
Currency.us_dollar
Currency.us_dollar
us_dollar
2
[<Currency.euro: 1>, <Currency.us_dollar: 2>, <Currency.yuan: 3>] 
 ['euro', 'us_dollar', 'yuan'] 
 [1, 2, 3] 
 Currency.euro
```

  

### Целочисленный диапазон (range)

  

range() возвращает иммутабельную последовательность чисел, которая часто используется как задатчик диапазона для цикла for.

  

```
r1: range = range(11)  # Возвращает последовательность чисел от 0 до 10r2: range = range(5, 21) # Возвращает последовательность чисел от 5 до 20r3: range = range(20, 9, -2)  # Возвращает последовательность чисел от 20 до 10 с шагом 2print("To exclusive: ", end="")for i in r1:  print(f"{i} ", end="")print("\nFrom inclusive to exclusive: ", end="")for i in r2:  print(f"{i} ", end="")print("\nFrom inclusive to exclusive with step: ", end="")for i in r3:  print(f"{i} ", end="")print(f"\nFrom = {r3.start}")print(f"To = {r3.stop}")
```

  

```
To exclusive: 0 1 2 3 4 5 6 7 8 9 10 
From inclusive to exclusive: 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 
From inclusive to exclusive with step: 20 18 16 14 12 10 
From = 20
To = 9
```

  

### Классы данных (dataclass)

  

Декоратор, автоматически создающий методы init(), repr() и eq(). Нужен для создания классов, главной задачей которых является хранение данных. Аннотации типов обязательны. Существует более продвинутая альтернатива под названием [attrs](https://pypi.org/project/attrs/).

  

```
from dataclasses import dataclassfrom decimal import *from datetime import datetime@dataclassclass Transaction:    value: Decimal    issuer: str = "Default Bank"    dt: datetime = datetime.now()t1 = Transaction(value=1000_000, issuer="Deutsche Bank", dt = datetime(2022, 1, 1, 12))t2 = Transaction(1000)print(t1)print(t2)
```

  

```
Transaction(value=1000000, issuer='Deutsche Bank', dt=datetime.datetime(2022, 1, 1, 12, 0))
Transaction(value=1000, issuer='Default Bank', dt=datetime.datetime(2022, 9, 6, 17, 50, 36, 162897))
```

  

Dataclass может быть сделан иммутабельным с директивой _frozen=True_.

  

```
from dataclasses import dataclass@dataclass(frozen=True)class User:    name: str    account: int
```

  

### Бинарная запаковка (struct)

  

Запаковка (и распаковка, разумеется) данных в байтовые последовательности с предопределенными размерами каждого элемента данных, их порядка в структуре, а также порядка байт для многобайтовых типов данных. Позволяет превращать Python-овский int в, например, short int или long int ([подробности про систему типов языка Си](https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%B0_%D1%82%D0%B8%D0%BF%D0%BE%D0%B2_%D0%A1%D0%B8)).  
При работе со структурами вам нужно будет знать, что такое little-endian и big-endian, а также не забывать, что размер типа данных в Си бывает разным.

  

```
from struct import pack, unpack, iter_unpackb = pack(">hhll", 1, 2, 3, 4)print(b)t = unpack(">hhll", b)print(t)i = pack("ii", 1, 2) * 5print(i)print(list(iter_unpack('ii', i)))
```

  

```
b'\x00\x01\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04'
(1, 2, 3, 4)
b'\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00'
[(1, 2), (1, 2), (1, 2), (1, 2), (1, 2)]
```

  

## Строка (string)

  

Строки в Python 3 — иммутабельные последовательности, использующие кодировку Unicode.

  

```
se: str = ""  # Пустая строкаsi: str = str(12345)  # Создает строку из числаsj: str = " ".join(["Follow", "the", "white", "rabbit"])  # Собирает строку из кусочков, используя указанный сепараторprint(f"Joined string: {sj}")is_contains: bool = "rabbit" in sj  # Проверка наличия подстрокиis_startswith = sj.startswith("Foll")is_endswith = sj.endswith("bbit")print(f"is_contains = {is_contains}, is_startswith = {is_startswith}, is_endswith = {is_endswith}")sr: str  = sj.replace("rabbit", "sheep")  # Замена подстроки. Можно указать количество замен: sr: str  = sj.replace("rabbit", "sheep", times)print(f"After replace: {sr}")i1 = sr.find("rabbit")  # Возвращает стартовый индекс первого вхождения или -1. Есть еще rfind(), начинающий искать с конца строкиi2 = sr.index("sheep")  #  Возвращает стартовый индекс первого вхождения или выкидывает ValueError. Есть еще rindex(), начинающий искать с конца строкиprint(f"Start index of 'rabbit' is {i1}, start index of 'sheep' is {i2}")d = str.maketrans({"a" : "x", "b" : "y", "c" : "z"})st  = "abc".translate(d)print(f"Translate string: {st}")sr = sj[::-1]  # Реверс через slice с отрицательным шагомprint(f"Reverse string: {sr}")
```

  

```
Joined string: Follow the white rabbit
is_contains = True, is_startswith = True, is_endswith = True
After replace: Follow the white sheep
Start index of 'rabbit' is -1, start index of 'sheep' is 17
Translate string: xyz
Reverse string: tibbar etihw eht wolloF
```

  

### Datetime

  

Для работы с датами и временем в _datetime_ есть типы _date_, _time_, _datetime_ и _timedelta_. Все они хешируемы и иммутабельны.

  

### Конструкторы

  

```
from datetime import date, time, datetime, timedeltad: date = date(year=1964, month=9, day=2)t: time  = time(hour=12, minute=30, second=0, microsecond=0, tzinfo=None, fold=0)dt: datetime = datetime(year=1964, month=9, day=2, hour=10, minute=30, second=0)td: timedelta = timedelta(weeks=1, days=1, hours=12, minutes=13, seconds=14)print (f"{d}\n {t}\n {dt}\n {td}")
```

  

```
1964-09-02
 12:30:00
 1964-09-02 10:30:00
 8 days, 12:13:14
```

  

### Now

  

Получение текущей даты или даты/времени.

  

```
from datetime import date, datetimeimport pytzimport timed: date  = date.today()dt1: datetime = datetime.today()dt2: datetime = datetime.utcnow()dt3: datetime = datetime.now(pytz.timezone('US/Pacific'))t1 = time.time()  # Эпоха Unixt2 = time.ctime()print (f"{d}\n {dt1}\n {dt2}\n {dt3}\n {t1}\n {t2}")
```

  

```
2022-09-27
 2022-09-27 09:47:02.430474
 2022-09-27 04:47:02.430474
 2022-09-26 21:47:02.430474-07:00
 1664254022.4304743
 Tue Sep 27 09:47:02 2022
```

  

### Timezone

  

Часовые пояса.

  

```
from datetime import date, time, datetime, timedelta, tzinfofrom dateutil.tz import UTC, tzlocal, gettz, datetime_exists, resolve_imaginarytz1: tzinfo = UTC  # Часовой пояс UTCtz2: tzinfo = tzlocal()  # Местный часовой поясtz3: tzinfo = gettz()  # Местный часовой поясtz4: tzinfo = gettz("America/Chicago")  # Или, например, "Asia/Kolkata". Полный список: en.wikipedia.org/wiki/List_of_tz_database_time_zoneslocal_dt = datetime.today()utc_dt = local_dt.astimezone(UTC)  # Конвертация местного часового пояса в часовой пояс UTCprint (f"{tz1}\n {tz2}\n {tz3}\n {tz4}\n {local_dt}\n {utc_dt}")
```

  

```
tzutc()
 tzlocal()
 tzlocal()
 tzfile('US/Central')
 2022-09-27 09:19:35.399362
 2022-09-27 04:19:35.399362+00:00
```

  

## 2. Обработка данных

  

![Data management](https://habrastorage.org/r/w1560/getpro/habr/post_images/26e/edf/fd8/26eedffd8bf1081be95015e2dccecfc7.png)

  

### Срез (slice)

  

Самый простой метод обработки данных, просто возвращает ту часть данных, местоположение которой (индексы) удовлетворяет определенным условиям.

  

```
a:str = "Pack my box with five dozen liquor jugs"start, stop = 8, 21b:str = a[start:stop]  # Значения от start до stop-1c:str = a[start:]  # Значения от start до конца структурыd:str = a[:stop]  # Значения от начала до stop-1e:str = a[:]  # Полная копия структурыprint(b, "\n",      c, "\n",      d, "\n",      e, "\n")
```

  

```
box with five 
 box with five dozen liquor jugs 
 Pack my box with five 
 Pack my box with five dozen liquor jugs 
```

  

Значения start и stop могут быть отрицательными, это будет означать, что отсчет ведется от конца структуры. Можно также использовать значение step, чтобы на выход среза попали не все подряд данные из входной структуры.

  

```
a:str = "Step on no pets"b:str = a[-4:]  # «Хвостик»c:str = a[::-1]  # Реверс входной строкиd:str = a[4::-1]  # Первые четыре значения, реверсированыe:str = a[::2]  # Каждый второй символprint(b, "\n",      c, "\n",      d, "\n",      e, "\n")
```

  

```
pets 
 step on no petS 
  petS 
 Se nn es 
```

  

### Сортировка (sort, sorted)

  

В сортировке всё самое интересное спрятано под капотом (мы ненадолго вернемся к этой теме чуть ниже, в разделе «Алгоритмы»), пока рассмотрим только Python-специфичный синтаксис.  
Надо различать методы sort() и sorted(), первый сортирует данные [in-place](https://en.wikipedia.org/wiki/In-place_algorithm), второй порождает новую структуру.

  

```
a: list = [5, 2, 3, 1, 4]b: list = sorted(a)print(a, b)a.sort()print(a)
```

  

```
[5, 2, 3, 1, 4] [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
```

  

И sort(), и sorted() имеют параметр key для указания функции, которая будет вызываться на каждом элементе. Если вам больше по нраву сортировка при помощи функции, принимающей два аргумента (или вы привыкли к cmp в Python 2), присмотритесь к functools.cmp_to_key().

  

```
# Регистрозависимое сравнение строкdinos: str = "Dinosaurs were Big and small"a = sorted(dinos.split())print(a)# Регистронезависимое сравнение строкdinos: str = "Dinosaurs were Big and small"b = sorted(dinos.split(), key=str.lower)print(b)
```

  

```
['Big', 'Dinosaurs', 'and', 'small', 'were']
['and', 'Big', 'Dinosaurs', 'small', 'were']
```

  

Сложносочиненные структуры данных можно сортировать по key=lambda el: el[1] или даже, например по key=lambda el: (el[1], el[0]).

  

### Comprehension

  

Comprehension, которое переводится то как включение в список, то как абстракция списков ([Википедия](https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BA%D0%BE%D0%B2%D0%BE%D0%B5_%D0%B2%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5)), то вообще никак не переводится — способ компактного описания операций обработки списков (а применительно к Python — еще и словарей, и множеств).

  

Проще говоря, если вам нужно получить из списка другой список, включающий только те значения, которые удовлетворяют какому-то определенному условию, или вычисляемые из первого списка по каким-то определенным правилам, то comprehension — претендент на решение этой задачи № 1.

  

```
# Примеры Comprehensiona = [i+1 for i in range(10)]  # listb  = {i for i in range(10) if i > 5}  # setc = (2*i+5 for i in range(10))  # iterd = {i: i**2 for i in range(10)}  # dictprint(a,"\n", b, "\n", list(c), "\n", d)
```

  

```
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
 {8, 9, 6, 7} 
 [5, 7, 9, 11, 13, 15, 17, 19, 21, 23] 
 {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
```

  

Тут главное не перегнуть палку. Если запись comprehension становится слишком сложной и нечитаемой, возможно, стоит развернуть логику в «нормальный» цикл или в другой более удобочитаемый алгоритм. Comprehension соблазняет записывать «однострочниками» достаточно сложные выражения, но не забывайте, что программист примерно 90 % времени читает код, и только 10 % пишет, так что если выражение будет плохочитаемым, вы усложните жизнь и себе, и свои коллегам.

  

Есть более-менее [удачные](https://leetcode.com/problems/flipping-an-image/discuss/2378360/python-1-liner-988-speed-97-mem) «однострочники», есть быстрые, но [плохочитаемые](https://leetcode.com/problems/reverse-string-ii/discuss/2281269/python-fast-beats-984-and-short-almost-1-line-solution-with-python-38-features-pep572), написанные из спортивного интереса (это ссылки на решенные мной задачки на leetcode), желательно использовать comprehension в меру; лучше написать понятный развернутый алгоритм, чем непонятный, но обложенный пояснениями (если нет особых требований к производительности, само собой).

  

Еще немного про list comprehension:

  

```
# new_list = [expression for member in iterable (if conditional)]fruits: list = ["Lemon", "Apple", "Banana", "Kiwi", "Watermelon", "Pear"]e_fruits = [fruit for fruit in fruits if "e" in fruit]#                                     ☝ условиеprint(e_fruits)upper_fruits = [fruit.upper() for fruit in fruits]#                     ☝ выражениеprint(upper_fruits)# Пример разбиения списка на фрагменты одинаковой длиныchunk_len = 2chunk_fruits = [fruits[i:i + chunk_len] for i in range(0, len(fruits), chunk_len)]print(chunk_fruits)
```

  

```
['Lemon', 'Apple', 'Watermelon', 'Pear']
['LEMON', 'APPLE', 'BANANA', 'KIWI', 'WATERMELON', 'PEAR']
[['Lemon', 'Apple'], ['Banana', 'Kiwi'], ['Watermelon', 'Pear']]
```

  

Dict comprehension, включение в словарь:

  

```
# new_dict = {expression for member in iterable (if conditional)}d: dict = {"Italy": "Pizza", "US": "Hot-Dog", "China": "Dim Sum", "South Korea": "Kimchi"}print(d)a: dict = {k: v for k, v in d.items() if "i" in v}  # Вернет новый словарь, отфильтрованный по значениюprint(a)b: dict = {k: v for k, v in d.items() if "i" in k}  # Вернет новый словарь, отфильтрованный по ключуprint(b)c: dict = {k: v for k, v in d.items() if len(v) >= 7}  # Вернет новый словарь, отфильтрованный по длине значенийprint(c)
```

  

```
{'Italy': 'Pizza', 'US': 'Hot-Dog', 'China': 'Dim Sum', 'South Korea': 'Kimchi'}
{'Italy': 'Pizza', 'China': 'Dim Sum', 'South Korea': 'Kimchi'}
{'China': 'Dim Sum'}
{'US': 'Hot-Dog', 'China': 'Dim Sum'}
```

  

Попробуйте самостоятельно поиграться с set comprehension. Не забывайте, что set «переваривает» только уникальные значения, поэтому в результате вы можете получить не совсем то, на что рассчитывали.

  

Попробуйте также освоить nested (вложенный) comprehension, используя конструкции вида [[func(y) for y in x] for x in n]. Для примера создайте двумерный массив, содержащий случайные значения, среднее значение которых плавно нарастает ближе к правому нижнему углу (если не получится, готовый пример есть чуть ниже, в коде, иллюстрирующем применение matplotlib).

  

### Операции над строками. lower(), upper(), capitalize() и title()

  

```
s: str = "camelCase string"print(s.lower())print(s.upper())print(s.capitalize())print(s.title())
```

  

```
camelcase string
CAMELCASE STRING
Camelcase string
Camelcase String
```

  

### strip()

  

```
s: str = "  ~~##A big blahblahblah##~~  "s = s.strip()  # Strips all whitespace characters from both endsprint(s)s = s.strip("~#")  # Strips all passed characters from both endsprint(s)s = s.lstrip(" A")  # Strips all passed characters from left endprint(s)s = s.rstrip("habl")  # Strips all passed characters from right endprint(s)
```

  

```
~~##A big blahblahblah##~~
A big blahblahblah
big blahblahblah
big 
```

  

### split()

  

```
s1: str = "Follow the white rabbit, Neo"c1 = s1.split()  # Splits on one or more whitespace charactersprint(c1)c2 = s1.split(sep=", ", maxsplit=1)  # Splits on "sep" str at most "maxsplit" timesprint(c2)s2: str = "Beware the Jabberwock, my son!\n The jaws that bite, the claws that catch!"c3 = s2.splitlines(keepends=False)  # On [\n\r\f\v\x1c-\x1e\x85\u2028\u2029] and \r\n.print(c3)# split() vs rsplit()c4 = s2.split(maxsplit=2)c5 = s2.rsplit(maxsplit=2)print(c4, c5)
```

  

```
['Follow', 'the', 'white', 'rabbit,', 'Neo']
['Follow the white rabbit', 'Neo']
['Beware the Jabberwock, my son!', ' The jaws that bite, the claws that catch!']
['Beware', 'the', 'Jabberwock, my son!\n The jaws that bite, the claws that catch!'] ['Beware the Jabberwock, my son!\n The jaws that bite, the claws', 'that', 'catch!']
```

  

### ord(), chr()

  

```
s1: str = "abcABC!"for ch in s1:    print(f"{ch} -> {ord(ch)}")  # Returns an integer representing the Unicode characternums = [72, 101, 108, 108, 111, 33]for num in nums:    print(f"{num} -> {chr(num)}")
```

  

```
a -> 97
b -> 98
c -> 99
A -> 65
B -> 66
C -> 67
! -> 33
72 -> H
101 -> e
108 -> l
108 -> l
111 -> o
33 -> !
```

  

### Regex

  

Регулярные выражения — отдельная область знаний, и весьма-весьма непростая область. Тут, пожалуй, самое время для бородатой шутки про то, что если вы решили свою проблему при помощи регулярных выражений — теперь у вас две проблемы.

  

Регулярки похожи на вхождение в воду на пляже острова Гуам в сторону Марианской впадины — даже когда вы думаете, что погрузились _реально_ глубоко, то, скорее всего, вы просто не видите впередилежащей бездны. Но — знать регулярные выражения, хотя бы на начальном уровне, необходимо для решения целого класса задач, а то, что вёрткие регулярки периодически поворачиваются к вам своими, кхм… новыми гранями, придется простить, переварить и принять.

  

Вот [здесь](https://habr.com/ru/post/349860/) есть грамотное и методически выдержанное введение в тему, пока же окинем взглядом основные возможности регулярных выражений:

  

```
import res1: str = "123 abc ABC 456"m1 = re.search("[aA]", s1)  # Ищет первое вхождение паттерна, при неудаче возвращает Noneprint(m1, m1.group(0))m2 = re.fullmatch("[aA]", s1)  # Проверка, подходит ли строка под шаблонprint(m2)c1: list = re.findall("[aA]", s1)  # Найти в строке все непересекающиеся шаблоныprint(c1)def replacer(s):    return chr(ord(s[0]) + 1)  # Следующий символ из алфавитаs2 = re.sub("\w", replacer, s1)  # Вы можете использовать функцию вместо шаблонаprint(s2)c2 = re.split("\d", s1)print(c2)iter = re.finditer("\D", s1)  # Итератор по непересекающимся шаблонамfor ch in iter:    print(ch.group(0), end= "")
```

  

```
<re.Match object; span=(4, 5), match='a'> a
None
['a', 'A']
234 bcd BCD 567
['', '', '', ' abc ABC ', '', '', '']
 abc ABC 
```

  

### Match Object

  

```
import rem3 = re.match(r"(\w+) (\w+)", "John Connor, leader of the Resistance")s3: str = m3.group(0)  # Возвращает полное совпадениеs4: str = m3.group(1)  # Возвращает часть в первых скобкахt1: tuple = m3.groups()start: int = m3.start()  # Возвращает начальный индекс совпаденияend: int = m3.end()  # Возвращает конечный индекс совпаденияt2: tuple[int, int] = m3.span()  # Кортеж (start, end)print (f"{s3}\n {s4}\n {t1}\n {start}\n {end}\n {t2}\n")
```

  

```
John Connor
 John
 ('John', 'Connor')
 0
 11
 (0, 11)
```

  

### Создание переменных datetime

  

Python использует Unix Epoch: "1970-01-01 00:00 UTC"

  

```
from datetime import datetimefrom dateutil.tz import tzlocaldt1: datetime = datetime.fromisoformat("2021-10-04 00:05:23.555+00:00")  # Может вызвать ValueErrordt2: datetime = datetime.strptime("21/10/04 17:30", "%d/%m/%y %H:%M")   # Подробнее про форматы - https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codesdt3: datetime = datetime.fromordinal(100_000)  # 100000-й день от 1.1.0001dt4: datetime = datetime.fromtimestamp(20_000_000.01)  # Время в секундах с начала Unix Epochtz = tzlocal()dt5: datetime = datetime.fromtimestamp(20_000_000.01, tz)  # С учетом часового поясаprint (f"{dt1}\n {dt2}\n {dt3}\n {dt4}\n {dt5}")
```

  

```
2021-10-04 00:05:23.555000+00:00
 2004-10-21 17:30:00
 0274-10-16 00:00:00
 1970-08-20 16:33:20.010000
 1970-08-20 16:33:20.010000+05:00
```

  

### Преобразование переменных datetime

  

```
from datetime import datetimedt1: datetime = datetime.today()s1: str = dt1.isoformat()s2: str = dt1.strftime("%d/%m/%y %H:%M")  # https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codesi: int = dt1.toordinal()a: float = dt1.timestamp()  # Секунды с начала Unix Epochprint (f"{dt1}\n {s1}\n {s2}\n {i}\n {a}")
```

  

```
2022-09-06 17:50:38.041159
 2022-09-06T17:50:38.041159
 06/09/22 17:50
 738404
 1662468638.041159
```

  

### Арифметика datetime

  

```
from datetime import date, time, datetime, timedeltafrom dateutil.tz import UTC, tzlocal, gettz, datetime_exists, resolve_imaginaryd: date  = date.today()dt1: datetime = datetime.today()dt2: datetime = datetime(year=1981, month=12, day=2)td1: timedelta = timedelta(days=5)td2: timedelta = timedelta(days=1)d = d + td1  # date = date ± timedeltadt3 = dt1 - td1  # datetime = datetime ± timedeltatd3 = dt1 - dt2  # timedelta = datetime - datetimetd4 = 10 * td1  # timedelta = const * timedeltac: float = td1/td2  # timedelta/timedeltaprint (f"{d}\n {dt3}\n {td3}\n {td4}\n {c}")
```

  

```
2022-09-11
 2022-09-01 17:50:38.132916
 14888 days, 17:50:38.132916
 50 days, 0:00:00
 5.0
```

  

### bisect и бинарный поиск

  

Бинарный поиск существенно быстрее, чем обычный (см. раздел «Алгоритмы»), но требует предварительной сортировки коллекции, по которой осуществляется поиск.

  

```
import bisecta: list[int] = [12, 6, 8, 19, 1, 33]a.sort()print(f"Sorted: {a}")print(bisect.bisect(a, 20))  # Найти индекс для потенциальной вставкиbisect.insort(a, 15)  # Вставка значения в отсортированную последовательностьprint(a)# Бинарный поискdef binary_search(a, x, lo=0, hi=None):    if hi is None:        hi = len(a)    pos = bisect.bisect_left(a, x, lo, hi)    return pos if pos != hi and a[pos] == x else -1print(binary_search(a, 15))
```

  

```
Sorted: [1, 6, 8, 12, 19, 33]
5
[1, 6, 8, 12, 15, 19, 33]
4
```

  

### Функциональное программирование (Map, Filter, Reduce, Partial)

  

На случай, если начиная с этого момента и до конца текущего жизненного цикла вы собираетесь к месту и не месту использовать приёмы функционального программирования, чтобы сделать свой код «воистину крутым», просто процитирую вам Джоэля Граса, автора книги «Data Science: Наука о данных с нуля»: «В первом издании этой книги были представлены функции partial, map, reduce и filter языка Python. На своем пути к просветлению я понял, что этих функций лучше избегать, и их использование в книге было заменено включениями в список, циклами и другими, более Python'овскими конструкциями». Такие дела…

  

```
import functools# Преобразует все входящие значения при помощи указанной функцииiter1 = map(lambda x: x + 1, range(10))print(list(iter1))# Передает в выходной итератор только значения, удовлетворяющие условиюiter2 = filter(lambda x: x > 5, range(10))print(list(iter2))# Применяет указанную функцию ко всей последовательности входных данных, сводя их к единственному значениюa = functools.reduce(lambda out, x: out + x, range(10))print(a)
```

  

```
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[6, 7, 8, 9]
45
```

  

```
import functoolsdef sum(a,b):    return a + badd_const = functools.partial(sum, 10)print(add_const(5))
```

  

```
15
```

  

Если вам не сразу станет понятно, как работает функция partial (и зачем она нужна), не расстраивайтесь, вы не одиноки :). Вот, пожалуйста, тема на Stackoverflow: «[I am not able to get my head on how the partial works](https://stackoverflow.com/questions/15331726/how-does-functools-partial-do-what-it-does)». Там, кстати, есть совет, как partial могут быть полезны при организации pipe с включением функций, имеющих разное количество аргументов.

  

### Any, All

  

any() вернет True, если хотя бы один элемент итерируемой коллекции истинен, all() вернет True только в случае истинности всех элементов коллекции.

  

```
animals = ["Squirrel", "Beaver", "Fox"]sentence = "Bison likes squirrels and beavers"any_animal: bool = any(animal.lower() in sentence.lower() for animal in animals)print(any_animal)all_animal: bool = all(animal.lower() in sentence.lower() for animal in animals)print(all_animal)
```

  

```
True
False
```

  

### Файлы

  

Файловые операции стоят немного особняком от остальных методов обработки данных, как подразумевающие не сиюминутную торопливую обработку информации, а взаимодействие с неким постоянным энергонезависимым хранилищем данных. Так что если вам нужно сохранить данные _на завтра_, или, наоборот, нужно прочитать данные, которые вам предоставили _неделю назад_, то вам, очевидно, нужно будет работать с файлами. В файлах же осядет информация, которую мы передаем базам данных, но эту тему мы рассмотри ниже.

  

```
f = open("f.txt", mode='r', encoding="utf-8", newline=None)print(f.read())
```

  

```
Hello from file!
```

  

На всякий случай, если вы испытываете программистский зуд даже небольшой степени выраженности, напоминаю — обязательно прогоняйте в IDE все непонятные куски кода, не надо на них _смотреть_, их надо видоизменять, корректировать, дорабатывать; только когда концы свяжутся, только когда вы поймете, как функционирует этот кусочек кода, только тогда промелькнёт маленькая искорка и ваша квалификация как программиста немного подрастёт.

  

Режимы (mode):  
"r" — чтение (поведение по умолчанию)  
"w" — запись (информация, ранее присутствующая в файле, будет стёрта)  
"x" — эксклюзивное создание и запись; если файл уже существует, будет выброшено исключение FileExistsError  
"a" — открытие с последующим добавлением в конец файла  
"w+" — чтение и запись  
"r+" — чтение и запись с начала файла  
"a+" — чтение и запись с конца файла  
"t" — текстовый режим ("rt", "wt" и т. д.; поведение по умолчанию)  
"b" — двоичный режим ("rb", "wb", "xb" и т. д.)

  

encoding=None — будет использована кодировка по умолчанию (зависит от системы, см. getpreferredencoding()). Если нет специальных требований, просто используйте везде encoding="utf-8"; без этого, например, русский текст запишется в текстовый файл в виде человеконечитаемой последовательности.

  

newline=None — при чтении системные символы конца строки будут конвертированы в "\n"; при записи, наоборот, "\n" будут конвертированы в системные символы конца строки.

  

Возможные исключения при работе с файлами:  
_FileNotFoundError_ при чтении в режиме "r" или "r+".  
_FileExistsError_ при записи в режиме "x".  
_IsADirectoryError_, _PermissionError_ — в любом режиме.

  

### Чтение из файла

  

Открывает файл и возвращает файловый объект.  
Для работы с файлами лучше использовать менеджеры контекста (рассмотрены ниже), т. е. конструкции вида "with open...". Даже если что-то пойдет не так, как задумано (например, вы не обработаете исключение во время работы с файлом), менеджер контекста «зачистит хвосты», и ваша оплошность не отразится, например, на файловой системе.

  

```
with open("f.txt", encoding="utf-8") as f:    chars = f.read(5)  # Reads chars/bytes or until EOF    print(chars)    f.seek(0)  # Moves to the start of the file. Also seek(offset) and seek(±offset, anchor), where anchor is 0 for start, 1 for current position and 2 for end    lines: list[str] = f.readlines()  # Also readline()    print(lines)
```

  

```
Hello
['Hello from file!']
```

  

### Запись в файл

  

```
with open("f.txt", "w", encoding="utf-8") as f:    f.write("Hello from file!")  # Или f.writelines(<collection>)
```

  

### JSON

  

Человекочитаемый формат для хранения и передачи данных.

  

```
import jsond: dict = {1: "Lemon", 2: "Apple", 3: "Banana!"}object_as_string: str = json.dumps(d, indent=2)print(object_as_string)restored_object = json.loads(object_as_string)# Write object to JSON filewith open("1.json", 'w', encoding='utf-8') as file:    json.dump(d, file, indent=2)# Read object from JSON filewith open("1.json", encoding='utf-8') as file:    restored_from_file = json.load(file)print(restored_from_file)
```

  

```
{
  "1": "Lemon",
  "2": "Apple",
  "3": "Banana!"
}
{'1': 'Lemon', '2': 'Apple', '3': 'Banana!'}
```

  

### Pickle

  

Бинарный формат для хранения и передачи данных.

  

```
import pickled: dict = {1: "Lemon", 2: "Apple", 3: "Banana!"}# Запись объекта в бинарный файлwith open("1.bin", "wb") as file:    pickle.dump(d, file)# Чтение объекта из файлаwith open("1.bin", "rb") as file:    restored_from_file = pickle.load(file)print(restored_from_file)
```

  

```
{1: 'Lemon', 2: 'Apple', 3: 'Banana!'}
```

  

### Protocol Buffers

  

Если вы хотите передавать и хранить данные, используя универсальную структуру, одинаково хорошо понимаемую всеми языками программирования (как JSON) и занимающую мало места (как Pickle), то можно посмотреть в сторону Protocol Buffers ([Wikipedia](https://en.wikipedia.org/wiki/Protocol_Buffers), [примеры для Python](https://developers.google.com/protocol-buffers/docs/pythontutorial)). Есть еще альтернативы, например, [FlatBuffers](https://google.github.io/flatbuffers/), [Apache Avro](https://avro.apache.org/) или [Thrift](https://thrift.apache.org/).

  

### Пути (Paths)

  

При работе с файлами не обойтись без манипулирования файловыми путями.

  

```
from os import getcwd, path, listdirfrom pathlib import Paths1: str = getcwd()  # Возвращает текущую рабочую директориюprint(s1)s2: str = path.abspath("f.txt")  # Возвращает полный путьprint(s2)s3: str = path.basename(s2)  # Возвращает имя файлаs4: str = path.dirname(s2)  # Возвращает путь без файлаt1: tuple = path.splitext(s2)  # Возвращает кортеж из пути и имени файлаprint(s3, s4, t1)p = Path(s2)st = p.stat()print(st)b1: bool = p.exists()b2: bool = p.is_file()b3: bool = p.is_dir()print(b1, b2, b3)c: list = listdir(path=s1)  # Возвращает список имен файлов, находящихся по указанному путиprint(c)s5: str = p.stem  # Возвращает имя файла без расширенияs6: str  = p.suffix  # Возвращает расширение файлаt2: tuple = p.parts  # Возвращает все элементы пути как отдельные строкиprint(s5, s6, t2)
```

  

```
c:\Works\amaargiru\pycore
c:\Works\amaargiru\pycore\f.txt
f.txt c:\Works\amaargiru\pycore ('c:\\Works\\amaargiru\\pycore\\f', '.txt')
os.stat_result(st_mode=33206, st_ino=2251799814917120, st_dev=3628794147, st_nlink=1, st_uid=0, st_gid=0, st_size=16, st_atime=1662468638, st_mtime=1662468638, st_ctime=1661089564)
True True False
['.git', '.gitignore', '.pytest_cache', '01_python.ipynb', '01_python.md', '02_postgre.md', '03_architecture.md', '04_algorithms.ipynb', '04_algorithms.md', '05_admin_devops.md', '06_pytest_mock.ipynb', '06_pytest_mock.md', '07_fastapi.md', '08_flask.md', '1.bin', '1.json', 'compose_readme.bat', 'coupling_vs_cohesion.svg', 'f.txt', 'gitflow.svg', 'graph_for_dfs.jpg', 'pycallgraph3.png', 'readme.md']
f .txt ('c:\\', 'Works', 'amaargiru', 'pycore', 'f.txt')
```

  

### Простейшие вычисления — Sum, Count, Min, Max

  

```
a: list[int] = [1, 2, 3, 4, 5, 2, 2]s = sum(a)print(s)c = a.count(2)  # Вернет количество вхожденийprint(c)mn = min(a)print(mn)mx = max(a)print(mx)
```

  

```
19
3
1
5
```

  

Присмотритесь к [встроенным функциям](https://docs.python.org/3/library/functions.html), там есть еще кое-что, касающееся элементарной математики.

  

### Базовая математика

  

```
from math import pia: float = pi ** 2  # Or pow(pi, 2)print(f"Power: {a}")b: float = round(pi, 2)print(f"Round: {b}")c: int = round(256, -2)print(f"Int round: {c}")d: float = abs(-pi)print(f"Abs: {d}")e: float = abs(10+10j)  # Or e: float = abs(complex(real=10, imag=10))print(f"Complex abs: {e}")
```

  

```
Power: 9.869604401089358
Round: 3.14
Int round: 300
Abs: 3.141592653589793
Complex abs: 14.142135623730951
```

  

### Побитовые операции

  

```
a: int = 0b01010101b: int = 0b10101010print(f"And: 0b{a&b:08b}")print(f"Or: 0b{a|b:08b}")print(f"Xor: 0b{a^b:08b}")print(f"Left shift: 0b{a << 4:08b}")print(f"Right shift: 0b{b >> 4:08b}")print(f"Not: 0b{~a:08b}")
```

  

```
And: 0b00000000
Or: 0b11111111
Xor: 0b11111111
Left shift: 0b10101010000
Right shift: 0b00001010
Not: 0b-1010110
```

  

### Подсчет битов

  

```
a: int = 4242print(f"{a} in binary format: 0b{a:b}")c = a.bit_count()  # Returns the number of ones in the binary representation of the absolute value of the integerprint(f"Bit count: {c}")
```

  

```
4242 in binary format: 0b1000010010010
Bit count: 4
```

  

### Fractions

  

```
from fractions import Fractionf = Fraction("0.2").as_integer_ratio()print(f)
```

  

```
(1, 5)
```

  

### Евклидово расстояние между двумя точками

  

```
import mathp1 = (0.22, 1, 12)p2 = (-0.12, 3, 7)print(math.dist(p1, p2))
```

  

```
5.39588732276722
```

  

### NumPy

  

Мини-язык для манипулирования массивами. На удачных сценариях работает в сотни раз быстрее встроенных функций. Еще более быстрая альтернатива работает на GPU, называется [CuPy](https://github.com/cupy/cupy) и опять-таки [обещает](https://medium.com/rapids-ai/single-gpu-cupy-speedups-ea99cbbb0cbb) стократный прирост производительности, только уже по сравнению с NumPy. Так что если вам нужен какой-нибудь быстрый [FFT](https://en.wikipedia.org/wiki/Fast_Fourier_transform) или еще какой числогрыз, то вы знаете, что делать. Если вы дружите с английским, то изучайте [официальный мануал](https://numpy.org/doc/stable/), если нет — на «Хабре» есть [перевод](https://habr.com/ru/post/352678/) (как всегда, читайте комментарии, там немало полезного).

  

Небольшое отступление.

  

Во-первых, тут мы переходим границу между встроенной функциональность языка и внешними библиотеками. Надо понимать, что успех Python во многом основан именно на богатстве его экосистемы (хотя, впрочем, то же самое можно сказать и про JavaScript, и про C#); сам язык предоставляет богатую, но всё же ограниченную функциональность, в то время как функционал внешних библиотек практически безграничен; это как бесконечно разнообразные кубики Лего. Соответственно, очень часто для решения задачи не нужно реализовывать алгоритм с нуля на чистом Python'е, достаточно подобрать нужную библиотеку.

  

Во-вторых, популярность разных библиотек Python (в том числе и конкурирующих) сильно разнится. Например, NumPy — очень популярная библиотека, но в мире существуют буквально миллионы Python-разработчиков, которые _никогда_ не работали с NumPy, просто в силу своего круга функциональных обязанностей.

  

Для начинающего разработчика это представляет собой довольно нешуточную проблему — как конкретно двигаться вперед, какие библиотеки изучать, ведь знания чистого Python, как правило, недостаточно для формирования актуального резюме.

  

Дам вам небольшой совет. Ежегодно компания JetBrains (делающая среди прочего очень классную IDE PyCharm) проводит всемирный опрос Python-разработчиков, а потом выкладывает полученные результаты в виде так называемого [Python Developers Survey Results](https://lp.jetbrains.com/python-developers-survey-2021/). Например, если вы почитаете результаты последнего исследования, то найдете там довольно чёткие ориентиры: скажем, в разделе «Data science frameworks and libraries» в топе находятся NumPy, Pandas (рассмотрен ниже) и Matplotlib; в тестировании с большим отрывом лидирует pytest (смотри ниже), в других областях вперед вырываются Flask (Django на втором месте с крошечным отрывом), SQLAlchemy (vs Django ORM) и PostgreSQL (vs SQLite), про них мы тоже еще поговорим. Так что в целом, общее направление развития определить можно.

  

Однако, вернемся к NumPy. Не забывайте, что в основе NumPy лежат массивы, а все данные в массиве должны быть одинакового типа (просто на случай, если вы уже познали пр-р-рел-л-лесть списков Python). Создание массивов:

  

```
import numpy as npa1 = np.array([1, 2, 3, 4, 5], float)  # Получение массива из спискаprint(a1[0:2])a2 = np.zeros(5)  # Массив, заполненный нулямиprint(a2)a3 = np.arange(0, 6, 1)  # Использование диапазона, np.arange(from_inclusive, to_exclusive, step_size)print(a3)a4 = np.random.randint(6, size=10)  # Создание массива, содержащего случайные значения, np.random.randint(low_inclusive, high_exclusive=None, size=None, dtype=int)print(a4)a5 = np.random.randint(6, size=(2, 5))  # Создание многомерного массива, содержащего случайные значенияprint(a5)print(a5.shape)  # Число строк и столбцов в массивеprint(a5.dtype)  # Тип переменныхprint(1 in a5)  # Проверка наличия элемента
```

  

```
[1. 2.]
[0. 0. 0. 0. 0.]
[0 1 2 3 4 5]
[2 2 4 0 0 0 0 4 0 5]
[[1 0 3 5 0]
 [3 1 4 2 2]]
(2, 5)
int32
True
```

  

Базовые математические операции ([полный список](https://numpy.org/doc/stable/reference/routines.math.html)):

  

```
import numpy as npa1 = np.array([1, 2, 3, 4, 5])a2 = np.array([6, 7, 8, 9, 10])a3 = a1 + 1print(a3)a4 = a1 + a2print(a4)a5 = a1 ** 3print(a5)a6 = a1 ** a2print(a6)
```

  

```
[2 3 4 5 6]
[ 7  9 11 13 15]
[  1   8  27  64 125]
[      1     128    6561  262144 9765625]
```

  

Вообще, можно сказать, что быстрые математические операции над многомерными массивами — это главная «фишка» NumPy. Вы просто говорите: возьми такие-то массивы и проделай над ними такую-то операцию. Далее все эти данные «проваливаются» в высокоскоростное ядро NumPy, где к ним уже можно применить всю мощь вашего процессора, которая раньше была вам недоступна (ну, или доступна не полностью) из-за ограничений Python-интерпретатора. Так что, если вы пытаетесь в цикле итерировать массив NumPy, по факту передавая данные на нижний уровень небольшими порциями (например, объектами row), то имейте в виду, что тем самым используете возможности NumPy недостаточно эффективно; попробуйте решить задачу без итерирования.

  

### Sum, Min, Max

  

```
import numpy as npa1 = np.random.randint(6, size=(2, 10))  # NumPy поддерживает несколько десятков видов распределений, например, Пуассона и Стьюдентаprint(a1)s = np.sum(a1)  # Сумма всех элементовprint(s)mn = a1.min(axis=0)  # Наименьшие числа в каждом столбцеprint(mn)mx = a1.max(axis=1)  # Наибольшие числа в каждой строкеprint(mx)amin = a1.argmin(axis=0)  # Индексы минимальных элементов в каждом столбцеprint(amin)amax = a1.argmax(axis=1)  # Индексы максимальных элементов в каждой строкеprint(amax)uniq = np.unique(a1)  # Извлечение уникальных элементовprint(uniq)
```

  

```
[[3 0 4 1 0 5 4 0 1 3]
 [0 3 4 0 0 1 4 0 5 4]]
42
[0 0 4 0 0 1 4 0 1 3]
[5 5]
[1 0 0 1 0 1 0 0 0 0]
[5 8]
[0 1 3 4 5]
```

  

В качестве домашнего задания попробуйте самостоятельно применить prod(), mean(), var(), std(), median(), cov() и corrcoef().

  

Форматирование массивов:

  

```
import numpy as npa = np.random.randint(6, size=(3, 5))print(a)a1 = a.reshape((5, 3))  # Форматирование. Если есть возможность, создается новый view на те же самые данныеprint(a1)a.shape = (5, 3)  # Форматирование in-placeprint(a)print(a.shape)a = a[:, :, np.newaxis]  # Увеличение размерности массива с 2 до 3print(a)print(a.shape)a = a.flatten()  # Конвертация в одномерный массивprint(a)print(a.shape)
```

  

```
[[5 5 5 1 1]
 [0 2 0 5 5]
 [0 2 5 4 5]]
[[5 5 5]
 [1 1 0]
 [2 0 5]
 [5 0 2]
 [5 4 5]]
[[5 5 5]
 [1 1 0]
 [2 0 5]
 [5 0 2]
 [5 4 5]]
(5, 3)
[[[5]
  [5]
  [5]]

 [[1]
  [1]
  [0]]

 [[2]
  [0]
  [5]]

 [[5]
  [0]
  [2]]

 [[5]
  [4]
  [5]]]
(5, 3, 1)
[5 5 5 1 1 0 2 0 5 5 0 2 5 4 5]
(15,)
```

  

Копирование массивов:

  

```
import numpy as npimport copya = np.random.randint(10, size=(4, 4))print(a)# Неглубокая (shallow) копияa1 = np.copy(a)# Глубокая (deep) копияa2 = copy.deepcopy(a)# Копирование ссылкиa3 = aa[0, 0] = 10print(a[0, 0] == a1[0, 0])print(a[0, 0] == a2[0, 0])print(a[0, 0] == a3[0, 0])
```

  

```
[[5 3 1 4]
 [0 8 7 0]
 [1 7 4 7]
 [5 3 5 2]]
False
False
True
```

  

NumPy очень мощный инструмент, не зря же он стоит на первом месте в списке «Data science frameworks and libraries» обзора, который мы упоминали чуть выше. Но углубляться в эту тему очень уж глубоко в рамках нашего достаточно поверхностного очерка, пожалуй, не стоит; вряд ли прямо сейчас вам кровь из носу нужно освоить скалярное, тензорное и внешнее произведение матриц или познать (вспомнить?) специфику линейной алгебры. Думаю, даже если мы сейчас начнем описывать транспонирование или выбор оси, по которой будет произведена конкатенация массивов, то это уже будет, что называется, «не в коня корм».  
К тому же, изучая тонкости употребления NumPy, начинает появляться соблазн упоминания SciPy, предоставляющего еще более широкий функционал, а после первого "import scipy" у нас начнётся уже полное непотребство. Давайте пока пройдем мимо этой кроличьей норы, для первого знакомства она слишком глубока.

  

Единственное, что еще можно освоить в конце ознакомительного курса NumPy — взаимодействие с внешним миром. Изучите для начала load/save/savez (бинарники) и loadtxt/savetxt (человекочитаемый формат).

  

### Pandas

  

Библиотека обработки и анализа данных. Работа с данными строится поверх библиотеки NumPy.  
В первом, грубом приближении pandas можно воспринимать как связку «Excel + VisualBasic-скрипты», только более гибкую и удобную. Библиотека создает своеобразный мостик между профессиями Python-программиста, дата-сайентиста и аналитика, позволяя сосредоточиться в большей степени именно на очистке и анализе данных, на читабельности отчетов, а не на программировании. Pandas также поддерживает широкий спектр «красивостей» при выводе информации, позволяя, например, добавлять в выводимые данные градиентную подсветку (heatmap) или визуализировать отклонение от среднего (bar chart).

  

Для того чтобы как следует «распробовать» pandas, по-хорошему надо загрузить какой-нибудь развесистый набор данных, но мы, пожалуй, не будем погружаться в глубины глубин, просто поиграем небольшим самодельным датасетом.

  

```
import pandas as pds = pd.Series([0, 1, 4, 7, 8, 10, 12])print(s)print(s[2])
```

  

```
0     0
1     1
2     4
3     7
4     8
5    10
6    12
dtype: int64
4
```

  

Series — базовая структура данных pandas. Вы можете воспринимать её как упорядоченный словарь или как строку Excel, смотря по тому, какая аналогия вам ближе.

  

```
import pandas as pds = pd.Series([0, 1, 4, 7, 8, 10, 12], index=["a", "b", "c", "d", "x", "y", "z"])  # Индексы Series можно задавать вручнуюprint(s)print(s["x"])print(s[["x", "y", "z"]])  # Выборкаprint(s[s > 5])  # Фильтрацияprint(s.max())  # Математика, примерно как в NumPyprint(s.sum())
```

  

```
a     0
b     1
c     4
d     7
x     8
y    10
z    12
dtype: int64
8
x     8
y    10
z    12
dtype: int64
d     7
x     8
y    10
z    12
dtype: int64
12
42
```

  

При объединении нескольких Series получается DataFrame, вторая базовая структура данных pandas, которую в первом приближении можно рассматривать как лист Excel.

  

```
import pandas as pdfrom pandas import DataFrames1 = pd.Series([0, 1, 4, 7, 8, 10, 12])s2 = pd.Series([0, 100, 200, 300, 600, 900, 1200])df = pd.DataFrame([s1, s2])print(df)print(df[1])print(df[2][0])print(df.iloc[0][2:4])
```

  

```
   0    1    2    3    4    5     6
0  0    1    4    7    8   10    12
1  0  100  200  300  600  900  1200
0      1
1    100
Name: 1, dtype: int64
4
2    4
3    7
Name: 0, dtype: int64
```

  

Давайте сделаем что-то более похожее на реальный анализ данных. При помощи формулы [ИМТ](https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%B4%D0%B5%D0%BA%D1%81_%D0%BC%D0%B0%D1%81%D1%81%D1%8B_%D1%82%D0%B5%D0%BB%D0%B0) выясним, кто из знаменитостей не следит за собой и обзавелся лишним весом:

  

```
from pandas import DataFrameimport matplotlib.pyplot as pltdef bmi(row):    return row["weight"] / row["height"] ** 2if __name__ == '__main__':    celebs: dict = {"Britney Spears": {"height": 1.63, "weight": 57},                    "Melanie Griffith": {"height": 1.73, "weight": 63},                    "Kylie Minogue": {"height": 1.52, "weight": 46},                    "Hulk Hogan": {"height": 1.98, "weight": 137}}    df = DataFrame(celebs)  # Создаем DataFrame    df.loc["bmi"] = df.apply(lambda row: bmi(row), axis=0)  # Добавлем новую строку с ИМТ    df = df.sort_values(by="bmi", ascending=True, axis=1)  # Сортируем    print(df)    df.loc["bmi"].plot.bar()  # Визуализация    plt.show()
```

  

```
        Kylie Minogue  Melanie Griffith  Britney Spears  Hulk Hogan
height       1.520000          1.730000        1.630000    1.980000
weight      46.000000         63.000000       57.000000  137.000000
bmi         19.909972         21.049818       21.453574   34.945414
```

  

![png](https://habrastorage.org/r/w1560/getpro/habr/post_images/501/fe2/953/501fe29532c00ff9b3e59fd7754455e7.png)

  

На самом деле Халк, конечно, не толстяк, а профессиональный спортсмен, к которым формула ИМТ малоприменима, но крошка Кайли действительно вырывается вперед, даже с учетом своего небольшого роста.

  

### Matplotlib/Seaborn

  

Библиотеку визуализации matplotlib мы уже слегка задействовали в примере выше. Прямо здесь и прямо сейчас глубоко погружаться в разбор возможностей matplotlib/seaborn, наверное, особого смысла не имеет; все вы видели примеры иллюстраций в научной и бизнес-литературе и, разумеется, все эти графики и иллюстрации можно повторить при помощи рассматриваемых библиотек.

  

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

  

Тепловая карта (heatmap), наглядно показывающая достижения отдельных членов команды:

  

```
from random import randrangeimport numpy as npimport matplotlib.pyplot as pltimport uuidtargets = ["authorities", "humans", "parrots", "cars", "motorcycles", "buildings", "warehouses"]robots = ["Terminator #" + str(uuid.uuid4())[:5] for _ in range(7)]harvest = np.array([[randrange(i * j) for i in range(10, 80, 10)] for j in range(1, 8)])fig, ax = plt.subplots()im = ax.imshow(harvest)ax.set_xticks(np.arange(len(robots)), labels=robots)ax.set_yticks(np.arange(len(targets)), labels=targets)plt.setp(ax.get_xticklabels(), rotation=60, ha="right", rotation_mode="anchor")for i in range(len(targets)):    for j in range(len(robots)):        text = ax.text(j, i, harvest[i, j], ha="center", va="center", color="w")ax.set_title("Targets destroyed")fig.tight_layout()plt.rcParams['figure.figsize'] = [4, 4]plt.rcParams['figure.dpi'] = 200plt.show()
```

  

![png](https://habrastorage.org/r/w1560/getpro/habr/post_images/eba/76c/412/eba76c412c65b8139db64f7a7aebfc19.png)

  

Аналогичная тепловая карта, визуализированная при помощи seaborn:

  

```
from random import randrangeimport numpy as npimport matplotlib.pyplot as pltimport uuidimport seaborn as snssns.set_theme()targets = ["authorities", "humans", "parrots", "cars", "motorcycles", "buildings", "warehouses"]robots = ["Terminator #" + str(uuid.uuid4())[:5] for _ in range(7)]harvest = np.array([[randrange(i * j) for i in range(10, 80, 10)] for j in range(1, 8)])fig, ax = plt.subplots()im = ax.imshow(harvest)ax.set_title("Targets destroyed")plt.rcParams['figure.figsize'] = [4, 4]sns.heatmap(harvest, annot=True, fmt="d", linewidths=.5, ax=ax, xticklabels=robots, yticklabels=targets)plt.setp(ax.get_xticklabels(), rotation=60, ha="right", rotation_mode="anchor")plt.xticks(rotation=60)plt.show()
```

  

![png](https://habrastorage.org/r/w1560/getpro/habr/post_images/fce/04a/0d5/fce04a0d5530e395ba123a3d045d9f2a.png)

  

А вот так будет выглядеть «Доска почёта» при отрисовке в 3D:

  

```
import uuidfrom random import randrangeimport matplotlib.cm as cmimport matplotlib.colors as colorsimport matplotlib.pyplot as pltimport numpy as nptargets = ["authorities", "humans", "parrots", "cars", "motorcycles", "buildings", "warehouses"]robots = ["Terminator #" + str(uuid.uuid4())[:5] for _ in range(7)]harvest = np.array([[randrange(i * j) for i in range(10, 80, 10)] for j in range(1, 8)])fig = plt.figure(figsize=(5, 5))ax = fig.add_subplot(projection='3d')ax.set_xticks(np.arange(len(robots)), labels=robots)ax.set_yticks(np.arange(len(targets)), labels=targets)plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor")plt.setp(ax.get_yticklabels(), ha="left", rotation_mode="anchor")ax.set_title("Our team")xx, yy = np.meshgrid(range(len(targets)), range(len(robots)))x1d, y1d = xx.ravel(), yy.ravel()harvest1d = harvest.ravel()# Setup color schemeoffset = harvest1d + np.abs(harvest1d.min())fracs = offset.astype(float) / offset.max()norm = colors.Normalize(fracs.min(), fracs.max())colors = cm.jet(norm(fracs))ax.bar3d(x1d, y1d, np.zeros_like(x1d + y1d), 0.7, 0.7, harvest1d, color=colors)plt.show()
```

  

![png](https://habrastorage.org/r/w1560/getpro/habr/post_images/a70/52e/4eb/a7052e4eb1eba178f4ef91a78351ff7e.png)

  

### Нейронные сети

  

Углубиться достаточно глубоко в тематику глубокого обучения в рамках нашего несколько неглубокого формата, конечно, не получится (каким-нибудь томом «Введение в Deep Learning» вполне можно нанести себе существенные травмы, если неудачно уронить со стола), так что скользнём буквально по верхушкам, рассмотрев основные понятия.

  

Глубинная нейронная сеть (deep neural network) — это, формально выражаясь, _многослойная_ искусственная нейронная сеть, использующая алгоритмы машинного обучения для моделирования высокоуровневых абстракций с применением нелинейных преобразований. В ходу есть еще такие термины, как «поверхностное машинное обучение», «слабый ИИ», «сильный ИИ»; существует классификация понятий «глубокое обучение», «машинное обучение» и «искусственный интеллект» (вот, например, [вариант](https://learn.microsoft.com/en-us/azure/machine-learning/concept-deep-learning-vs-machine-learning) от Microsoft, такую же структуру-матрёшку демонстрирует Франсуа Шолле в своей книге «Глубокое обучение на Python»), но сейчас, по крайней мере при неформальном общении, всё чаще ставится знак равенства между «машинным обучением» и «искусственным интеллектом»; причём в работе, как правило, используется «машинное обучение», а при подготовке презентаций — «искусственный интеллект» :) Дело, по всей видимости, в том, что глубокое обучение стало самой многообещающей и динамично развивающейся областью машинного обучения, а искусственным интеллектом уже давно именовали всю эту область знаний как популяризаторы науки, так и журналисты.

  

В общем, при первоначальном знакомстве с TensorFlow или PyTorch можете смело всем говорить, что занимаетесь AI, а если захотите углубиться — уж терминологией-то овладеете.  
Что касается сути работы нейронных сетей, то здесь можно выделить следующие основные понятия:

  

**Датасет** — маркированные данные, используемые для обучения сети и для последующей проверки качества этого обучения. Вариант простого датасета — коллекция изображений одинакового размера с рукописными цифрами и буквами, где про каждое изображение точно известно, какай именно символ в нём содержится; такой датасет нужен для разработки систем распознавания рукописного текста (handwritten text recognition, HTR). Создание качественных датасетов — большая, тяжелая работа, поэтому сейчас идет активная работа по созданию моделей, способных работать с большими объёмами _немаркированных_ данных. Как правило, датасет разбивается на две части — для обучения сети и для проверки качества проведенного обучения.

  

**Искусственная нейронная сеть** — программный или аппаратный аналог биологической нейронной сети; в самом простом варианте, в сети прямого распространения, это последовательно соединенные слои нейронов. Первые реализации нейронных сетей получили практическое воплощение еще в 60-х годах XX века, хотя существенный прогресс и практическое внедрение приходятся примерно на последние лет пятнадцать.

  

**Архитектура** искусственной нейронной сети — определяет общие принципы её построения, вот [здесь](https://www.asimovinstitute.org/neural-network-zoo/) есть хорошее введение в тему, наглядно показано, чем, например, GAN отличается от LSTM.

  

**Обучение** нейронной сети — если в двух словах, то это нахождение коэффициентов связи между нейронами. Применительно к машинному обучению заменяет процесс собственно программирования. Когда мы говорим о DALL-E 2, способном создавать изображения по текстовым описаниям или об AlphaGo, обыгрывающем профессиональных игроков в го — мы говорим в первую очередь об обученных нейронных сетях, создатели которых проявили бездну изобретательности, чтобы все коэффициенты связи были на своём месте. Дообучение — частичная корректировка коэффициентов связи между нейронами при модификации старого или добавлении нового функционала.

  

Зачем нужно машинное обучение? Сильный ИИ, который будет (скрестим пальцы) решать все наши проблемы в режиме реального времени, еще за горизонтом, а прямо сейчас нейронные сети решают задачи, которые не по зубам классическим алгоритмам. Можно ли при помощи «обычного» программирования решить, например, задачу распознавания отсканированного текста или перевода с одного языка на другой? Да, можно, и такие небезуспешные попытки неоднократно предпринимались. Но, учитывая прорыв в развитии математической базы машинного обучения, наметившийся в последние 10-15 лет, помноженный на гигантский прирост производительности даже обычных повседневных вычислительных устройств, вроде смартфонов, фактически и распознавание, и перевод сейчас реализуют только при помощи машинного обучения. Для некоторых же классов задач, таких как распознавание изображений и видео с последующей классификацией объектов или беспилотная транспортировка, решения на базе классических алгоритмов никогда не заходили дальше вялотекущих концептов.

  

Мало-помалу машинное обучение делает нашу жизнь лучше. Со временем, надеюсь, каждый из нас сможет воспользоваться плодами работы искусственного интеллекта, хоть мы и величаем его «слабым». И речь идет не только о громких проектах, вроде автопилота «Теслы» (хотя и это крайне немаловажно), но в первую очередь о постоянном сканировании эксабайтных потоков информации, порождаемых современной цифровой цивилизацией — видео с уличных камер, сканов МРТ и КТ, телеметрии с фитнес-браслетов, отчетов об исследовании лекарственных средств и пищевых добавок. И если всё это пройдет перед, может быть, пока не очень умными, но зато неустанными глазами предварительно должным образом обученной нейронной сети, то кратно уменьшится неверных диагнозов, людей, умерших от инсультов или замерзших на улицах, а также лекарств с тяжелыми побочными эффектами.

  

Но помните, однако — даже если вы полны энтузиазма и готовы хоть прямо сейчас нырнуть во все эти модные, интересные и актуальные сущности вроде бигдаты или ИИ — чем дальше вы будете углубляться в эту тематику, тем меньше в вас будет программиста, тем больше исследователя, учёного, математика. Где-то в самом конце этого длинного-предлинного коридора сидит вовсе не Нео из «Матрицы», а эдакий себе [Григорий Перельман](https://en.wikipedia.org/wiki/Grigori_Perelman), уставший человек с ручкой и листом бумаги, иногда этой ручкой на этой бумаге пишущий вещи, которые потом приходится два года разжевывать лучшим математикам мира. Крепко подумайте, как глубоко вы хотите и можете зайти, взвесьте свои силы.

  

## 3. Потоки данных

  

![Data Flows](https://habrastorage.org/r/w1560/getpro/habr/post_images/4b1/afd/231/4b1afd231fcd5e11f79e1347cff43e7e.png)

  

### Itertools

  

Методы модуля itertools возвращают _итераторы_.

  

Итератор — механизм поэлементного обхода данных, который использует метод next() для получения следующего значения последовательности. Подробнее создание итераторов будет рассмотрено ниже, в разделе «ООП / Утиная типизация». В «нормальные» данные итераторы перегоняются при помощи for, next или list().

  

Itertools содержит множество готовых итераторов, которые могут быть бесконечными (порождаются при помощи count, cycle или repeat), конечными (accumulate, chain, takewhile и другие) и комбинаторными (product, combinations, combinations_with_replacement, permutations). Лучше изучить их все, хотя бы поверхностно, потому что даже относительно редко употребляемый метод, например, какой-нибудь zip_longest(), иногда весьма и весьма пригождается, идеально ложась на поставленную задачу.

  

Пример работы с бесконечными итераторами:

  

```
from itertools import count, repeat, cycle# Итератор, возвращающий равномерно распределенные значенияi1 = count(start=0, step=.1)print(next(i1))print(next(i1))print(next(i1))# Итератор, циклично и бесконечно возвращающий элементы итерируемого объектаi2 = cycle([1, 2])print(next(i2))print(next(i2))print(next(i2))# Итератор, возвращающий один и тот же объект бесконечно, если не указано значение аргумента timesi3 = repeat("Wow!", times=3)print(list(i3))
```

  

```
0
0.1
0.2
1
2
1
['Wow!', 'Wow!', 'Wow!']
```

  

Применение некоторых конечных итераторов:

  

```
from itertools import accumulate, chain, compress, dropwhile, takewhile, pairwiseimport operator# Итератор, возвращающий накопленный результат выполнения указанной функции (по умолчанию — сложение)i1 = accumulate([1, 2, 3, 4])i2 = accumulate([1, 2, 3, 4], initial=10)print(list(i1), list(i2))i3 = accumulate([ -3, -2, -1, 1, 2, 3, 4], operator.mul)print(list(i3))# Можно использовать свою функциюdef myfunc(accumulated, current):    return accumulated + 2 * currenti4 = accumulate([1, 2, 3, 4], func=myfunc)print(list(i4))# Можно использовать лямбду (подробнее рассмотрены ниже)i5 = accumulate([1, 2, 3, 4], lambda accumulated, current: accumulated + 2 * current)print(list(i5))# Итератор, возвращающий только те элементы входной последовательности,# которые имеют соответствующий элемент, равный True или 1 в последовательности selectorsi6 = compress("ABCDEF", [1, 1, 1, 0, 0, 1])print(list(i6))# Итератор, отбрасывающий элементы входной последовательности, если результат выполнения функции равен True.# Как только предикат становится False, то отбрасывание прекращается (предикат больше не применяется)i7 = dropwhile(lambda x: x<5, [1, 4, 6, 4, 1, 1, 1, 0])print(list(i7))# takewhile, в отличие от dropwhile, наоборот, возвращает элементы входной последовательности,# если результат выполнения функции равен Truei8 = takewhile(lambda x: x<5, [1, 4, 6, 0, 4, 1, 2, 1])print(list(i8))# Итератор, формирующий из нескольких входных последовательностей одну общуюi2 = chain(["A", "B", "C"],["D", "E", "F"],["G", "H", "I"])print(list(i2))# Кстати, такой же трюк можно провернуть при помощи обычной sum(), задав ей начальный параметр [] (т. е. пустой список)a = sum([["A", "B", "C"],["D", "E", "F"],["G", "H", "I"]], [])print(a)# Возвращает элементы входной коллекции попарноi6 = pairwise([1, 2, 3, 4, 5])print(list(i6))
```

  

```
[1, 3, 6, 10] [10, 11, 13, 16, 20]
[-3, 6, -6, -6, -12, -36, -144]
[1, 5, 11, 19]
[1, 5, 11, 19]
['A', 'B', 'C', 'F']
[6, 4, 1, 1, 1, 0]
[1, 4]
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
[(1, 2), (2, 3), (3, 4), (4, 5)]
```

  

Комбинаторика

  

```
from itertools import product, combinations, combinations_with_replacement, permutations# Создает множество, содержащее все упорядоченные пары элементов из входных множествa = product("abc", "xyz")print(list(a))b = product([0, 1], repeat=3)print(list(b))# Возвращает подпоследовательности длины r из элементов входного итерируемого объекта, повторяющиеся элементы не допускаютсяc = combinations("abc", r=2)print(list(c))# Выдает перестановки элементов итерируемого объектаd = permutations("abc", r=2)print(list(d))# Возвращает подпоследовательности длины r из элементов входного итерируемого объекта, повторяющиеся элементы допустимыe = combinations_with_replacement("abc", r=2)print(list(e))
```

  

```
[('a', 'x'), ('a', 'y'), ('a', 'z'), ('b', 'x'), ('b', 'y'), ('b', 'z'), ('c', 'x'), ('c', 'y'), ('c', 'z')]
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]
[('a', 'b'), ('a', 'c'), ('b', 'c')]
[('a', 'b'), ('a', 'c'), ('b', 'a'), ('b', 'c'), ('c', 'a'), ('c', 'b')]
[('a', 'a'), ('a', 'b'), ('a', 'c'), ('b', 'b'), ('b', 'c'), ('c', 'c')]
```

  

### Enumerate

  

Иногда, при переборе объектов в цикле for, нужно получить не только сам объект, но и его порядковый номер. Разумеется, это можно сделать, создав дополнительную переменную, которая будет инкрементироваться на каждом шаге цикла. Однако, гораздо удобнее это делать с помощью итератора enumerate, введенным в [PEP-279](https://peps.python.org/pep-0279/). Enumerate — синтаксический сахар («introduces… to simplify a commonly used looping idiom»), позволяющий проще и нагляднее работать с объектами, поддерживающими итерацию. Метод __next__() enumerate возвращает кортеж, содержащий значение индекса и соответствующее этому индексу значение.

  

В документации работа enumerate упрощенно объясняется через генератор:

  

```
def enumerate(sequence, start=0):    n = start    for elem in sequence:        yield n, elem        n += 1
```

  

На самом деле enumerate — не генератор, а итератор:

  

```
import collectionsimport typese = enumerate("abcdef")print(isinstance(e, enumerate))print(isinstance(e, collections.Iterable))print(isinstance(e, collections.Iterator))print(isinstance(e, types.GeneratorType))
```

  

```
True
True
True
False
```

  

Enumerate реализован не на Python, а на C, и в его [исходном коде](https://github.com/python/cpython/blob/master/Objects/enumobject.c#L289), разумеется, нет ключевого слова yield.

  

Примеры использования enumerate:

  

```
values = ["a", "b", "c", "d"]for count, value in enumerate(values):    print(count, value)print("\n")for count, value in enumerate(values, start=10 ):    print(count, value)
```

  

```
0 a
1 b
2 c
3 d

10 a
11 b
12 c
13 d
```

  

### Генератор (generator)

  

Любая функция, содержащая ключевое слово _yield_, вернет генератор. Генератор не хранит в памяти все необходимые элементы, а просто содержит метод для вычисления очередного элемента; результат может создаваться на основе математического алгоритма или брать элементы из другого источника данных (коллекция, файл, сетевое подключение и т. д.), при необходимости модифицируя их.

  

Пройти генератор в цикле можно только один раз, на каждом шаге возможно вычислить только следующий элемент, но не предыдущий. Элемент генератора нельзя извлечь по индексу, будет выброшена ошибка, т. к. генератор не поддерживает метод __getitem__().

  

Бесконечный генератор:

  

```
def count(start, step):    current = start    while True:        yield current        current += stepc = count(100, 10)print(next(c))print(next(c))print(next(c))
```

  

```
100
110
120
```

  

Конечный генератор.  
Также, как и конечный итератор, конечный генератор можно превратить в список при помощи list() (вы можете попробовать превратить в list и бесконечный генератор, но процесс рискует несколько затянуться :):

  

```
def count(start, stop, step):    current = start    while current <= stop:        yield current        current += stepc = count(100, 200, 10)print(next(c))print(next(c))print(next(c))print(list(c))
```

  

```
100
110
120
[130, 140, 150, 160, 170, 180, 190, 200]
```

  

Следует разделять итераторы и генераторы. Итератор — объект, который использует метод __next__() для получения следующего значения последовательности. Генератор — функция, которая позволяет отложено создавать результат при итерации.

  

#### Объявление генератора

  

Объявить генератор можно несколькими методами. Первый метод — объявить функцию с yield, как было показано выше.  
Второй метод — использовать _генераторное выражение_ (generator expression):

  

```
r = range(1, 11)squares = (n**2 for n in r)print(list(squares))
```

  

```
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
```

  

Можно объединить генераторы или делегировать часть функционала генератора другому генератору при помощи конструкции _yield from_:

  

```
def subg():    yield 'World'def generator():    yield 'Hello'    yield from subg()    yield '!'for i in generator():    print(i, end = ' ')
```

  

```
Hello World ! 
```

  

До широкого распространения asyncio конструкция yield from использовалась для создания [корутин на базе генераторов](https://docs.python.org/3.7/library/asyncio-task.html#asyncio.coroutine).

  

### Декораторы

  

Что такое декораторы?

  

Декоратор в широком смысле – паттерн проектирования, когда один объект изменяет поведение другого. Декораторы — это, по сути, своеобразные «обёртки», которые дают нам возможность делать что-либо до или после того, что сделает декорируемая функция, не изменяя её. Можно сказать, что декоратор является просто синтаксическим сахаром для конструкции вида:

  

```
my_function = my_decorator(my_function)
```

  

```
def makebold(fn):    def wrapped():        return "<b>" + fn() + "</b>"    return wrappeddef makeitalic(fn):    def wrapped():        return "<i>" + fn() + "</i>"    return wrapped# Разумеется, при последовательном применении нескольких декораторов играет роль порядок декорирования.@makebold@makeitalicdef hello():    return "Hello, world!"print(hello())
```

  

```
<b><i>Hello, world!</i></b>
```

  

Декоратор, подсчитывающий время работы оборачиваемой функции:

  

```
import timedef perf_counter(function):    def counted(*args):        start_time = time.perf_counter_ns()        res = function(*args)        print(f"{time.perf_counter_ns() - start_time} ns")        return res    return counted@perf_counterdef slow_sum(x, y):    time.sleep(1)    return x + yprint(slow_sum(1, 2))
```

  

```
1002478400 ns
3
```

  

### LRU Cache

  

Декоратор, кеширующий значения, возвращаемые функцией. Все аргументы функции должны быть хэшируемы.

  

```
import functoolsdef recursion_sum(n):    if n == 1:        return n    print(n, end=" ")    return n + recursion_sum(n - 1)recursion_sum(5)print("\n")recursion_sum(9)print("\n")@functools.lru_cachedef recursion_sum2(n):    if n == 1:        return n    print(n, end=" ")    return n + recursion_sum2(n - 1)recursion_sum2(5)print("\n")recursion_sum2(9)
```

  

```
5 4 3 2 

9 8 7 6 5 4 3 2 

5 4 3 2 

9 8 7 6 

45
```

  

Размер кеша по умолчанию 128 значений. Ограничение можно отменить при помощи 'maxsize=None'.

  

Пока мы не ушли далеко от тема кеша, погуглите заодно модуль weakref и WeakValueDictionary, позволяющие организовать более гибкую работу с кешем.

  

### Параметризованный декоратор

  

В декоратор можно передать и позиционные, и именованные аргументы — args и kwargs соответственно. Синтаксис декораторов с аргументами немного отличается — декоратор с аргументами должен возвращать функцию, которая принимает функцию и возвращает другую функцию:

  

```
def text_wrapper(wrap_text):    def wrapped(function):        def wrapper(*args, **kwargs):            result = function(*args, **kwargs)            return f"{wrap_text}\n{result}\n{wrap_text}"        return wrapper    return wrapped@text_wrapper('============')def my_decorated_function(text):    return textprint(my_decorated_function('Hello, world!'))
```

  

```
============
Hello, world!
============
```

  

### Контекстный менеджер

  

Код, размещенный внутри оператора with выполняется с особенностью: как до, так и после срабатывают события входа в блок with и выхода из него. Объект, который определяет логику событий, называется контекстным менеджером.

  

На уровне класса события определены методами __enter__ и __exit__.  
__enter__ срабатывает в тот момент, когда ход исполнения программы переходит внутрь with. Метод может вернуть значение. Оно будет доступно расположенному внутри блока with коду.  
__exit__ срабатывает в момент выхода блока, в т.ч. и в случае исключения. В этом случае в метод будет передана тройка значений (exc_class, exc_instance, traceback).

  

Самый распространённый контекстный менеджер – класс, порожденный функцией open. Он гарантирует, что файл будет закрыт даже в том случае, если внутри блока возникнет ошибка.

  

Желательно побыстрее выходить из контекстного менеджера, освобождая контекст и ресурсы.

  

```
with open('file.txt') as f:    data = f.read()process_data(data)
```

  

В примере выше мы вышли из блока with сразу же после прочтения файла. Обработка данных происходит в основном блоке программы.

  

Контекстные менеджеры можно использовать для временной замены параметров, переменных окружения, транзакций БД.

  

Напишем свой контекстный менеджер для подключения к БД SQLite:

  

```
import sqlite3class db_conn:    def __init__(self, db_name):        self.db_name = db_name    # Открываем подключение к БД    def __enter__(self):        self.conn = sqlite3.connect(self.db_name)        return self.conn    # Закрываем подключение к БД    def __exit__(self, exc_type, exc_value, exc_traceback):        self.conn.close()        if exc_value:            raiseif __name__ == "__main__":    db = "test_context_connect.db"    with db_conn(db) as conn:        cursor = conn.cursor()
```

  

### Контекстный менеджер на базе contextlib

  

Перепишем наш контекстный менеджер для подключения к БД SQLite при помощи contextlib:

  

```
import sqlite3from contextlib import contextmanager# Схема конструирования следующая: всё, что написано до оператора yield - вызывается в рамках функции __enter__,# всё что после – в рамках __exit__.@contextmanagerdef db_conn(db_name):    # Открываем подключение к БД    conn = sqlite3.connect(db_name)    yield conn    # Закрываем подключение к БД    conn.close()if __name__ == "__main__":    db = "test_contextlib_connect.db"    with db_conn(db) as conn:        cursor = conn.cursor()
```

  

## 4. ООП

  

![OOP](https://habrastorage.org/r/w1560/getpro/habr/post_images/7ec/81b/9a6/7ec81b9a69c879e5ff97b2f1a10ad093.png)

  

### Классы и объекты

  

Тут, конечно, было бы к месту кратенькое, минут на сорок, введеньице в тему классов и объектов, но в наш текущий формат такая мощная врезка не совсем укладывается. Объясню максимально просто, на доступных примерах из киновселенной «Чужих»:  
объект — это один конкретный ксеноморф;  
класс — это Королева ксеноморфов. Класс либо рожает ксеноморфа, либо может вступить в бой сам (@staticmethod);  
метапрограммирование — это такая Супер-Королева, размером с «Сулако», которая рожает других Королев;  
наследование — это ксеноморф из «Воскрешения», помните, миленький такой, взявший лучшее и от собственной генетической программы и от генов Рипли.

  

Мы попробуем вернуться к теме объектов с чуть более серьезным настроением позже, в главе «Архитектура», но, вообще, в объектно-ориентированном программировании нет ничего особо сложного; просто до него лучше дойти, предварительно немного погрязнув в поддержке обычного процедурного подхода, когда зачастую стоит выбор — попробовать подлечить этот кусок кода или уже усыпить и переписать всё по новой? Когда-то давно, когда я писал относительно несложные программы на ассемблере для микроконтроллеров, то читая Страуструпа, слегка недоумевал — зачем всё это? Чтобы осознать потребность в обуви, надо походить босиком.

  

### Магические методы

  

Специальные (называемые также magic или dunder) методы класса — перегрузка, позволяющая классам определять собственное поведение по отношению к операторам языка.  
Магические они потому, что почти никогда не вызываются явно. Их вызывают встроенные функции или синтаксические конструкции. Например, функция len() вызывает метод __len__() переданного объекта. Метод __add__(self, other) вызывается автоматически при сложении оператором +.

  

Примеры магических методы:

  

__init__: конструктор класса  
__add__: сложение с другим объектом  
__eq__: проверка на равенство с другим объектом  
__cmp__: сравнение (больше, меньше, равно)  
__iter__: при подстановке объекта в цикл

  

```
print(dir(int), "\n")class A:  # An empty class    ...a = A()print(dir(a), "\n")print(repr(a), "\n")print(str(a))
```

  

```
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes'] 

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] 

<__main__.A object at 0x000001A1FFF3AF20> 

<__main__.A object at 0x000001A1FFF3AF20>
```

  

Особенностью метода __init__ является то, что он не должен ничего возвращать. При попытке возврата данных будет сгенерировано исключение.  
__repr__ (representation) возвращает более-менее машино-читаемое представление объекта, полезное для отладки.  
_Иногда_ repr может содержать достаточно информации для восстановления объекта.  
__str__ возвращает человеко-читаемое сообщение. Если __str__ не определён, то str использует repr.

  

```
class Person:  # A simple class with init, repr and str methods    def __init__(self, name: str):        self.name: str = name    def __repr__(self):        return f"Person '{self.name}'"    def __str__(self):        return f"{self.name}"    def say_hi(self):        print("Hi, my name is", self.name)p = Person("Charlie")p.say_hi()print(repr(p))print(str(p))
```

  

```
Hi, my name is Charlie
Person 'Charlie'
Charlie
```

  

### @property

  

Декоратор [@property](https://docs.python.org/3/library/functions.html?highlight=property#property) используется для определения методов, доступных как поля. Таким образом операции чтения/записи поля можно обрамить дополнительной логикой, например, проверкой допустимых значений входного аргумента.

  

```
import mathclass Circle:    def __init__(self, radius, max_radius):        self._radius = radius        self.max_radius = max_radius    @property    def radius(self):        return self._radius    @radius.setter    def radius(self, value):        if value <= self.max_radius:            self._radius = value        else:            raise ValueError    @property    def area(self):        return 2 * self.radius * math.picircle = Circle(10, 100)circle.radius = 20  # OK# circle.radius = 101  # Raises ValueErrorprint(circle.area)
```

  

```
125.66370614359172
```

  

### @staticmethod

  

Обычный метод (т. е. не помеченный декораторами @staticmethod или @classmethod) имеет доступ к свойствам конкретного экземпляра класса.

  

@staticmethod — метод, принадлежащий классу, а не экземпляру класса. Можно вызывать без создания экземпляра, т. к. метод не имеет доступа к свойствам экземпляра. При помощи @staticmethod помечают функционал, логически связанный с классом, но не требующий доступа к свойствам экземпляра.

  

### @classmethod, cls, self

  

Если метод не должен иметь доступа к свойствам конкретного экземпляра класса (как @staticmethod), но должен иметь доступ к другим методам и переменным класса, то следует использовать @classmethod.

  

```
class B(object):    def foo(self, x):        print(f"Run foo({self}, {x})")    @classmethod    def class_foo(cls, x):        print(f"Run class_foo({cls}, {x})")    @staticmethod    def static_foo(x):        print(f"Run static_foo({x})")b = B()b.foo(1)b.class_foo(1)b.static_foo(1)
```

  

```
Run foo(<__main__.B object at 0x000001A1FFF3A980>, 1)
Run class_foo(<class '__main__.B'>, 1)
Run static_foo(1)
```

  

У @classmethod первым параметром должен быть cls (класс), а у обычного метода — self (экземпляр класса).  
Для @staticmethod не требуется ни cls, ни self.

  

### __dict__

  

Каждый класс и каждый объект имеет атрибут __dict__. Это «системный», определённый интерпретатором атрибут, его не нужно создавать вручную. __dict__ — словарь, который хранит пользовательские атрибуты, и в котором ключом является _имя атрибута_, значением, соответственно, _значение атрибута_.

  

```
class Supercriminal:    publisher = 'DC Comics'Riddler = Supercriminal()print(Supercriminal.__dict__)print(Riddler.__dict__)Riddler.name = 'Edward Nygma'print(Riddler.__dict__)  # Values from object __dict__print(Riddler.publisher)  # Value from class __dict__
```

  

```
{'__module__': '__main__', 'publisher': 'DC Comics', '__dict__': <attribute '__dict__' of 'Supercriminal' objects>, '__weakref__': <attribute '__weakref__' of 'Supercriminal' objects>, '__doc__': None}
{}
{'name': 'Edward Nygma'}
DC Comics
```

  

Каждый раз при запросе пользовательского атрибута Python последовательно обыскивает сам объект, класс объекта и классы, от которых унасаледован класс объекта.

  

### __slots__

  

Если вы припомните разницу между списком и кортежем, а также между множеством и иимутабельным множеством, то заметите, что создатели Python пытаются предоставлять разработчикам выбор между удобством и скоростью (тут Си с ассемблерными вставками слегка напрягается, но потом опадает, как будто хотел что-то сказать, но благоразумно передумал). К списку таких же особенностей языка, заточенных на увеличение производительности и уменьшение занимаемой памяти, относится и __slots__.

  

Вот [официальная документация](https://docs.python.org/3/reference/datamodel.html?highlight=slots#object.__slots__) по __slots__, а вот [дополнительные разъяснения](https://stackoverflow.com/questions/472000/usage-of-slots/28059785#28059785) от одного из разработчиков официальной документации. При выборе «slots или не slots» не забывайте также про существование [PEP 412 – Key-Sharing Dictionary](https://peps.python.org/pep-0412/), который внёс некоторый раздрай в некогда однозначное отношение к __slots__.

  

__dict__, рассмотренный чуть выше – изменяемая структура, и вы можете на лету добавлять и удалять поля из класса, что удобно, но порой медленно. Вы можете разменять удобство на скорость и размер занимаемой памяти, создав __slots__ — жестко заданный список предопределенных атрибутов, резервирующий память, создание которого _запрещает дальнейшее создание __dict__ и __weakref___. Слоты можно использовать, когда у класса может быть очень много полей, например, в ORM, либо когда критична производительность, потому что доступ к списку работает быстрее, чем поиск в словаре.

  

```
class Clan:    __slots__ = ["first", "second"]clan = Clan()clan.first = "Joker"clan.second = "Lex Luthor"# clan.third = "Green Goblin"  # Raises AttributeError# print(clan.__dict__)  # Raises AttributeError
```

  

Слоты используются, скажем, в библиотеках requests (например, __slots__ = ["url", "netloc", "simple_url", "pypi_url", "file_storage_domain"]) или ORM peewee (__slots_\_ = ('stack', '_sql', '_values', 'alias_manager', 'state')).

  

Наследование __slots__ имеет определенную специфику и будет рассмотрено ниже.

  

Чтобы было понятно, о каком приросте производительности и снижении потребления памяти идёт речь, сделаем простое сравнение:

  

```
import timeitimport pympler.asizeof  # В нашем случае sys.getsizeof - не лучший вариант, берем стороннее решениеclass NotSlotted():    passclass Slotted():    __slots__ = 'foo'not_slotted = NotSlotted()slotted = Slotted()def get_set_delete_fn(obj):    def get_set_delete():        obj.foo = "Never Ending Song of Love"        del obj.foo    return get_set_deletens = min(timeit.repeat(get_set_delete_fn(not_slotted)))s = min((timeit.repeat(get_set_delete_fn(slotted))))print(ns, s, f'{(ns - s) / s * 100} %')print(pympler.asizeof.asizeof(not_slotted), 'bytes')print(pympler.asizeof.asizeof(slotted), 'bytes')
```

  

```
0.10838449979200959 0.08712740009650588 24.39772066187959 %
280 bytes
40 bytes
```

  

Я в Python 3.10 в Windows вижу 24 % разницы.

  

На всякий случай напоминаю еще раз — прогняйте все непонятные примеры кода в IDE, их можно и нужно анализировать, корректировать и видоизменять. Попробуйте, например, самостоятельно посмотреть потребление памяти объектов с __dict__ и __slots__. А заодно на практике испытайте давно напрашивающийся, и наконец появившийся в Python 3.10 [симбиоз](https://docs.python.org/3/library/dataclasses.html#module-contents) между __slots__ и dataclass.

  

### Утиная типизация

  

[Утиная типизация](https://en.wikipedia.org/wiki/Duck_typing) (duck types) — постулирование реализации интерфейса классом не через явное объявление, а через реализацию методов интерфейса. Так, каждый класс, реализующий методы __next__() и __iter__(), автоматически становится итератором, несмотря на отсутствие явного объявления (что-нибудь вроде [iterator](https://habr.com/ru/users/iterator/)) или, скажем, наследования от класса Iterator.

  

### Iterator

  

Итератор — класс, реализующий методы __next__() и __iter__().  
Метод __next__() должен возвращать следующее значение итератора или выкидывать исключение StopIteration, чтобы сигнализировать о том, что итератор исчерпал доступные значения.  
Метод __iter__() должен возвращать "self".

  

```
class LimitCounter:    def __init__(self, max_value: int):        self.count = 0        self.max_value = max_value    def __next__(self):        self.count += 1        if self.count <= self.max_value:            return self.count        else:            raise StopIteration    def __iter__(self):        return selflimit_counter = LimitCounter(2)print(next(limit_counter))print(next(limit_counter))# print(next(limit_counter))  # Raises StopIteration
```

  

```
1
2
```

  

### Comparable

  

Начиная с Python 3.4, для того, чтобы экземпляры метода можно было сравнивать между собой, достаточно определить методы __lt__ (меньше) и __eq__ (равно), а также задействовать декоратор @functools.total_ordering.

  

```
from functools import total_ordering@total_orderingclass Person:    def __init__(self, firstname: str, lastname: str):        self.firstname: str = firstname        self.lastname: str = lastname    def _is_valid_operand(self, other):        return hasattr(other, "lastname") and hasattr(other, "firstname")    def __eq__(self, other):        if not self._is_valid_operand(other):            return NotImplemented        return (self.lastname, self.firstname) == (other.lastname, other.firstname)    def __lt__(self, other):        if not self._is_valid_operand(other):            return NotImplemented        return (self.lastname, self.firstname) < (other.lastname, other.firstname)Finn = Person("Finn", "the Human")Jake = Person("Jake", "the Dog")print(Finn != Jake)
```

  

```
True
```

  

### Hashable

  

Хешируемые объекты должны реализовывать методы __hash__() и __eq__(). Хеш объекта должен быть неизменен в течении всего жизненного цикла. Хешируемые объекты можно использовать как ключи в словарях и как элементы множеств, так как эти структуры используют хеш-таблицу для внутреннего представления данных.

  

Hashable objects that compare equal must have the same hash value, meaning default hash() that returns `'id(self)'` will not do. That is why Python automatically makes classes unhashable if you only implement eq().

  

```
class Hero:    def __init__(self, name: str, level: int):        self.name: str = name        self.level: int = level    def _is_valid_operand(self, other):        return hasattr(other, "name") and hasattr(other, "level")    def __eq__(self, other):        if not self._is_valid_operand(other):            return NotImplemented        return (self.name, self.level) == (other.name, other.level)    def __hash__(self):        return hash((self.name, self.level))Finn = Hero("Finn the Human", 10_000)Jake = Hero("Jake the Dog", 10_000)print(hash(Finn))print(hash(Jake))
```

  

```
-8707075988359731747
-2276052447712954388
```

  

### Sortable

  

Для возможности применения к последовательностям объектов таких методов как sort() или max() необходимо, как и в случае Comparable, определить методы __lt__ (меньше) и __eq__ (равно), а также задействовать декоратор @functools.total_ordering.

  

Для более предсказумого поведения объекта в условиях различного контекста вы можете определить полное множество функций сравнения (__lt()__, __gt()__, __le__() и __ge__()).

  

Для примера создадим класс студентов, которых можно будет сортировать не по имени, а по среднему баллу.

  

```
from functools import total_orderingfrom statistics import mean@total_orderingclass Student:    def __init__(self, name: str, grades: list[int]):        self.name: str = name        self.grades: list[int] = grades    def _is_valid_operand(self, other):        return hasattr(other, "name") and hasattr(other, "grades")    def __eq__(self, other):        if not self._is_valid_operand(other):            return NotImplemented        return mean(self.grades) == mean(other.grades)    def __lt__(self, other):        if not self._is_valid_operand(other):            return NotImplemented        return mean(self.grades) < mean(other.grades)    # определим str для человеко-читаемой репрезентации объекта    def __str__(self):        return self.name + " " + str(mean(self.grades))Melissa = Student("Melissa Andrew", [4, 3, 4, 5, 4])Peter = Student("Peter Shining Jr.", [3, 3, 4, 5, 3])Joe = Student("Just Joe", [5, 5, 4, 5, 5])print([str(stud) for stud in sorted([Peter, Melissa, Joe], reverse=True)])
```

  

```
['Just Joe 4.8', 'Melissa Andrew 4', 'Peter Shining Jr. 3.6']
```

  

### Callable

  

Для возможности вызова объекта в качестве функции необходимо реализовать метод __call__. Типы, поддерживающие возможность их вызова в качестве функции, могут принимать набор аргументов.

  

```
class Counter:    def __init__(self):        self.i = 0    def __call__(self):        self.i += 1        return self.icounter = Counter()print(counter())print(counter())print(counter())
```

  

```
1
2
3
```

  

@classmethod нельзя вызывать в качестве функции:

  

```
class Check():    @classmethod     def class_method(cls):        pass     @staticmethod    def static_method():        pass    def instance_method(self):        pass for attr, val in vars(Check).items():    if not attr.startswith("__"):        print (attr, f"{'is' if callable(val) else 'is NOT'} callable")
```

  

```
class_method is NOT callable
static_method is callable
instance_method is callable
```

  

Контекстные менеджеры, описанные в предыдущей главе, тоже, как мы теперь видим, определяются через утиную типизацию при помощи методов __enter__ и __exit__.

  

### Утиная типизация итерируемых объектов

  

### Iterable

  

[Iterable](https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable) — объект, который для предоставления возможности поочерёдного прохода по всем своим элементам должен реализовывать метод __iter__(), возвращающий итератор. У каждого объекта с методом __iter__() автоматически начинает работать метод __contains__().

  

```
class MyIterable:    def __init__(self, *args):        self.a = list(args)    def __iter__(self):        return iter(self.a)mi = MyIterable(1, 2, 3, 4)print([el for el in mi])print(1 in mi)  # __contains__()
```

  

```
[1, 2, 3, 4]
True
```

  

### Collection

  

[Collection](https://docs.python.org/3/library/collections.abc.html#collections.abc.Collection) — объект, предоставляющий возможность поочерёдного прохода по всем своим элементам и обладающий конечным размером.  
В дополнение к iter() должен быть реализован метод len(), возвращающий размер коллекции.

  

```
class MyCollection:    def __init__(self, *args):        self.a = list(args)    def __iter__(self):        return iter(self.a)    def __len__(self):        return len(self.a)mc = MyCollection(1, 2, 3, 4)print([el for el in mc])print(1 in mc)print(len(mc))
```

  

```
[1, 2, 3, 4]
True
4
```

  

### Sequence

  

Требует методы len() and getitem(). getitem() должен отдавать элемент с требуемым индексом или вызывать исключение IndexError.  
Автоматически будут порождены методы iter(), reversed() и contains().

  

```
class MySequence:    def __init__(self, a):        self.a = a    def __len__(self):        return len(self.a)    def __getitem__(self, i):        return self.a[i]
```

  

### ABC Sequence

  

Коллекция Sequence из [Abstract Base Classes for Containers](https://docs.python.org/3/library/collections.abc.html) предоставляет расширенный интерфейс по сравнению с обычной Sequence.  
Всё так же требуя __getitem__ и __len__, предоставляет __contains__, __iter__, __reversed_\_, index и count.

  

```
from collections import abcclass MyAbcSequence(abc.Sequence):    def __init__(self, a):        self.a = a    def __len__(self):        return len(self.a)    def __getitem__(self, i):        return self.a[i]
```

  

### Таблица требуемых и доступных методов:

  

```
+------------+------------+------------+------------+--------------+
|            |  Iterable  | Collection |  Sequence  | ABC Sequence |
+------------+------------+------------+------------+--------------+
| iter()     |   нужен    |   нужен    |     +      |      +       |
| contains() |     +      |     +      |     +      |      +       |
| len()      |            |   нужен    |   нужен    |    нужен     |
| getitem()  |            |            |   нужен    |    нужен     |
| reversed() |            |            |     +      |      +       |
| index()    |            |            |            |      +       |
| count()    |            |            |            |      +       |
+------------+------------+------------+------------+--------------+
```

  

И вообще, потщательнее присмотритесь с collections.abc, там есть множество заготовок, которые помогут вам сэкономить немало времени. Например, если к упомянутым относительно ABC Sequence __getitem__ и __len__ добавить __setitem__, __delitem__ и insert, то в ответ вы получите коллекцию MutableSequence, которая, кроме возможностей Sequence, имеет еще методы append, reverse, extend, pop, remove и __iadd__.

  

### Копирование объектов

  

В Python оператор присваивания (=) не копирует объекты. Вместо этого он создает связь между существующим объектом и именем целевой переменной. Чтобы создать копии объекта в Python, необходимо использовать модуль copy. Более того, существует два способа создания копий для данного объекта с помощью модуля copy.

  

Shallow Copy – это побитовая копия объекта. Созданный скопированный объект имеет точную копию значений в исходном объекте. Если одно из значений является ссылкой на другие объекты, копируются только адреса ссылок на них.

  

Deep Copy – рекурсивно копирует все значения от исходного объекта к целевому, т. е. дублирует даже объекты, на которые ссылается исходный объект.

  

```
from copy import copy, deepcopyclass A:    def __init__(self, val: list):        self.val = val    def change_val(self, val: list):        self.val = vala = A(list("one"))# Просто копирование ссылки на объектb = a  # Assignment# Создание нового объекта и копирование ссылок на объекты, найденные в изначальном объектеc = copy(a)  # Shallow copy# Создание нового объекта с последующим рекурсивным копированием содержащихся внутри объектовd = deepcopy(a)  # Deep Copyb.change_val(list("two"))c.change_val(list("three"))d.change_val(list("four"))print(a.val, b.val, c.val, d.val)print(id(a), id(b), id(c), id(d))print(id(a.val[1]), id(c.val[1]))
```

  

```
['t', 'w', 'o'] ['t', 'w', 'o'] ['t', 'h', 'r', 'e', 'e'] ['f', 'o', 'u', 'r']
1795295519472 1795295519472 1793149281968 1793149273808
1795217224688 1795217321264
```

  

### Наследование

  

```
class Person:    def __init__(self, name, age):        self.name = name        self.age = ageclass Employee(Person):    def __init__(self, name, age, staff_num, email):        super().__init__(name, age)        self.staff_num = staff_num        self.email = email
```

  

### Множественное наследование

  

При множественном наследовании порядок разрешения методов (method resolution order, MRO) позволяет Питону выяснить, из какого класса-предка нужно вызывать метод, если он не обнаружен непосредственно в классе-потомке.

  

```
class PrivateStaffData:    def __init__(self, private_email):        self.private_email = private_emailclass PublicStaffData:    def __init__(self, work_email):        self.work_email = work_emailclass StaffData(PrivateStaffData, PublicStaffData):    def __init__(self, private_email, work_email):        super().__init__()print(StaffData.mro())
```

  

```
[<class '__main__.StaffData'>, <class '__main__.PrivateStaffData'>, <class '__main__.PublicStaffData'>, <class 'object'>]
```

  

MRO строит иерархию наследования таким образом, чтобы более специфичные методы класса-потомка перекрывали менее специфичные методы класса-предка. MRO строит упорядоченный список классов, в которых будет производиться поиск метода слева направо (линеаризация класса).

  

Для решения проблемы ромбовидной структуры (которая неявно присутствует даже в простейшем случае, так как все классы наследуются от object) линеаризация должна быть монотонной. Монотонность — свойство, которое требует соблюдения в линеаризации класса-потомка того же порядка следования классов-прародителей, что и в линеаризации класса-родителя. Линеаризация по сути является [топологической сортировкой](https://en.wikipedia.org/wiki/Topological_sorting). В ранних версиях Python использовался алгоритм DLR, сейчас в ходу [C3-линеаризация](https://en.wikipedia.org/wiki/C3_linearization).

  

Если после удовлетворения свойства монотонности остаётся больше одного варианта линеаризации, то применяется порядок локального старшинства (local precedence ordering), т. е. порядок соблюдения для классов-родителей в линеаризации класса-потомка того же порядка, что и при его объявлении. Например, если класс объявлен как D(A, B, C), то в линеаризации D класс A должен стоять раньше B, а класс B — раньше C.

  

Если разрешение всех конфликтов при линеаризации невозможно, то остается три пути:  
1 — переменой мест классов-предков в объявлении класса-потомка (но это помогает далеко не всегда);  
2 — пересмотр иерархии наследования;  
3 — определение своей собственной линеаризации через метаклассы при помощи метода mro(cls). Но при данном подходе надо быть готовым к тому, что будет использован менее специфичный метод класса-родителя вместо более специфичного метода класса-потомка.

  

При задании своей собственной линеаризации Python отключает встроенные проверки.

  

### Наследование классов со __slots__

  

При одиночном наследовании __slots__ нормально наследуется, но это не предотвращает создание __dict__:

  

```
class SlotsClass:  __slots__ = 'foo', 'bar'class ChildSlotsClass(SlotsClass):  ...obj = ChildSlotsClass()print(obj.__slots__)obj.something_new = "underwater stones"print(obj.__dict__)
```

  

```
('foo', 'bar')
{'something_new': 'underwater stones'}
```

  

Для ограничения дочернего класса слотами нужно в нём снова присвоить значение атрибуту __slots__, родительские поля дублировать не нужно.

  

```
class SlotsClass:  __slots__ = 'foo', 'bar'class ChildSlotsClass(SlotsClass):  __slots__ = 'baz'obj = ChildSlotsClass()# obj.something_new = "underwater stones"  # Raises AttributeError: 'ChildSlotsClass' object has no attribute 'something_new'
```

  

Множественное же наследование классов с _непустыми_ __slots__ невозможно.

  

### Метапрограммирование

  

Что такое класс? Это, в принципе, просто кусок кода, описывающий, как создать объект. Но в Python класс — это нечто большее, классы также являются объектами; как только используется ключевое слово class, Python исполняет команду и создаёт объект:

  

```
class A:    ...
```

  

В памяти будет создан объект с именем A.

  

Классы, как и другие объекты, можно создавать на ходу:

  

```
def custom_class(name):    if name == "foo":        class Foo:            ...        return Foo  # Возвращает именно класс, а не экземпляр    else:        class Bar:            ...        return BarMyClass = custom_class("foo")print(MyClass)  # Функция возвращает класс, а не экземплярprint(my_class := MyClass())  # Можно создать экземпляр класса
```

  

```
<class '__main__.custom_class.<locals>.Foo'>
<__main__.custom_class.<locals>.Foo object at 0x000001F0ECF97610>
```

  

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

  

Основная цель метаклассов — автоматически изменять класс в момент создания, генерируя классы в соответствии с текущим контекстом.  
Сами по себе метаклассы достаточно просты и работают примерно следующим образом:  
перехватывают создание класса,  
изменяют класс,  
возвращают модифицированный класс.

  

Но обычно логику работы метаклассов насыщают вещами вроде [интроспекции](https://en.wikipedia.org/wiki/Type_introspection#Python) или манипуляцией наследованием, поэтому конечный код выглядит достаточно громоздко.

  

Здесь неплохо было бы добавить еще пару страниц про ньюансы создания и работы метаклассов, но позвольте переадресовать вас на вот эту [прекрасную статью](https://habr.com/ru/post/145835/).

  

При помощи метаклассов хорошо решаются задачи, например, генерации классов для ORM. Скажем, для

  

```
class Person(models.Model):    name = models.CharField(max_length=30)    age = models.IntegerField()
```

  

код

  

```
keanu = Person(name="Keanu Reeves", age=58)    print(keanu.age)
```

  

распечатает число, взятое из БД, потому что models.Model определяет __metaclass__, который сотворит некоторую магию и превратит класс Person, который мы только что определили достаточно простым выражением, в сложную привязку к базе данных.

  

Если вы всё еще ломаете голову, где бы вам пришить метапрограммирование в своём текущем проекте, чтобы потом упомянуть об этом в резюме, то вот вам на 147 % уместная цитата из [Тима Питерса](https://en.wikipedia.org/wiki/Tim_Peters_(software_engineer)): «[Metaclasses] are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why)», что в вольном переводе означает «Метаклассы нужны только уверенным в себе людям, которые точно знают, чего хотят от жизни, а вовсе не тебе».

  

### @abstractmethod

  

Абстрактный класс в Python — аналог интерфейса в других языках (например, в C#) — класс, содержащий только сигнатуры методов, без реализации. Реализация методов переложена на классы-потомки. Задача абстрактного класса соответствует задаче интерфейса — _обязать_ классы-потомки реализовывать _все_ методы, заложенные в классе-родителе.

  

```
import abcclass AbstractClass(metaclass=abc.ABCMeta):    @abc.abstractmethod    def return_anything(self):        returnclass ConcreteClass(AbstractClass):    def return_anything(self):        return 42c = ConcreteClass()print(c.return_anything())
```

  

```
42
```

  

Если не специфицировать return_anything() в ConcreteClass, при попытке вызвать c.return_anything() будет выброшено исключение TypeError: Can't instantiate abstract class ConcreteClass with abstract method return_anything.

  

## Источники

  

Официальная документация Python [docs.python.org](https://docs.python.org/), включающая [The Python Standard Library](https://docs.python.org/3/library/index.html).  
Весьма подробное руководство (совсем уж базовый синтаксис не включен): [Comprehensive Python Cheatsheet](https://github.com/gto76/python-cheatsheet).  
Руководство с включением базового синтаксиса: [Python Cheatsheet](https://github.com/wilfredinni/python-cheatsheet). Включает практические Jupiter [Notebooks](https://github.com/wilfredinni/python-cheatsheet/tree/master/jupyter_notebooks).  
Сипсок библиотек и фреймворков: [Awesome Python](https://github.com/vinta/awesome-python).  
Около-питоновские практические советы (pip, virtualenv, pyInstaller и т. д.): ["The Hitchhiker’s Guide to Python"](https://github.com/realpython/python-guide).  
Мануал для начинающих дата-сайентистов: [Joel Grus, "Data Science from Scratch"](https://github.com/joelgrus/data-science-from-scratch).  
Руководство для начинающих: ["Python Notes for Professionals"](https://goalkicker.com/PythonBook/).  
Руководство для опытных программистов: ["Python 3 Patterns, Recipes and Idioms"](https://python-3-patterns-idioms-test.readthedocs.io/en/latest/index.html).  
Архитектурные паттерны: [Harry Percival & Bob Gregory, "Architecture Patterns with Python"](https://www.cosmicpython.com/book/preface.html).

Теги:

- [учебник](https://habr.com/ru/search/?target_type=posts&order=relevance&q=[%D1%83%D1%87%D0%B5%D0%B1%D0%BD%D0%B8%D0%BA])
- [python](https://habr.com/ru/search/?target_type=posts&order=relevance&q=[python])
- [roadmap](https://habr.com/ru/search/?target_type=posts&order=relevance&q=[roadmap])
- [career](https://habr.com/ru/search/?target_type=posts&order=relevance&q=[career])

Хабы:

- [Python](https://habr.com/ru/hubs/python/)
- [Программирование](https://habr.com/ru/hubs/programming/)
- [Карьера в IT-индустрии](https://habr.com/ru/hubs/career/)