-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
1615 lines (1512 loc) · 279 KB
/
atom.xml
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[阿驹]]></title>
<subtitle><![CDATA[我和你们一样, 我和你们不一样。]]></subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://aju.space/"/>
<updated>2017-09-16T02:06:24.180Z</updated>
<id>http://aju.space/</id>
<author>
<name><![CDATA[阿驹]]></name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title><![CDATA[深入理解Python异步编程(上)]]></title>
<link href="http://aju.space/2017/07/31/Drive-into-python-asyncio-programming-part-1.html"/>
<id>http://aju.space/2017/07/31/Drive-into-python-asyncio-programming-part-1.html</id>
<published>2017-07-31T19:13:51.000Z</published>
<updated>2017-09-16T02:06:24.180Z</updated>
<content type="html"><![CDATA[<p>Python asyncio异步编程中文教程,只此一篇足矣,一览众山小!</p>
<p>彻底理解异步编程是什么、为什么、怎么样。深入学习asyncio的基本原理和原型,了解生成器、协程在Python异步编程中是如何发展的。<br>本文首发至微信公众号<strong>“驹说码事”(jushuoms)</strong>,欢迎关注以获取更多干货!<br><a id="more"></a></p>
<h2 id="前言">前言</h2><p>很多朋友对异步编程都处于“听说很强大”的认知状态。鲜有在生产项目中使用它。而使用它的同学,则大多数都停留在知道如何使用 Tornado、Twisted、Gevent 这类异步框架上,出现各种古怪的问题难以解决。而且使用了异步框架的部分同学,由于用法不对,感觉它并没牛逼到哪里去,所以很多同学做 Web 后端服务时还是采用 Flask、Django等传统的非异步框架。</p>
<p>从上两届 PyCon 技术大会看来,异步编程已经成了 Python 生态下一阶段的主旋律。如新兴的 Go、Rust、Elixir 等编程语言都将其支持异步和高并发作为主要“卖点”,技术变化趋势如此。Python 生态为不落人后,从2013年起由 Python 之父 Guido 亲自操刀主持了Tulip(asyncio)项目的开发。</p>
<p>本系列教程分为上中下篇,让读者深入理解Python异步编程,解决在使用异步编程中的疑惑,深入学习Python3中新增的<code>asyncio</code>库和<code>async/await</code>语法,尽情享受 Python 带来的简洁优雅和高效率。</p>
<p>关键词:异步、非阻塞、并发、asyncio、协程、Gevent、uvloop</p>
<h2 id="内容安排">内容安排</h2><h3 id="上篇">上篇</h3><ul>
<li>了解 异步编程及其紧密相关的概念,如阻塞/非阻塞、同步/异步、并发/并行等</li>
<li>理解 异步编程是什么,以及异步编程的困难之处</li>
<li>理解 为什么需要异步编程</li>
<li>熟悉 如何从同步阻塞发展到异步非阻塞的</li>
<li>掌握<code>epoll + Callback + Event loop</code>是如何工作的</li>
<li>掌握 Python 是如何逐步从回调到生成器再到原生协程以支持异步编程的</li>
<li>掌握 <code>asyncio</code> 的工作原理</li>
</ul>
<h3 id="中篇">中篇</h3><ul>
<li>掌握 asyncio 标准库基本使用</li>
<li>掌握 asyncio 的事件循环</li>
<li>掌握 协程与任务如何使用与管理(如调度与取消调度)</li>
<li>掌握 同步原语的使用(Lock、Event、Condition、Queue)</li>
<li>掌握 asyncio 和多进程、多线程结合使用</li>
</ul>
<h3 id="下篇">下篇</h3><ul>
<li>理解 GIL 对异步编程的影响</li>
<li>理解 asyncio 踩坑经验</li>
<li>理解 回调、协程、绿程(Green-Thread)、线程对比总结</li>
<li>掌握 多进程、多线程、协程各自的适用场景</li>
<li>了解 Gevent/libev、uvloop/libuv 与asyncio的区别和联系</li>
<li>掌握 Python异步编程的一些指导细则</li>
</ul>
<h2 id="1_什么是异步编程">1 什么是异步编程</h2><p>通过学习相关概念,我们逐步解释异步编程是什么。</p>
<h3 id="1-1_阻塞">1.1 阻塞</h3><ul>
<li>程序未得到所需计算资源时被挂起的状态。</li>
<li><strong>程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在该操作上是阻塞的。</strong></li>
<li>常见的阻塞形式有:网络I/O阻塞、磁盘I/O阻塞、用户输入阻塞等。</li>
</ul>
<p>阻塞是无处不在的,包括CPU切换上下文时,所有的进程都无法真正干事情,它们也会被阻塞。(如果是多核CPU则正在执行上下文切换操作的核不可被利用。)</p>
<h3 id="1-2_非阻塞">1.2 非阻塞</h3><ul>
<li><strong>程序在等待某操作过程中,自身不被阻塞,可以继续运行干别的事情,则称该程序在该操作上是非阻塞的。</strong></li>
<li>非阻塞并<strong>不是</strong>在任何程序级别、任何情况下都可以存在的。</li>
<li>仅当程序封装的级别可以囊括独立的子程序单元时,它才可能存在非阻塞状态。</li>
</ul>
<p>非阻塞的存在是因为阻塞存在,正因为某个操作阻塞导致的耗时与效率低下,我们才要把它变成非阻塞的。</p>
<h3 id="1-3_同步">1.3 同步</h3><ul>
<li>不同程序单元为了完成某个任务,在执行过程中需靠某种通信方式以<strong>协调一致</strong>,称这些程序单元是同步执行的。</li>
<li>例如购物系统中更新商品库存,需要用“行锁”作为通信信号,让不同的更新请求强制排队顺序执行,那更新库存的操作是同步的。</li>
<li>简言之,<strong>同步意味着有序</strong>。</li>
</ul>
<h3 id="1-4_异步">1.4 异步</h3><ul>
<li>为完成某个任务,不同程序单元之间<strong>过程中无需通信协调</strong>,也能完成任务的方式。</li>
<li>不相关的程序单元之间可以是异步的。</li>
<li>例如,爬虫下载网页。调度程序调用下载程序后,即可调度其他任务,而无需与该下载任务保持通信以协调行为。不同网页的下载、保存等操作都是无关的,也无需相互通知协调。这些异步操作的完成时刻并不确定。</li>
<li>简言之,<strong>异步意味着无序</strong>。</li>
</ul>
<p>上文提到的“通信方式”通常是指异步和并发编程提供的同步原语,如信号量、锁、同步队列等等。我们需知道,虽然这些通信方式是为了让多个程序在一定条件下同步执行,但正因为是异步的存在,才需要这些通信方式。如果所有程序都是按序执行,其本身就是同步的,又何需这些同步信号呢?</p>
<h3 id="1-5_并发">1.5 并发</h3><ul>
<li>并发描述的是程序的组织结构。指程序要被设计成多个可独立执行的子任务。</li>
<li><strong>以利用有限的计算机资源使多个任务可以被实时或近实时执行为目的。</strong></li>
</ul>
<h3 id="1-6_并行">1.6 并行</h3><ul>
<li>并行描述的是程序的执行状态。指多个任务同时被执行。</li>
<li><strong>以利用富余计算资源(多核CPU)加速完成多个任务为目的。</strong></li>
</ul>
<p>并发提供了一种程序组织结构方式,让问题的解决方案可以并行执行,但并行执行不是必须的。</p>
<h3 id="1-7_概念总结">1.7 概念总结</h3><ul>
<li><strong>并行</strong>是为了利用多核加速多任务完成的进度</li>
<li><strong>并发</strong>是为了让独立的子任务都有机会被尽快执行,但不一定能加速整体进度</li>
<li><strong>非阻塞</strong>是为了提高程序整体执行效率</li>
<li><strong>异步</strong>是高效地组织非阻塞任务的方式</li>
</ul>
<p>要支持并发,必须拆分为多任务,不同任务相对而言才有阻塞/非阻塞、同步/异步。所以,并发、异步、非阻塞三个词总是如影随形。</p>
<h3 id="1-8_异步编程">1.8 异步编程</h3><ul>
<li><strong>以进程、线程、协程、函数/方法作为执行任务程序的基本单位,结合回调、事件循环、信号量等机制,以提高程序整体执行效率和并发能力的编程方式。</strong></li>
</ul>
<p>如果在某程序的运行时,能根据已经执行的指令准确判断它接下来要进行哪个具体操作,那它是同步程序,反之则为异步程序。(无序与有序的区别)</p>
<p>同步/异步、阻塞/非阻塞并非水火不容,要看讨论的程序所处的封装级别。例如购物程序在处理多个用户的浏览请求可以是异步的,而更新库存时必须是同步的。</p>
<h3 id="1-9_异步之难(nán)">1.9 异步之难(nán)</h3><ul>
<li>控制不住“计几”写的程序,因为其执行顺序不可预料,<strong>当下正要发生什么事件不可预料</strong>。在并行情况下更为复杂和艰难。</li>
</ul>
<p>所以,几乎所有的异步框架都将异步编程模型<strong>简化</strong>:<strong>一次只允许处理一个事件</strong>。故而有关异步的讨论几乎都集中在了单线程内。</p>
<ul>
<li>如果某事件处理程序需要长时间执行,所有其他部分都会被阻塞。</li>
</ul>
<p>所以,<strong>一旦采取异步编程,每个异步调用必须“足够小”</strong>,不能耗时太久。如何拆分异步任务成了难题。</p>
<ul>
<li>程序下一步行为往往依赖上一步执行结果,如何知晓上次异步调用已完成并获取结果?</li>
<li><strong>回调(Callback)</strong>成了必然选择。那又需要面临“回调地狱”的折磨。</li>
<li>同步代码改为异步代码,必然破坏代码结构。</li>
<li>解决问题的逻辑也要转变,不再是一条路走到黑,需要精心安排异步任务。</li>
</ul>
<h2 id="2_苦心异步为哪般">2 苦心异步为哪般</h2><p>如上文所述,异步编程面临诸多难点,Python 之父亲自上阵打磨4年才使 asyncio 模块在Python 3.6中“转正”,如此苦心为什么?答案只有一个:它值得!下面我们看看为何而值得。</p>
<h3 id="2-1_CPU的时间观">2.1 CPU的时间观</h3><p><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/01_cpu_time.png" alt="cpu_time"></p>
<p>我们将一个 2.6GHz 的 CPU 拟人化,假设它执行一条命令的时间,他它感觉上过了一秒钟。CPU是计算机的处理核心,也是最宝贵的资源,如果有浪费CPU的运行时间,导致其利用率不足,那程序效率必然低下(因为实际上有资源可以使效率更高)。</p>
<p>如上图所示,在千兆网上传输2KB数据,CPU感觉过了14个小时,如果是在10M的公网上呢?那效率会低百倍!如果在这么长的一段时间内,CPU只是傻等结果而不能去干其他事情,是不是在浪费CPU的青春?</p>
<p>鲁迅说,浪费“CPU”的时间等于谋财害命。而凶手就是程序猿。</p>
<h3 id="2-2_面临的问题">2.2 面临的问题</h3><ul>
<li><strong>成本问题</strong></li>
</ul>
<p>如果一个程序不能有效利用一台计算机资源,那必然需要更多的计算机通过运行更多的程序实例来弥补需求缺口。例如爬虫组数据流系统在改版后,由原来的7台服务器削减至3台,成本骤降57%。一台AWS m4.xlarge 型通用服务器按需付费实例一年价格约 1.2 万人民币。</p>
<ul>
<li><strong>效率问题</strong></li>
</ul>
<p>如果不在乎钱的消耗,那也会在意效率问题。当服务器数量堆叠到一定规模后,如果不改进软件架构和实现,加机器是徒劳,而且运维成本会骤然增加。比如别人家的电商平台支持6000单/秒支付,而自家在下单量才支撑2000单/秒,在双十一这种活动的时候,钱送上门也赚不到。</p>
<ul>
<li><strong>C10k/C10M挑战</strong></li>
</ul>
<p>C10k(concurrently handling 10k connections)是一个在1999年被提出来的技术挑战,如何在一颗1GHz CPU,2G内存,1gbps网络环境下,让单台服务器同时为1万个客户端提供FTP服务。而到了2010年后,随着硬件技术的发展,这个问题被延伸为C10M,即如何利用8核心CPU,64G内存,在10gbps的网络上保持1000万并发连接,或是每秒钟处理100万的连接。(两种类型的计算机资源在各自的时代都约为1200美元)</p>
<p>成本和效率问题是从企业经营角度讲,C10k/C10M问题则是从技术角度出发挑战软硬件极限。C10k/C10M 问题得解,成本问题和效率问题迎刃而解。</p>
<h3 id="2-3_解决方案">2.3 解决方案</h3><p>《约束理论与企业优化》中指出:“<strong>除了瓶颈之外,任何改进都是幻觉。</strong>”</p>
<p>CPU告诉我们,它自己很快,而上下文切换慢、内存读数据慢、磁盘寻址与取数据慢、网络传输慢……总之,离开CPU 后的一切,除了一级高速缓存,都很慢。我们观察计算机的组成可以知道,主要由运算器、控制器、存储器、输入设备、输出设备五部分组成。运算器和控制器主要集成在CPU中,除此之外全是I/O,包括读写内存、读写磁盘、读写网卡全都是I/O。<strong>I/O成了最大的瓶颈</strong>。</p>
<p>异步程序可以提高效率,而最大的瓶颈在I/O,业界诞生的解决方案没出意料:<strong>异步I/O吧,异步I/O吧,异步I/O吧吧!</strong></p>
<h2 id="3_异步I/O进化之路">3 异步I/O进化之路</h2><p>如今,地球上最发达、规模最庞大的计算机程序,莫过于因特网。而从CPU的时间观中可知,<strong>网络I/O是最大的I/O瓶颈</strong>,除了宕机没有比它更慢的。所以,诸多异步框架都对准的是网络I/O。</p>
<p>我们从一个爬虫例子说起,从因特网上下载10篇网页。</p>
<h3 id="3-1_同步阻塞方式">3.1 同步阻塞方式</h3><p>最容易想到的解决方案就是依次下载,从建立socket连接到发送网络请求再到读取响应数据,顺序进行。<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/02_blocking.png" alt="blocking"></p>
<p>注:总体耗时约为4.5秒。(因网络波动每次测试结果有所变动,本文取多次平均值)</p>
<p>如上图所示,<code>blocking_way()</code> 的作用是建立 socket 连接,发送HTTP请求,然后从 socket 读取HTTP响应并返回数据。示例中我们请求了 example.com 的首页。在<code>sync_way()</code> 执行了10次,即下载 example.com 首页10次。</p>
<p>在示例代码中有两个关键点。一是第10行的 <code>sock.connect(('example.com', 80))</code>,该调用的作用是向<code>example.com</code>主机的<code>80</code>端口发起网络连接请求。 二是第14行、第18行的<code>sock.recv(4096)</code>,该调用的作用是从socket上读取4K字节数据。</p>
<p>我们知道,创建网络连接,多久能创建完成不是客户端决定的,而是由网络状况和服务端处理能力共同决定。服务端什么时候返回了响应数据并被客户端接收到可供程序读取,也是不可预测的。<strong>所以<code>sock.connect()</code>和<code>sock.recv()</code>这两个调用在默认情况下是阻塞的。</strong></p>
<blockquote>
<p>注:<code>sock.send()</code>函数并不会阻塞太久,它只负责将请求数据拷贝到TCP/IP协议栈的系统缓冲区中就返回,并不等待服务端返回的应答确认。</p>
</blockquote>
<p>假设网络环境很差,创建网络连接需要1秒钟,那么<code>sock.connect()</code>就得阻塞1秒钟,等待网络连接成功。这1秒钟对一颗2.6GHz的CPU来讲,仿佛过去了83年,然而它不能干任何事情。<code>sock.recv()</code>也是一样的必须得等到服务端的响应数据已经被客户端接收。我们下载10篇网页,这个阻塞过程就得重复10次。如果一个爬虫系统每天要下载1000万篇网页呢?!</p>
<p>上面说了很多,我们力图说明一件事:<strong>同步阻塞的网络交互方式,效率低十分低下。</strong>特别是在网络交互频繁的程序中。这种方式根本<strong>不可能</strong>挑战C10K/C10M。</p>
<h3 id="3-2_改进方式:多进程">3.2 改进方式:多进程</h3><p>在一个程序内,依次执行10次太耗时,那开10个一样的程序同时执行不就行了。于是我们想到了多进程编程。<strong>为什么我们会先想到多进程呢</strong>?发展脉络如此。在更早的操作系统(Linux 2.4)及其以前,进程是 OS 调度任务的实体,是面向进程设计的OS。<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/03_multiproc.png" alt="multiproc"></p>
<p>注:总体耗时约为 0.6 秒。</p>
<p>改善效果立竿见影。但仍然有问题。总体耗时并没有缩减到原来的十分之一,而是九分之一左右,还有一些时间耗到哪里去了?<strong>进程切换开销</strong>。</p>
<p>进程切换开销不止像“CPU的时间观”所列的“上下文切换”那么低。CPU从一个进程切换到另一个进程,需要把旧进程运行时的寄存器状态、内存状态全部保存好,再将另一个进程之前保存的数据恢复。对CPU来讲,几个小时就干等着。<strong>当进程数量大于CPU核心数量时,进程切换是必然需要的。</strong></p>
<p>除了切换开销,多进程还有另外的缺点。一般的服务器在能够稳定运行的前提下,可以同时处理的进程数在数十个到数百个规模。如果进程数量规模更大,系统运行将不稳定,而且可用内存资源往往也会不足。</p>
<p>多进程解决方案在面临每天需要成百上千万次下载任务的爬虫系统,或者需要同时搞定数万并发的电商系统来说,并不适合。</p>
<p>除了<strong>切换开销大</strong>,以及<strong>可支持的任务规模小</strong>之外,多进程还有其他缺点,如状态共享等问题,后文会有提及,此处不再细究。</p>
<h3 id="3-3_继续改进:多线程">3.3 继续改进:多线程</h3><p>由于线程的数据结构比进程更轻量级,同一个进程可以容纳多个线程,从进程到线程的优化由此展开。后来的OS也把调度单位由进程转为线程,进程只作为线程的容器,用于管理进程所需的资源。而且OS级别的线程是可以被分配到不同的CPU核心同时运行的。<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/04_multithread.png" alt="multithread"></p>
<p>注:总体运行时间约0.43秒。</p>
<p>结果符合我们预期,比多进程耗时要少些。从运行时间上看,多线程似乎已经解决了切换开销大的问题。而且可支持的任务数量规模,也变成了数百个到数千个。</p>
<p>但是,多线程仍有问题,特别是Python里的多线程。首先,Python中的多线程因为GIL的存在,它们并不能利用CPU多核优势,<strong>一个Python进程中,只允许有一个线程处于运行状态</strong>。那为什么结果还是如预期,耗时缩减到了十分之一?</p>
<p>因为在做阻塞的系统调用时,例如<code>sock.connect()</code>,<code>sock.recv()</code>时,当前线程会释放GIL,让别的线程有执行机会。但是单个线程内,在阻塞调用上还是阻塞的。</p>
<blockquote>
<p>小提示:Python中 time.sleep 是阻塞的,都知道使用它要谨慎,但在多线程编程中,time.sleep 并不会阻塞其他线程。</p>
</blockquote>
<p>除了GIL之外,所有的多线程还有通病。它们是被OS调度,调度策略是抢占式的,以保证同等优先级的线程都有均等的执行机会,那带来的问题是:并不知道下一时刻是哪个线程被运行,也不知道它正要执行的代码是什么。所以就可能存在<strong>竞态条件</strong>。</p>
<p>例如爬虫工作线程从任务队列拿待抓取URL的时候,如果多个爬虫线程同时来取,那这个任务到底该给谁?那就需要用到“锁”或“同步队列”来保证下载任务不会被重复执行。</p>
<p>而且线程支持的多任务规模,在数百到数千的数量规模。在大规模的高频网络交互系统中,仍然有些吃力。当然,<strong>多线程最主要的问题还是竞态条件</strong>。</p>
<h3 id="3-4_非阻塞方式">3.4 非阻塞方式</h3><p>终于,我们来到了非阻塞解决方案。先来看看最原始的非阻塞如何工作的。<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/05_nonblocking.png" alt="nonblocking"></p>
<p>注:总体耗时约4.3秒。</p>
<p>首先注意到两点,就感觉被骗了。一是耗时与同步阻塞相当,二是代码更复杂。要非阻塞何用?且慢。</p>
<p>上图第9行代码<code>sock.setblocking(False)</code>告诉OS,让socket上阻塞调用都改为非阻塞的方式。之前我们说到,非阻塞就是在做一件事的时候,不阻碍调用它的程序做别的事情。上述代码在执行完 <code>sock.connect()</code> 和 <code>sock.recv()</code> 后的确不再阻塞,可以继续往下执行请求准备的代码或者是执行下一次读取。</p>
<p>代码变得更复杂也是上述原因所致。第11行要放在<code>try</code>语句内,是因为<code>socket</code>在发送非阻塞连接请求过程中,系统底层也会抛出异常。<code>connect()</code>被调用之后,立即可以往下执行第15和16行的代码。</p>
<p>需要<code>while</code>循环不断尝试 <code>send()</code>,是因为<code>connect()</code>已经非阻塞,在<code>send()</code>之时并不知道 socket 的连接是否就绪,只有不断尝试,尝试成功为止,即发送数据成功了。<code>recv()</code>调用也是同理。</p>
<p><strong>虽然 <code>connect()</code> 和 <code>recv()</code> 不再阻塞主程序,空出来的时间段CPU没有空闲着,但并没有利用好这空闲去做其他有意义的事情,而是在循环尝试读写 socket (不停判断非阻塞调用的状态是否就绪)。还得处理来自底层的可忽略的异常。也不能同时处理多个 socket 。</strong></p>
<p>然后10次下载任务仍然按序进行。所以总体执行时间和同步阻塞相当。如果非得这样子,那还不如同步阻塞算了。</p>
<h3 id="3-5_非阻塞改进">3.5 非阻塞改进</h3><h4 id="3-5-1_epoll">3.5.1 epoll</h4><p>判断非阻塞调用是否就绪如果 OS 能做,是不是应用程序就可以不用自己去等待和判断了,就可以利用这个空闲去做其他事情以提高效率。</p>
<p>所以<strong>OS将I/O状态的变化都封装成了事件</strong>,如可读事件、可写事件。并且<strong>提供了专门的系统模块让应用程序可以接收事件通知</strong>。这个模块就是<code>select</code>。让应用程序可以通过<code>select</code>注册文件描述符和回调函数。当文件描述符的状态发生变化时,<code>select</code> 就调用事先注册的回调函数。</p>
<p><code>select</code>因其算法效率比较低,后来改进成了<code>poll</code>,再后来又有进一步改进,BSD内核改进成了<code>kqueue</code>模块,而Linux内核改进成了<code>epoll</code>模块。这四个模块的作用都相同,暴露给程序员使用的API也几乎一致,区别在于<code>kqueue</code> 和 <code>epoll</code> 在处理大量文件描述符时效率更高。</p>
<p>鉴于 Linux 服务器的普遍性,以及为了追求更高效率,所以我们常常听闻被探讨的模块都是 <code>epoll</code> 。</p>
<h4 id="3-5-2_回调(Callback)">3.5.2 回调(Callback)</h4><p>把I/O事件的等待和监听任务交给了 OS,那 OS 在知道I/O状态发生改变后(例如socket连接已建立成功可发送数据),它又怎么知道接下来该干嘛呢?<strong>只能回调</strong>。</p>
<p>需要我们将发送数据与读取数据封装成独立的函数,让<code>epoll</code>代替应用程序监听<code>socket</code>状态时,得告诉<code>epoll</code>:“如果<code>socket</code>状态变为可以往里写数据(连接建立成功了),请调用HTTP请求发送函数。如果<code>socket</code> 变为可以读数据了(客户端已收到响应),请调用响应处理函数。”</p>
<p>于是我们利用<code>epoll</code>结合回调机制重构爬虫代码:<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/06_callback.png" alt="callback"></p>
<p>此处和前面稍有不同的是,我们将下载不同的10个页面,相对URL路径存放于<code>urls_todo</code>集合中。现在看看改进在哪。</p>
<p>首先,不断尝试<code>send()</code> 和 <code>recv()</code> 的两个循环被消灭掉了。</p>
<p>其次,导入了<code>selectors</code>模块,并创建了一个<code>DefaultSelector</code> 实例。Python标准库提供的<code>selectors</code>模块是对底层<code>select/poll/epoll/kqueue</code>的封装。<code>DefaultSelector</code>类会根据 OS 环境自动选择最佳的模块,那在 Linux 2.5.44 及更新的版本上都是<code>epoll</code>了。</p>
<p>然后,在第25行和第31行分别注册了<code>socket</code>可写事件(<code>EVENT_WRITE</code>)和可读事件(<code>EVENT_READ</code>)发生后应该采取的回调函数。</p>
<p>虽然代码结构清晰了,阻塞操作也交给OS去等待和通知了,但是,我们要抓取10个不同页面,就得创建10个<code>Crawler</code>实例,就有20个事件将要发生,那如何从<code>selector</code>里获取当前正发生的事件,并且得到对应的回调函数去执行呢?</p>
<h4 id="3-5-3_事件循环(Event_Loop)">3.5.3 事件循环(Event Loop)</h4><p>为了解决上述问题,那我们只得采用老办法,写一个循环,去访问<code>selector</code>模块,等待它告诉我们当前是哪个事件发生了,应该对应哪个回调。<strong>这个等待事件通知的循环,称之为事件循环</strong>。<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/07_ioloop.png" alt="ioloop"></p>
<p>上述代码中,我们用<code>stopped</code>全局变量控制事件循环何时停止。当<code>urls_todo</code>消耗完毕后,会标记<code>stopped</code>为<code>True</code>。</p>
<p>重要的是第49行代码,<strong><code>selector.select()</code> 是一个阻塞调用</strong>,因为如果事件不发生,那应用程序就没事件可处理,所以就干脆阻塞在这里等待事件发生。那可以推断,如果只下载一篇网页,一定要<code>connect()</code>之后才能<code>send()</code>继而<code>recv()</code>,那它的效率和阻塞的方式是一样的。因为不在<code>connect()/recv()</code>上阻塞,也得在<code>select()</code>上阻塞。</p>
<p>所以,<strong><code>selector</code>机制(后文以此称呼代指<code>epoll/kqueue</code>)是设计用来解决大量并发连接的</strong>。当系统中有大量非阻塞调用,能随时产生事件的时候,<code>selector</code>机制才能发挥最大的威力。</p>
<p>下面是如何启创建10个下载任务和启动事件循环的:<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/08_useloop.png" alt="useloop"></p>
<p>注:总体耗时约0.45秒。</p>
<p>上述执行结果令人振奋。在单线程内用 <strong>事件循环+回调</strong> 搞定了10篇网页同时下载的问题。这,已经是<strong>异步编程</strong>了。虽然有一个<code>for</code> 循环顺序地创建<code>Crawler</code> 实例并调用 <code>fetch</code> 方法,但是<code>fetch</code> 内仅有<code>connect()</code>和注册可写事件,而且从执行时间明显可以推断,多个下载任务确实在同时进行!</p>
<p>上述代码异步执行的过程:</p>
<ol>
<li>创建<code>Crawler</code> 实例;</li>
<li>调用<code>fetch</code>方法,会创建<code>socket</code>连接和在<code>selector</code>上注册可写事件;</li>
<li><code>fetch</code>内并无阻塞操作,该方法立即返回;</li>
<li>重复上述3个步骤,将10个不同的下载任务都加入事件循环;</li>
<li>启动事件循环,进入第1轮循环,阻塞在事件监听上;</li>
<li>当某个下载任务<code>EVENT_WRITE</code>被触发,回调其<code>connected</code>方法,第一轮事件循环结束;</li>
<li>进入第2轮事件循环,当某个下载任务有事件触发,执行其回调函数;此时已经不能推测是哪个事件发生,因为有可能是上次<code>connected</code>里的<code>EVENT_READ</code>先被触发,也可能是其他某个任务的<code>EVENT_WRITE</code>被触发;(<strong>此时,原来在一个下载任务上会阻塞的那段时间被利用起来执行另一个下载任务了</strong>)</li>
<li>循环往复,直至所有下载任务被处理完成</li>
<li>退出事件循环,结束整个下载程序</li>
</ol>
<h4 id="3-5-4_总结">3.5.4 总结</h4><p>目前为止,我们已经从同步阻塞学习到了异步非阻塞。掌握了在单线程内同时并发执行多个网络I/O阻塞型任务的黑魔法。而且与多线程相比,连线程切换都没有了,执行回调函数是函数调用开销,在线程的栈内完成,因此性能也更好,单机支持的任务规模也变成了数万到数十万个。(不过我们知道:没有免费午餐,也没有银弹。)</p>
<p>部分编程语言中,对异步编程的支持就止步于此(不含语言官方之外的扩展)。需要程序猿直接使用<code>epoll</code>去注册事件和回调、维护一个事件循环,然后大多数时间都花在设计回调函数上。</p>
<p>通过本节的学习,我们应该认识到,不论什么编程语言,但凡要做异步编程,上述的“事件循环+回调”这种模式是逃不掉的,尽管它可能用的不是<code>epoll</code>,也可能不是<code>while</code>循环。如果你找到了一种不属于 “<strong>等会儿告诉你</strong>” 模型的异步方式,请立即给我打电话(注意,打电话是Call)。</p>
<p>为什么我们在某些异步编程中并没有看到 CallBack 模式呢?这就是我们接下来要探讨的问题。本节是学习异步编程的一个终点,也是另一个起点。毕竟咱们讲 Python 异步编程,还没提到其主角<strong>协程</strong>的用武之地。</p>
<h2 id="4_Python_对异步I/O的优化之路">4 Python 对异步I/O的优化之路</h2><p>我们将在本节学习到 Python 生态对异步编程的支持是如何继承前文所述的“<strong>事件循环+回调</strong>”模式演变到<code>asyncio</code>的原生协程模式。</p>
<h3 id="4-1_回调之痛,以终为始">4.1 回调之痛,以终为始</h3><p>在第3节中,我们已经学会了“<strong>事件循环+回调</strong>”的基本运行原理,可以基于这种方式在单线程内实现异步编程。也确实能够大大提高程序运行效率。但是,刚才所学的只是最基本的,然而在生产项目中,要应对的复杂度会大大增加。考虑如下问题:</p>
<ul>
<li>如果回调函数执行不正常该如何?</li>
<li>如果回调里面还要嵌套回调怎么办?要嵌套很多层怎么办?</li>
<li>如果嵌套了多层,其中某个环节出错了会造成什么后果?</li>
<li>如果有个数据需要被每个回调都处理怎么办?</li>
<li>……</li>
</ul>
<p>在实际编程中,上述系列问题不可避免。在这些问题的背后隐藏着回调编程模式的一些<strong>缺点</strong>:</p>
<ul>
<li><p><strong>回调层次过多时代码可读性差</strong></p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">callback_1</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="comment"># processing ...</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">callback_2</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="comment"># processing.....</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">callback_3</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="comment"># processing ....</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">callback_4</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="comment">#processing .....</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">callback_5</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="comment"># processing ......</span></span><br><span class="line"> async_function(callback_5)</span><br><span class="line"> async_function(callback_4)</span><br><span class="line"> async_function(callback_3)</span><br><span class="line"> async_function(callback_2)</span><br><span class="line">async_function(callback_1)</span><br></pre></td></tr></table></figure>
</li>
<li><p><strong>破坏代码结构</strong><br>写同步代码时,关联的操作时自上而下运行:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">do_a()</span><br><span class="line">do_b()</span><br></pre></td></tr></table></figure>
</li>
</ul>
<p>如果 b 处理依赖于 a 处理的结果,而 a 过程是异步调用,就不知 a 何时能返回值,需要将后续的处理过程以callback的方式传递给 a ,让 a 执行完以后可以执行 b。代码变化为:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">do_a(do_b())</span><br></pre></td></tr></table></figure></p>
<p>如果整个流程中全部改为异步处理,而流程比较长的话,代码逻辑就会成为这样:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">do_a(do_b(do_c(do_d(do_e(do_f(......))))))</span><br></pre></td></tr></table></figure></p>
<p>上面实际也是回调地狱式的风格,但这不是主要矛盾。主要在于,原本从上而下的代码结构,要改成从外到内的。先a,再b,再c,…,直到最内层 f 执行完成。在同步版本中,执行完a后执行b,这是线程的指令指针控制着的流程,而在回调版本中,流程就是程序猿需要注意和安排的。</p>
<ul>
<li><p><strong>共享状态管理困难</strong><br>回顾第3节爬虫代码,同步阻塞版的<code>sock</code>对象从头使用到尾,而在回调的版本中,我们必须在<code>Crawler</code>实例化后的对象<code>self</code>里保存它自己的<code>sock</code>对象。如果不是采用OOP的编程风格,那需要把要共享的状态接力似的传递给每一个回调。多个异步调用之间,到底要共享哪些状态,事先就得考虑清楚,精心设计。</p>
</li>
<li><p><strong>错误处理困难</strong><br>一连串的回调构成一个完整的调用链。例如上述的 a 到 f。假如 d 抛了异常怎么办?整个调用链断掉,接力传递的状态也会丢失,这种现象称为<strong>调用栈撕裂</strong>。 c 不知道该干嘛,继续异常,然后是 b 异常,接着 a 异常。好嘛,报错日志就告诉你,a 调用出错了,但实际是 d 出错。<strong>所以,为了防止栈撕裂,异常必须以数据的形式返回,而不是直接抛出异常,然后每个回调中需要检查上次调用的返回值,以防错误吞没。</strong></p>
</li>
</ul>
<p>如果说代码风格难看是小事,但栈撕裂和状态共享困难这两个缺点会让基于回调的异步编程很艰难。所以不同编程语言的生态都在致力于解决这个问题。才诞生了后来的<code>Promise</code>、<code>Co-routine</code>等解决方案。</p>
<p>Python 生态也以终为始,秉承着“程序猿不必难程序猿”的原则,让语言和框架开发者苦逼一点,也要让应用开发者舒坦。在<strong>事件循环+回调</strong>的基础上衍生出了基于协程的解决方案,代表作有 Tornado、Twisted、asyncio 等。接下来我们随着 Python 生态异步编程的发展过程,深入理解Python异步编程。</p>
<h3 id="4-2_核心问题">4.2 核心问题</h3><p>通过前面的学习,我们清楚地认识到异步编程最大的困难:异步任务何时执行完毕?接下来要对异步调用的返回结果做什么操作?</p>
<p>上述问题我们已经通过事件循环和回调解决了。但是回调会让程序变得复杂。要异步,必回调,又是否有办法规避其缺点呢?那需要弄清楚其本质,为什么回调是必须的?还有使用回调时克服的那些缺点又是为了什么?</p>
<p>答案是程序为了知道自己已经干了什么?正在干什么?将来要干什么?<strong>换言之,程序得知道当前所处的状态,而且要将这个状态在不同的回调之间延续下去。</strong></p>
<p>多个回调之间的状态管理困难,那让每个回调都能管理自己的状态怎么样?链式调用会有栈撕裂的困难,让回调之间不再链式调用怎样?不链式调用的话,那又如何让被调用者知道已经完成了?那就让这个回调通知那个回调如何?而且一个回调,不就是一个待处理任务吗?</p>
<p>任务之间得相互通知,每个任务得有自己的状态。那不就是很古老的编程技法:协作式多任务?然而要在单线程内做调度,<strong>啊哈,协程!</strong>每个协程具有自己的栈帧,当然能知道自己处于什么状态,协程之间可以协作那自然可以通知别的协程。</p>
<h3 id="4-3_协程">4.3 协程</h3><ul>
<li><strong>协程(Co-routine),即是协作式的例程。</strong></li>
</ul>
<p>它是非抢占式的多任务子例程的概括,可以允许有多个入口点在例程中确定的位置来控制程序的暂停与恢复执行。</p>
<p>例程是什么?编程语言定义的可被调用的代码段,为了完成某个特定功能而封装在一起的一系列指令。一般的编程语言都用称为函数或方法的代码结构来体现。</p>
<h3 id="4-4_基于生成器的协程">4.4 基于生成器的协程</h3><p>早期的 Pythoner 发现 Python 中有种特殊的对象——生成器(Generator),它的特点和协程很像。每一次迭代之间,会暂停执行,继续下一次迭代的时候还不会丢失先前的状态。</p>
<p>为了支持用生成器做简单的协程,Python 2.5 对生成器进行了增强(PEP 342),该增强提案的标题是 “Coroutines via Enhanced Generators”。有了PEP 342的加持,生成器可以通过<code>yield</code> 暂停执行和向外返回数据,也可以通过<code>send()</code>向生成器内发送数据,还可以通过<code>throw()</code>向生成器内抛出异常以便随时终止生成器的运行。</p>
<p>接下来,我们用基于生成器的协程来重构先前的爬虫代码。</p>
<h4 id="4-4-1_未来对象(Future)">4.4.1 未来对象(Future)</h4><p>不用回调的方式了,怎么知道异步调用的结果呢?先设计一个对象,异步调用执行完的时候,就把结果放在它里面。这种对象称之为未来对象。<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/09_future.png" alt="future"></p>
<p>未来对象有一个<code>result</code>属性,用于存放未来的执行结果。还有个<code>set_result()</code>方法,是用于设置<code>result</code>的,并且会在给<code>result</code>绑定值以后运行事先给<code>future</code>添加的回调。回调是通过未来对象的<code>add_done_callback()</code>方法添加的。</p>
<p>不要疑惑此处的<code>callback</code>,说好了不回调的嘛?难道忘了我们曾经说的<strong>要异步,必回调</strong>。不过也别急,此处的回调,和先前学到的回调,还真有点不一样。</p>
<h4 id="4-4-2_重构_Crawler">4.4.2 重构 Crawler</h4><p>现在不论如何,我们有了未来对象可以代表未来的值。先用<code>Future</code>来重构爬虫代码。<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/10_gen_crawler.png" alt="gen_crawler"></p>
<p>和先前的回调版本对比,已经有了较大差异。<code>fetch</code> 方法内有了<code>yield</code>表达式,使它成为了生成器。我们知道生成器需要先调用<code>next()</code>迭代一次或者是先<code>send(None)</code>启动,遇到<code>yield</code>之后便暂停。那这<code>fetch</code>生成器如何再次恢复执行呢?至少 <code>Future</code> 和 <code>Crawler</code>都没看到相关代码。</p>
<h4 id="4-4-3_任务对象(Task)">4.4.3 任务对象(Task)</h4><p>为了解决上述问题,我们只需遵循一个编程规则:单一职责,每种角色各司其职,如果还有工作没有角色来做,那就创建一个角色去做。没人来恢复这个生成器的执行么?没人来管理生成器的状态么?创建一个,就叫<code>Task</code>好了,很合适的名字。<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/11_task.png" alt="task"></p>
<p>上述代码中Task封装了<code>coro</code>对象,即初始化时传递给他的对象,被管理的任务是待执行的协程,故而这里的<code>coro</code>就是<code>fetch()</code>生成器。它还有个<code>step()</code>方法,在初始化的时候就会执行一遍。<code>step()</code>内会调用生成器的<code>send()</code>方法,初始化第一次发送的是<code>None</code>就驱动了<code>coro</code>即<code>fetch()</code>的第一次执行。</p>
<p><code>send()</code>完成之后,得到下一次的<code>future</code>,然后给下一次的<code>future</code>添加<code>step()</code>回调。原来<code>add_done_callback()</code>不是给写爬虫业务逻辑用的。此前的<code>callback</code>可就干的是业务逻辑呀。</p>
<p>再看<code>fetch()</code>生成器,其内部写完了所有的业务逻辑,包括如何发送请求,如何读取响应。而且注册给<code>selector</code>的回调相当简单,就是给对应的<code>future</code>对象绑定结果值。两个<code>yield</code>表达式都是返回对应的<code>future</code>对象,然后返回<code>Task.step()</code>之内,这样<code>Task</code>, <code>Future</code>, <code>Coroutine</code>三者精妙地串联在了一起。</p>
<p>初始化<code>Task</code>对象以后,把<code>fetch()</code>给驱动到了第44行<code>yied f</code>就完事了,接下来怎么继续?</p>
<h4 id="4-4-4_事件循环(Event_Loop)驱动协程运行">4.4.4 事件循环(Event Loop)驱动协程运行</h4><p>该事件循环上场了。接下来,只需等待已经注册的<code>EVENT_WRITE</code>事件发生。事件循环就像心脏一般,只要它开始跳动,整个程序就会持续运行。<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/12_evloop.png" alt="evloop"></p>
<p>注:总体耗时约0.43秒。</p>
<p>现在<code>loop</code>有了些许变化,<code>callback()</code>不再传递<code>event_key</code>和<code>event_mask</code>参数。也就是说,这里的回调根本不关心是谁触发了这个事件,结合<code>fetch()</code>可以知道,它只需完成对<code>future</code>设置结果值即可<code>f.set_result()</code>。而且<code>future</code>是谁它也不关心,因为<strong>协程</strong>能够保存自己的状态,知道自己的<code>future</code>是哪个。也不用关心到底要设置什么值,因为要设置什么值也是协程内安排的。</p>
<p><strong>此时的<code>loop()</code>,真的成了一个心脏,它只管往外泵血,不论这份血液是要输送给大脑还是要给脚趾,只要它还在跳动,生命就能延续。</strong></p>
<h4 id="4-4-5_生成器协程风格和回调风格对比总结">4.4.5 生成器协程风格和回调风格对比总结</h4><p>在回调风格中:</p>
<ul>
<li>存在链式回调(虽然示例中嵌套回调只有一层)</li>
<li>请求和响应也不得不分为两个回调以至于破坏了同步代码那种结构</li>
<li>程序员必须在回调之间维护必须的状态。</li>
</ul>
<p>还有更多示例中没有展示,但确实存在的问题,参见4.1节。</p>
<p>而基于生成器协程的风格:</p>
<ul>
<li>无链式调用</li>
<li><code>selector</code>的回调里只管给<code>future</code>设置值,不再关心业务逻辑</li>
<li><code>loop</code> 内回调<code>callback()</code>不再关注是谁触发了事件</li>
<li>已趋近于同步代码的结构</li>
<li>无需程序员在多个协程之间维护状态,例如哪个才是自己的<code>sock</code></li>
</ul>
<h4 id="4-4-6_碉堡了,但是代码很丑!能不能重构?">4.4.6 碉堡了,但是代码很丑!能不能重构?</h4><p>如果说<code>fetch</code>的容错能力要更强,业务功能也需要更完善,怎么办?而且技术处理的部分(socket相关的)和业务处理的部分(请求与返回数据的处理)混在一起。</p>
<ul>
<li>创建<code>socket</code>连接可以抽象复用吧?</li>
<li>循环读取整个<code>response</code>可以抽象复用吧?</li>
<li>循环内处理<code>socket.recv()</code>的可以抽象复用吧?</li>
</ul>
<p>但是这些关键节点的地方都有<code>yield</code>,抽离出来的代码也需要是生成器。而且<code>fetch()</code>自己也得是生成器。生成器里玩生成器,代码好像要写得更丑才可以……</p>
<p>Python 语言的设计者们也认识到了这个问题,再次秉承着“程序猿不必为难程序猿”的原则,他们捣鼓出了一个<code>yield from</code>来解决生成器里玩生成器的问题。</p>
<h3 id="4-5_用_yield_from_改进生成器协程">4.5 用 yield from 改进生成器协程</h3><h4 id="4-5-1_yield_from语法介绍">4.5.1 <code>yield from</code>语法介绍</h4><p><code>yield from</code> 是Python 3.3 新引入的语法(PEP 380)。它主要解决的就是在生成器里玩生成器不方便的问题。它有两大主要功能。</p>
<p>第一个功能是:让嵌套生成器不必通过循环迭代<code>yield</code>,而是直接<code>yield from</code>。以下两种在生成器里玩子生成器的方式是等价的。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">gen_one</span><span class="params">()</span>:</span></span><br><span class="line"> subgen = range(<span class="number">10</span>)</span><br><span class="line"> <span class="keyword">yield</span> <span class="keyword">from</span> subgen</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">gen_two</span><span class="params">()</span>:</span></span><br><span class="line"> subgen = range(<span class="number">10</span>)</span><br><span class="line"> <span class="keyword">for</span> item <span class="keyword">in</span> subgen:</span><br><span class="line"> <span class="keyword">yield</span> item</span><br></pre></td></tr></table></figure></p>
<p>第二个功能就是在子生成器和原生成器的调用者之间打开双向通道,两者可以直接通信。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">gen</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="keyword">yield</span> <span class="keyword">from</span> subgen()</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">subgen</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="keyword">while</span> <span class="keyword">True</span>:</span><br><span class="line"> x = <span class="keyword">yield</span></span><br><span class="line"> <span class="keyword">yield</span> x+<span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">()</span>:</span></span><br><span class="line"> g = gen()</span><br><span class="line"> next(g) <span class="comment"># 驱动生成器g开始执行到第一个 yield</span></span><br><span class="line"> retval = g.send(<span class="number">1</span>) <span class="comment"># 看似向生成器 gen() 发送数据</span></span><br><span class="line"> print(retval) <span class="comment"># 返回2</span></span><br><span class="line"> g.throw(StopIteration) <span class="comment"># 看似向gen()抛入异常</span></span><br></pre></td></tr></table></figure></p>
<p>通过上述代码清晰地理解了<code>yield from</code>的双向通道功能。关键字<code>yield from</code>在<code>gen()</code>内部为<code>subgen()</code>和<code>main()</code>开辟了通信通道。<code>main()</code>里可以直接将数据<code>1</code>发送给<code>subgen()</code>,<code>subgen()</code>也可以将计算后的数据<code>2</code>返回到<code>main()</code>里,<code>main()</code>里也可以直接向<code>subgen()</code>抛入异常以终止<code>subgen()</code>。</p>
<p>顺带一提,<code>yield from</code> 除了可以 <code>yield from <generator></code> 还可以 <code>yield from <iterable></code>。</p>
<h4 id="4-5-2_重构代码">4.5.2 重构代码</h4><p>抽象socket连接的功能:<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/13_yf_conn.png" alt="yf_conn"></p>
<p>抽象单次<code>recv()</code>和读取完整的response功能:<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/14_yf_read.png" alt="yf_read"></p>
<p>三个关键点的抽象已经完成,现在重构<code>Crawler</code>类:<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/15_yf_crawler.png" alt="yf_crawler"></p>
<p>上面代码整体来讲没什么问题,可复用的代码已经抽象出去,作为子生成器也可以使用 <code>yield from</code> 语法来获取值。但另外有个点需要<strong>注意</strong>:在第24和第35行返回future对象的时候,我们了<code>yield from f</code> 而不是原来的<code>yield f</code>。<code>yield</code>可以直接作用于普通Python对象,而<code>yield from</code>却不行,所以我们对<code>Future</code>还要进一步改造,把它变成一个<code>iterable</code>对象就可以了。<br><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/16_yf_future.png" alt="yf_future"></p>
<p>只是增加了<code>__iter__()</code>方法的实现。如果不把<code>Future</code>改成<code>iterable</code>也是可以的,还是用原来的<code>yield f</code>即可。那为什么需要改进呢?</p>
<p>首先,我们是在基于生成器做协程,而生成器还得是生成器,如果继续混用<code>yield</code>和<code>yield from</code> 做协程,代码可读性和可理解性都不好。其次,如果不改,协程内还得关心它等待的对象是否可被<code>yield</code>,如果协程里还想继续返回协程怎么办?如果想调用普通函数动态生成一个<code>Future</code>对象再返回怎么办?</p>
<p>所以,在Python 3.3 引入<code>yield from</code>新语法之后,就不再推荐用<code>yield</code>去做协程。全都使用<code>yield from</code>由于其双向通道的功能,可以让我们在协程间随心所欲地传递数据。</p>
<h4 id="4-5-3_yield_from改进协程总结">4.5.3 <code>yield from</code>改进协程总结</h4><p>用<code>yield from</code>改进基于生成器的协程,代码抽象程度更高。使业务逻辑相关的代码更精简。由于其双向通道功能可以让协程之间随心所欲传递数据,使Python异步编程的协程解决方案大大向前迈进了一步。</p>
<p>于是Python语言开发者们充分利用<code>yield from</code>,使 Guido 主导的Python异步编程框架<code>Tulip</code>迅速脱胎换骨,并迫不及待得让它在 Python 3.4 中换了个名字<code>asyncio</code>以“实习生”角色出现在标准库中。</p>
<h4 id="4-5-4_asyncio_介绍">4.5.4 asyncio 介绍</h4><p><code>asyncio</code>是Python 3.4 试验性引入的异步I/O框架(PEP 3156),提供了基于协程做异步I/O编写单线程并发代码的基础设施。其核心组件有事件循环(Event Loop)、协程(Coroutine)、任务(Task)、未来对象(Future)以及其他一些扩充和辅助性质的模块。</p>
<p>在引入<code>asyncio</code>的时候,还提供了一个装饰器<code>@asyncio.coroutine</code>用于装饰使用了<code>yield from</code>的函数,以标记其为协程。但并不强制使用这个装饰器。</p>
<p>虽然发展到 Python 3.4 时有了<code>yield from</code>的加持让协程更容易了,但是由于协程在Python中发展的历史包袱所致,很多人仍然弄不明白<strong>生成器</strong>和<strong>协程</strong>的联系与区别,也弄不明白<code>yield</code> 和 <code>yield from</code> 的区别。这种混乱的状态也违背Python之禅的一些准则。</p>
<p>于是Python设计者们又快马加鞭地在 3.5 中新增了<code>async/await</code>语法(PEP 492),对协程有了明确而显式的支持,称之为<strong>原生协程</strong>。<code>async/await</code> 和 <code>yield from</code>这两种风格的协程底层复用共同的实现,而且相互兼容。</p>
<p>在Python 3.6 中<code>asyncio</code>库“转正”,不再是实验性质的,成为标准库的正式一员。</p>
<h3 id="4-6_总结">4.6 总结</h3><p>行至此处,我们已经掌握了<code>asyncio</code>的核心原理,学习了它的原型,也学习了异步I/O在 CPython 官方支持的生态下是如何一步步发展至今的。</p>
<p>实际上,真正的<code>asyncio</code>比我们前几节中学到的要复杂得多,它还实现了零拷贝、公平调度、异常处理、任务状态管理等等使 Python 异步编程更完善的内容。理解原理和原型对我们后续学习有莫大的帮助。</p>
<h2 id="5_asyncio和原生协程初体验">5 asyncio和原生协程初体验</h2><p>本节中,我们将初步体验<code>asyncio</code>库和新增语法<code>async/await</code>给我们带来的便利。由于Python2-3的过度期间,Python3.0-3.4的使用者并不是太多,也为了不让更多的人困惑,也因为<code>aysncio</code>在3.6才转正,所以更深入学习<code>asyncio</code>库的时候我们将使用<code>async/await</code>定义的原生协程风格,<code>yield from</code>风格的协程不再阐述(实际上它们可用很小的代价相互代替)。</p>
<p><img src="http://imgj.metasotalaw.cn/static/images/asyncio/part1/17_aio.png" alt="aio"></p>
<p>对比生成器版的协程,使用asyncio库后变化很大:</p>
<ul>
<li>没有了<code>yield</code> 或 <code>yield from</code>,而是<code>async/await</code></li>
<li>没有了自造的<code>loop()</code>,取而代之的是<code>asyncio.get_event_loop()</code></li>
<li>无需自己在socket上做异步操作,不用显式地注册和注销事件,<code>aiohttp</code>库已经代劳</li>
<li>没有了显式的 <code>Future</code> 和 <code>Task</code>,<code>asyncio</code>已封装</li>
<li>更少量的代码,更优雅的设计</li>
</ul>
<p>说明:我们这里发送和接收HTTP请求不再自己操作<code>socket</code>的原因是,在实际做业务项目的过程中,要处理妥善地HTTP协议会很复杂,我们需要的是功能完善的异步HTTP客户端,业界已经有了成熟的解决方案,DRY不是吗?</p>
<p>和同步阻塞版的代码对比:</p>
<ul>
<li>异步化</li>
<li>代码量相当(引入aiohttp框架后更少)</li>
<li>代码逻辑同样简单,跟同步代码一样的结构、一样的逻辑</li>
<li>接近10倍的性能提升</li>
</ul>
<h2 id="结语">结语</h2><p>到此为止,我们已经深入地学习了异步编程是什么、为什么、在Python里是怎么样发展的。我们找到了一种让代码看起来跟同步代码一样简单,而效率却提升N倍(具体提升情况取决于项目规模、网络环境、实现细节)的异步编程方法。它也没有回调的那些缺点。</p>
<p>本系列教程接下来的一篇将是学习<code>asyncio</code>库如何的使用,快速掌握它的主要内容。后续我们还会深入探究<code>asyncio</code>的优点与缺点,也会探讨Python生态中其他异步I/O方案和<code>asyncio</code>的区别。</p>
<h2 id="附">附</h2><h4 id="参考资料">参考资料</h4><ul>
<li><a href="http://www.aosabook.org/en/500L/a-web-crawler-with-asyncio-coroutines.html" target="_blank" rel="external">《A Web Crawler With asyncio Coroutines》</a></li>
<li><a href="http://cizixs.com/2017/01/03/how-slow-is-disk-and-network" target="_blank" rel="external">《让 CPU 告诉你硬盘和网络到底有多慢》</a></li>
</ul>
<h4 id="相关代码">相关代码</h4><ul>
<li><a href="http://github.com/denglj/aiotutorial" target="_blank" rel="external">http://github.com/denglj/aiotutorial</a></li>
</ul>
<h4 id="请关注微信公众号驹说码事">请关注微信公众号驹说码事</h4><p><img src="http://imgj.metasotalaw.cn/jushuoms.jpg" alt="驹说码事"></p>
]]></content>
<summary type="html">
<![CDATA[<p>Python asyncio异步编程中文教程,只此一篇足矣,一览众山小!</p>
<p>彻底理解异步编程是什么、为什么、怎么样。深入学习asyncio的基本原理和原型,了解生成器、协程在Python异步编程中是如何发展的。<br>本文首发至微信公众号<strong>“驹说码事”(jushuoms)</strong>,欢迎关注以获取更多干货!<br>]]>
</summary>
<category term="asyncio" scheme="http://aju.space/tags/asyncio/"/>
<category term="python" scheme="http://aju.space/tags/python/"/>
<category term="协程" scheme="http://aju.space/tags/%E5%8D%8F%E7%A8%8B/"/>
<category term="并发" scheme="http://aju.space/tags/%E5%B9%B6%E5%8F%91/"/>
</entry>
<entry>
<title><![CDATA[阻塞、非阻塞、同步、异步]]></title>
<link href="http://aju.space/2016/09/20/blocking-nonblocking-sync-async.html"/>
<id>http://aju.space/2016/09/20/blocking-nonblocking-sync-async.html</id>
<published>2016-09-20T04:07:39.000Z</published>
<updated>2016-09-24T10:14:17.998Z</updated>
<content type="html"><![CDATA[<p>几个经常容易混淆的概念。特别是像异步与非阻塞,在某些情况下这两个术语常常混用。下面就我谈谈自己对这四个概念的理解,也解释了异步与非阻塞这两个词为何会形影不离。希望对这些概念迷惑,学习Python协程、并发相关的朋友有所帮助。</p>
<h2 id="探究事物的基本逻辑">探究事物的基本逻辑</h2><p><strong>当我们探讨事物的时候,一定要先界定清楚事物的时空界限以及其基本定义,也要先界定清楚探讨的事物/问题的级别或层次</strong>(在阿驹看来,世界上的任何东西都是具有分层/分级的,当我们层次分明的时候,很多架构就显得清晰明了)。<br><a id="more"></a><br>就像参考系不确定,怎敢断言一个物体是处于运动态还是静止态?<strong>而且当我们提升层次(抽象)和降低层次(分解)来分析问题的时候,一定要回到当初研究的级别上。否则逻辑都有问题,结论还怎可正确。</strong>就像研究人,可以分解到各个器官,甚至是各种细胞来研究人,但你最终要回到人本身这个角度,你不能说细胞的特性就是人性。</p>
<p>就讨论计算机程序设计与编写的角度来说,问题的级别与层次是什么呢?最基本的就是要确定我们讨论的是一条CPU指令?一个函数?一个类?一个模块?一个服务?一个线程?一个进程?一个操作系统?…还是你要下的结论与任何级别的程序都通用?在不同的级别下,有些概念是存在的,有些概念是不存在的。</p>
<p>还有就是界定清楚时空界限。比如常常谈论程序A与B的性能孰高孰低,架构其孰好孰坏等等。谈论者往往都会站在自己的主观角度,给程序套上自己常接触到的环境,比如没做过分布式应用的,拿着分布式应用在单体应用运行环境和场景中去比。就算功能、架构都定位相同的程序,也得划清一个界限来比较,比如执行时效?生存周期?部署规模?吞吐量?</p>
<p><strong>在我们谈论阻塞、非阻塞、同步、异步这几个概念的时候,也得先划定基本的前提条件,要在同一个级别、同样的时空里来探讨</strong>。后文,用“程序单元”这个说法,站在不同的级别,程序单元是不同的。当没有特别说明的时候,你可以认为程序单元就是你心中认为的那一个,当说某程序单元的上一级的时候,你心里要知道,如果先前认为的程序单元是线程,那它的上一级可以是进程,诸如此类。</p>
<h2 id="阻塞">阻塞</h2><p>阻塞与非阻塞的概念是针对一个程序单元自身而言。</p>
<p><strong>如果一个程序单元的某个操作,在等待这个操作完成的过程中程序单元它自身<font color="red">无法</font>继续进行下去做别的事情,那就称这个程序单元在等待该操作时是阻塞的。</strong></p>
<p>这可以是被阻塞的程序单元依赖于别的程序单元,别的程序单元完成它的要求中,它无法继续做任何其他事。 也可以是对硬件设备的依赖(其实在程序看来是对更底层驱动程序的依赖)。常见的阻塞形式有网络I/O阻塞,磁盘I/O阻塞,用户输入阻塞,CPU阻塞等等。</p>
<p>站在被阻塞的程序单元自身来讲,是它消耗了时间等待某事情的结果而发生了阻塞。<strong>所以,在说到阻塞的时候,心里就需明确知道,完整的一句描述应该是“某程序因等待某操作的结果而阻塞”。</strong> 比如一个文件操作函数,因为要等待磁盘I/O拿到数据,那么就称为该文件操作函数因等待磁盘I/O的结果而发生了阻塞。</p>
<h2 id="非阻塞">非阻塞</h2><p><strong>非阻塞</strong>就是阻塞的反面。即是<strong>如果一个程序单元的某个操作,在等待这个操作完成的过程中程序单元它自身<font color="red">可以</font>继续进行下去做别的事情,那就称这个程序单元在等待该操作时是非阻塞的。</strong></p>
<p>这里我们就会看到<strong>非阻塞<font color="red">并不是</font>在任何程序级别、任何情况下都是存在的</strong>。例如在单个函数的级别,上一行是向磁盘索取文件,下一行是对文件内容进行运算,那么当磁盘I/O未有结果时,是无法继续下一行的,此时这个函数是不可能存在非阻塞的状态的。</p>
<p><strong>只有当程序单元高到了一定级别,它可以囊括独立的子程序单元,它才可能有非阻塞状态的存在</strong>。因为阻塞的操作都在子程序单元中,阻塞的是子程序单元而不会阻塞它本身。例如一个单进程多线程的文件操作程序,某个线程在操作一个文件时阻塞了,而别的线程还可以继续运行下去,该进程本身还是运行态而非阻塞态,所以该文件操作程序是非阻塞的。</p>
<p>由上可知,在计算机程序的世界里,阻塞是绝对的,非阻塞是相对的。</p>
<h2 id="同步">同步</h2><p>同步异步的概念是针对至少两个程序单元而言(可以是同级别的,也可以是不同级别的程序单元)。同,一致;异,不一致;只有一个程序单元的世界里,没有别的和它对比,谈何一致或不一致?</p>
<p><strong>多个程序单元之间,通过某种方式通信进行了协调,使它们在相同的时间点的行为或目标一致就称为同步。</strong></p>
<p>在生活中同步的例子就是三军仪仗队行进过程中,他们通过瞄排头兵和身边队友的这种行为来协调自己的动作与他们一致,这就是同步。国旗手听到国歌的某个旋律(信号)就开始撒开国旗并往上升,通过这种行为让国旗的上升与国歌的旋律同步。</p>
<p><strong>可见,同步的重点在于多方之间有信息传递并以此协调一致完成共同目的,而不在于非要多方完成一模一样的事情。</strong> </p>
<h2 id="异步">异步</h2><p><strong>毫不相干的程序单元之间肯定是异步的,多个相干的程序单元之间虽然有某种方式的通信,但过程中无需协调一致也能完成共同目标就是异步的。</strong> </p>
<p>我们会发现,真正意义上的异步是比较少的。而我们常见的技术文档中所提到的异步,是指多个相干的程序单元,其中一个对另一个有依赖,发起请求的程序单元不必等待另一个完成所有的需求就得到了一个临时的返回结果,而请求方真正需要的数据是稍后再返回给请求方。为了解耦程序单元之间的直接关系,所以常常引入了第三者,即是所谓的异步框架或者异步机制。</p>
<p>常见的异步机制有回调、事件循环、信号量等,它们也常常会相互结合使用。</p>
<p><strong>由上述可知,现在大多数文档或者大家所说的异步,其主要目的其实是为了让请求方不必为了该次请求而阻塞以提高请求方的工作效率,所以非阻塞与异步两词就如孪生兄弟般形影不离甚至出现了相互代替的现象。</strong> 我们也可以得知,绝大多数时候,程序单元的级别能够囊括独立子程序单元的情况下,才会有所谓的异步,比如要借助多协程、多线程、多进程来实现异步程序。</p>
<h2 id="总结">总结</h2><p>我们在理解以上四个概念的时候,一定别以固化的思维去理解,别一提到上述概念想到的就是I/O,就是单体应用,甚至是还将多个层次混为一谈,那是不可的。一个程序单元可以在某些情况下是阻塞的,可以在某些情况下是非阻塞的,可以在某些时候是同步的,可以在某些时候是异步的,这都没有确切的定性。</p>
<p>根据上文的解释,大家还可以自行理解一下“异步阻塞”、“异步非阻塞”、“同步非阻塞”、“同步阻塞”这四种模式各自是怎样的情景?为了完成同样的功能,针对不同的应用目的,选择哪种模式才是最合适的?哪些模式是完全没必要存在于任何程序中的?哪些模式是可以被任何程序都可以采用的?在应对大规模并发的时候,这四种模式应该各自如何扩展才能应对挑战?</p>
<p>如果上一段落的几个问题都能思考明白,得到清晰准确的结果,那对本文所述的四个概念算是理解透彻了。</p>
<p>如果你对本文所述的内容有不同的见解,欢迎留言,共同探讨,共同进步。</p>
]]></content>
<summary type="html">
<![CDATA[<p>几个经常容易混淆的概念。特别是像异步与非阻塞,在某些情况下这两个术语常常混用。下面就我谈谈自己对这四个概念的理解,也解释了异步与非阻塞这两个词为何会形影不离。希望对这些概念迷惑,学习Python协程、并发相关的朋友有所帮助。</p>
<h2 id="探究事物的基本逻辑">探究事物的基本逻辑</h2><p><strong>当我们探讨事物的时候,一定要先界定清楚事物的时空界限以及其基本定义,也要先界定清楚探讨的事物/问题的级别或层次</strong>(在阿驹看来,世界上的任何东西都是具有分层/分级的,当我们层次分明的时候,很多架构就显得清晰明了)。<br>]]>
</summary>
<category term="并发" scheme="http://aju.space/tags/%E5%B9%B6%E5%8F%91/"/>
<category term="异步" scheme="http://aju.space/tags/%E5%BC%82%E6%AD%A5/"/>
<category term="非阻塞" scheme="http://aju.space/tags/%E9%9D%9E%E9%98%BB%E5%A1%9E/"/>
</entry>
<entry>
<title><![CDATA[什么是云计算]]></title>
<link href="http://aju.space/2016/08/30/what-is-cloud-compute.html"/>
<id>http://aju.space/2016/08/30/what-is-cloud-compute.html</id>
<published>2016-08-30T02:36:29.000Z</published>
<updated>2016-09-01T02:27:40.189Z</updated>
<content type="html"><![CDATA[<p>美国国家标准技术研究所(NIST)对云计算给的基本定义如下。</p>
<h3 id="定义">定义</h3><p>云计算是一个实现了无处不在的、方便的、通过网络访问按需取用可配置的计算资源共享池(例如:网络、服务器、存储、应用程序和服务)的模型,用户能以最小的管理成本或与提供商沟通的代价即可快速配置和发布所需的计算资源。<br><a id="more"></a></p>
<h3 id="基本特征">基本特征</h3><ul>
<li><p><strong>按需自助服务</strong>。客户能单方面地规定他所需的计算能力,比如服务时间和网络存储,便可自动地获得所需要的资源而无需人工参与。</p>
</li>
<li><p><strong>宽带网接入</strong>。服务能力可以基于宽带网通过标准机制访问,而屏蔽异构的瘦客户端或胖客户端平台(例如手机、平板电脑、笔记本电脑和工作站)。</p>
</li>
<li><p><strong>资源池化</strong>。提供商的计算资源被集中起来为多个客户提供多租户模式的服务,不同的物理或虚拟资源按客户需求动态地分配和再分配。还有一种位置独立感,客户一般不能控制也无需知晓被提供的资源的确切位置,但可以让其指定位置更高级别的抽象(如国家、州或数据中心)。资源一般包括存储、计算、内存、网络带宽。</p>
</li>
<li><p><strong>快速伸缩</strong>。服务能力可以在某些情况下自动地弹性调配与发布,规模化地向外扩展或向内收缩以适应需求。对客户而言,可以用于调配的资源能力几乎是无限制的,可以在任意时间任意取用。</p>
</li>
<li><p><strong>服务可计量</strong>。云计算系统通过利用各类服务(如,存储、计算、带宽、活跃用户)在某种级别上相应的抽象的计量能力以自动控制和优化资源使用。资源使用情况可以被监视、控制和报告,对利用服务的提供商和客户两者来说都是透明的。</p>
</li>
</ul>
<h3 id="服务模型">服务模型</h3><ul>
<li><p><strong>软件即服务(SaaS)</strong> 客户能使用提供商的应用程序以运行在云计算基础设施之上。这类应用程序可以从各种客户端访问,从瘦客户端到胖客户端,比如通用的Web浏览器(例如基于web的email服务)或者是专用的程序界面。客户不用管理和控制底层的云计算基础设施,包括网络、服务器、操作系统、存储甚至是个别应用程序的功能等,可能例外是需要客户指定应用程序的部分配置信息。</p>
</li>
<li><p><strong>平台即服务(PaaS)</strong> 客户可以在云计算基础设施上部署客户自己创建的或者购置提供商支持的编程语言、库、服务及工具创建的应用程序。客户不管理或控制云计算底层基础设施,包括网络、服务器、操作系统或存储,但对其部署的应用程序有控制权并可能对应用运行环境的相关配置进行设定。</p>
</li>
<li><p><strong>基础设施即服务(IaaS)</strong> 客户可以规定满足其运行任何软件(包括操作系统和应用程序)所需的计算、存储、网络和其他基本的计算资源。客户不管理或控制云计算底层基础设施,但能控制操作系统、存储和部署的应用;可能对选择网络组件(如防火墙)等具有有限的控制权。</p>
</li>
</ul>
<h3 id="部署模式">部署模式</h3><ul>
<li><p><strong>私有云</strong> 云计算基础设施提供给包含多个消费者(例如业务单元)组成的单一的独占使用的机构。它可能由这个机构拥有、管理或运营,也可以是第三方或者其他合作方式。这些基础设施可以存放于或不存放于该机构本地。</p>
</li>
<li><p><strong>社区云</strong> 云计算基础设施提供给来自组织中具有共同利益(例如任务、安全要求、政策和法规遵从性等方面考虑)的消费者组成的特定社区独占使用。基础设施被社区中的一个或多个组织拥有、管理及运营,可以存放或不存放于它们的处所。</p>
</li>
<li><p><strong>公有云</strong> 云基础设施提供给一般公众开放使用。它可能被一个企业、学院、政府机构或其他合作形式的组织拥有、管理及运营。它存放于云计算提供商那里。</p>
</li>
<li><p><strong>混合云</strong> 这种是私有云、社区云、公有云的混合组成形式,各部分是独立的实体,但被标准化或专利技术绑定在一起,实现数据和应用程序的可移植性(例如在突发情况下在不同的云之间做负载均衡)。</p>
</li>
</ul>
<p><img src="http://imgj.metasotalaw.cn/static/images/cloudstack.png" alt="cloudstack"></p>
]]></content>
<summary type="html">
<![CDATA[<p>美国国家标准技术研究所(NIST)对云计算给的基本定义如下。</p>
<h3 id="定义">定义</h3><p>云计算是一个实现了无处不在的、方便的、通过网络访问按需取用可配置的计算资源共享池(例如:网络、服务器、存储、应用程序和服务)的模型,用户能以最小的管理成本或与提供商沟通的代价即可快速配置和发布所需的计算资源。<br>]]>
</summary>
<category term="云计算" scheme="http://aju.space/tags/%E4%BA%91%E8%AE%A1%E7%AE%97/"/>
</entry>
<entry>
<title><![CDATA[Unable to find vcvarsall.bat]]></title>
<link href="http://aju.space/2016/08/15/unable-to-find-vcvarsall-bat.html"/>
<id>http://aju.space/2016/08/15/unable-to-find-vcvarsall-bat.html</id>
<published>2016-08-15T21:09:40.000Z</published>
<updated>2016-08-16T03:14:38.790Z</updated>
<content type="html"><![CDATA[<h2 id="1-_什么是_vcvarsall-bat_?">1. 什么是 vcvarsall.bat ?</h2><p>Visual Studio 的编译器的一个组件。</p>
<h2 id="2-_使用Python开发环境,何时需要它?">2. 使用Python开发环境,何时需要它?</h2><p>在Windows环境下,使用了lxml、mysqlclient、pillow等第三方库,这些第三方库的一些子模块是用C语言扩展写的。而在安装这些库时试图通过源码安装,需要编译,如果当前系统下没有对应的编译环境,则会报错,这里是报“无法找到vcvarsall.bat”。<br><a id="more"></a></p>
<h2 id="3-_如何克服没有_vcvarsall-bat_带来的问题?">3. 如何克服没有 vcvarsall.bat 带来的问题?</h2><h3 id="1)_使用已编译的包">1) 使用已编译的包</h3><p>推荐下载whl格式的包然后通过 <code>pip install path_to_package.whl</code> 命令来安装,如此安装的包还可以被pip管理起来,卸载也方便。可以去这里找编译好的whl包:<a href="http://www.lfd.uci.edu/~gohlke/pythonlibs/" target="_blank" rel="external">http://www.lfd.uci.edu/~gohlke/pythonlibs/</a></p>
<h3 id="2)_通过源码编译安装">2) 通过源码编译安装</h3><p>找不到现成已编译的whl包的情况下,只能通过源码安装。要查清楚各版本Python对应的VC编译环境。可在此处查看Python官方文档的描述:<a href="https://packaging.python.org/extensions/#building-binary-extensions" target="_blank" rel="external">https://packaging.python.org/extensions/#building-binary-extensions</a></p>
<p>也可按照下方提示直接安装相关工具后再编译安装所需的Python包。</p>
<ul>
<li>Python2.6 ~ 3.2: <a href="https://www.microsoft.com/download/details.aspx?id=44266" target="_blank" rel="external">Microsoft Visual C++ Compiler for Python 2.7</a></li>
<li>Python3.3 ~ 3.4: <a href="https://www.microsoft.com/download/details.aspx?id=8279" target="_blank" rel="external">Windows SDK for Windows 7 and .NET 4.0</a></li>
<li>Python3.5 ~ ?? : <a href="http://go.microsoft.com/fwlink/?LinkId=691126" target="_blank" rel="external">Visual C++ Build Tools 2015</a></li>
</ul>
<p>根据以上提示安装好相应版本的工具即可,是无需安装 Visual Studio 完整套件的。一个更好的解决办法是,使用 <strong>Linux</strong> 吧 :)</p>
]]></content>
<summary type="html">
<![CDATA[<h2 id="1-_什么是_vcvarsall-bat_?">1. 什么是 vcvarsall.bat ?</h2><p>Visual Studio 的编译器的一个组件。</p>
<h2 id="2-_使用Python开发环境,何时需要它?">2. 使用Python开发环境,何时需要它?</h2><p>在Windows环境下,使用了lxml、mysqlclient、pillow等第三方库,这些第三方库的一些子模块是用C语言扩展写的。而在安装这些库时试图通过源码安装,需要编译,如果当前系统下没有对应的编译环境,则会报错,这里是报“无法找到vcvarsall.bat”。<br>]]>
</summary>
<category term="Python" scheme="http://aju.space/tags/Python/"/>
<category term="Python on Windows" scheme="http://aju.space/tags/Python-on-Windows/"/>
</entry>
<entry>
<title><![CDATA[UTC+8还是GMT+8?聊聊时间与计时法]]></title>
<link href="http://aju.space/2016/06/24/talk-about-time-and-timer.html"/>
<id>http://aju.space/2016/06/24/talk-about-time-and-timer.html</id>
<published>2016-06-24T05:17:14.000Z</published>
<updated>2016-06-24T11:22:58.202Z</updated>
<content type="html"><![CDATA[<p>今天一位同事问道:“你写入ES的时间字段是考虑过时区处理了的吗?”</p>
<p>原来是我们有一个可能部署在全球各地的软件系统,这个系统会记录各种服务监控指标存入ES(一种数据存档索引系统)。今日那位同事发现他获取的是北京时间,而存入ES系统后却比北京时间晚了8个小时。<br><a id="more"></a><br>位于美国的系统面向美国用户,时间肯定是以美国当地为准,北京的同理。但如果最终将数据规整到一个系统中时,计时不统一将会导致数据几乎不可用,因为无法界定各个事件发生的先后顺序。</p>
<p>当然要解决这个问题很简单,不论是美国还是中国的系统,存储记录时均以国际标准时为准就可以了。可这件事却引起了我对时间、计时法、计时器的兴趣,于是有了本文。</p>
<h2 id="到底该用UTC+8还是用GMT+8?"><strong>到底该用UTC+8还是用GMT+8?</strong></h2><p>简短的回答是:<strong>对一般人来说没什么区别</strong>。虽然这两种时间有差异,但差异很小。</p>
<p>如果你不是一般人,想寻根究底,请往下看。那么它们到之间以及与其他你可能听过的计时有什么区别呢?为什么要闰秒呢?为什么一天要划分为24小时而非25小时?为什么一小时划分为60分而不是100分?</p>
<p>下面我们进行深入探讨,计时方式依据时间而来,那么首要弄明白的问题就是什么是时间,为什么要计时。</p>
<h2 id="什么是时间">什么是时间</h2><p>长久以来,时间一直是宗教、哲学及科学领域的研究主题之一。但各领域学者还没有找到一个适用于所有领域、具有一致性、没有争议的定义。争议比较小的定义有:“<strong>时间是时钟量测的物理量</strong>。”以及“<strong>时间使得所有事情不会同时发生</strong>。”(What the fuck?! 你没看错,没有同时发生的事。因为这样的定义下,时间是连续不可分割的,其精度无限高,故而事情发生的时间总会在某个精度下没有重合。还有人认为时间是不连续的,后文再谈时间的量子化。)</p>
<p>对于<strong>时间的存在性</strong>也有两种派别,一种以牛顿为代表的认为<strong>时间是宇宙的基本结构,是一个会依序列方式出现的维度</strong>。另一派以莱布尼茨和康德为代表的认为<strong>时间不是任何一种已存在的维度,只是一种心智的概念,是人类为了便于思考宇宙,对事件先后排序比较的人为规定</strong>。</p>
<h2 id="计时">计时</h2><p>日出而作日入而息,这是更古以来人类以及各类动物作息的基本标准。这是进化历程中对时间自然而然地感知。人们观察到天亮是因为太阳会从空中划过,天黑是因为太阳落山了,“一天”的概念就形成了。</p>
<p>如何精确地测定<strong><code>一天</code></strong>的长度就成了最主要问题,进而要确定四季变化,再到制定历法。因为最直观的就是太阳、月亮以及一些恒星在天空中周期性地变化,<code>日、月、年</code>长度的基本参照物就有了。所以制定历法,在全世界都是最早推动天文学发展的主要因素。</p>
<p>以此,用太阳照射物体的影子来测量时间自然被人类想到了,所以发明了<strong>日晷</strong>、<strong>圭表</strong>等,不仅能测天,还能测季节。后来为了满足阴天、夜晚、室内的计时需求,水钟、沙漏等被发明出来。</p>
<h2 id="GMT">GMT</h2><p><strong>GMT</strong>(Greenwich Mean Time)译作<strong>格林威治标准时间</strong>或<strong>格林威治平均时间</strong>。是由英国伦敦皇家格林威治天文台发布的标准时间。民用GMT自1847年起就在大不列颠岛使用。1884年,在华盛顿召开了国际子午线会议,格林威治当地平太阳时被指定为通用日,以午夜零时作为当天的起点。从1924年2月5日开始每隔一小时向全世界报时。被世界广泛采用。</p>
<p><img src="http://imgj.metasotalaw.cn/static/images/jingxian.jpg" alt="jingxian"></p>
<p>GMT时间是如何测量的呢?是观察<strong>真太阳前后两次经过格林威治当地正上空为周期</strong>,即一天。这也是为什么经过格林威治的经线,规定为<strong>0度经线</strong>、<strong>本初子午线</strong>的原因。但因为地球绕太阳公转轨道不是正圆且公转速度不匀速等影响,如此测出来的每天的长短不一,格林威治天文台<strong>连续观测365个长短不一的天,然后求平均值得到一天的长度,称为平太阳日,每一天以格林威治子夜算起。然后再把天划分为时分秒。这种方法和天文学上假想一个太阳(假太阳,亦称平太阳)在天球赤道上匀速运行的观测结果是一致的。</strong></p>
<p><img src="http://imgj.metasotalaw.cn/static/images/chidao.png" alt="chidao"></p>
<p>这种方法测出来的基本单位是天。至于为什么一天要划分为24小时,每小时划分为60分钟,每分钟为60秒,后文再谈。<strong>平太阳日规定了每天正好86400秒</strong>。只测真太阳的话,每天长度不固定,这会给很多需要精确计时的领域带来困扰,是引入平太阳时的原因。</p>
<h2 id="时区">时区</h2><p>国际统一的计时标准有了,但为了解决“18:00点整A地区太阳快落山”而“18:00点整B地区太阳还没出来”的反人类直觉的尴尬现象,<strong>为了让大家都能统一地有“凌晨5点太阳就要出来了”的认知,引入了时区的概念。</strong></p>
<p>理论时区以被15整除的子午线(经线)为中心,向东西两侧延伸7.5度,即每15°划分一个时区,<strong>全球被划分为24个时区</strong>,这是理论时区。但因为国界线并不规则的原因,所以实际划分的时区并不规则。<strong>格林威治所在时区为0时区</strong>。</p>
<p>0度经线(经过格林威治)和180度经线所形成的圆环将地球划分为东西半球。从英国格林威治开始经过亚欧大陆至澳洲再至大约太平洋中间的位置为<strong>东半球</strong>,<strong>在这个方向上,每经过一个时区时间则在0时区的基础上则加 1 作为当地时间</strong>。从格林威治经过大西洋,至南美洲再至北美洲这边为<strong>西半球</strong>,<strong>这个方向上每经过一个时区,当地时间就是在0时区基础上减 1 </strong>。</p>
<p><strong>北京位于东八区,所以北京时间是GMT+8的来由于此。而我们还听说的UTC时间、UT时间、TAI时间、GPS时间等等,都是因为计时方法、计时工具、计时精度的不同而衍生出来的。它们能够代替GMT时间,但因为GMT历史影响深远,保留了这个称呼。而且以格林威治所在时区为0时区也保留了下来。</strong></p>
<h2 id="UT">UT</h2><p>1928年,国际天文联合会引入了<strong>世界时(UT,Universal Time)</strong>的概念来指代GMT。随着科学技术的发达,观测方式越来越多,并不一定非要根据太阳来观察,也可以是一些宇宙射线。发现GMT测算是有偏差的。所以,1955年国际天文联合会又定义了<strong>UT0</strong>、<strong>UT1</strong>和<strong>UT2</strong>三个系统。</p>
<p>UT0系统是由天文观测的世界时,未经任何修正,即与GMT一致。后来天文学家发现在不同地点使用相同的方法观测结果仍不一致,这是由于地轴摆动引起的,UT1就修正了这种影响。后来又发现UT1具有周期性的变化,这种变化和地球自转速率的季节性变动有关,又在UT1的基础上修正了这种地轴摆动影响称为UT2。总结:<strong>GMT = UT0 精度小于 UT1 精度小于 UT2</strong>。(<a href="http://www.hko.gov.hk/gts/time/basicterms-UTandGMTc.htm" target="_blank" rel="external">参考资料</a>)</p>
<p>如今我们称呼的GMT时,实际上是UT时。</p>
<h2 id="TAI">TAI</h2><p><strong>国际原子时</strong>(<strong>International Atomic Time,TAI</strong>)1967年的第13届国际度量衡会议上通过了一项决议,采纳以下定义代替秒的天文定义: <strong>一秒为铯-133原子基态两个超精细能级间跃迁辐射9,192,631,770周所持续的时间</strong>。<strong>TAI</strong>被设定在1958年1月1日0:00:00与<strong>UT2</strong>相同。</p>
<p><strong>现在TAI被国际度量衡局(BIPM)和国际地球自转和参考座标系统服务(IERS)管理,他们依据全球约60个实验室中约240台原子钟提供国际标准时间。</strong></p>
<p>1955年,铯原子钟被发明出来,这种方式比天文观测更稳定,也证实了地球自转的不稳定性。铯原子钟使用铯的同位素铯-133来计时,因为铯-133是所有铯的同位素中最稳定的。至于为什么用铯,而不是其他元素,因为目前尝试过的元素中,铯-133的计时精度最高。在铯原子钟之前,还采用过最常见的氢元素。</p>
<p>1956年美国国家标准局和美国海军天文台开始研究基于原子频率的时间尺度。经过三年的观测与比较,发现不受外场干扰的铯-133原子基态的两个超精细结构能阶跃迁对应辐射的9,192,631,770个周期的持续时间和<strong>历书秒</strong>一致。</p>
<p>关于<strong>秒</strong>单位,英国科学促进协会在1862年表示“所有科学界的人都同意用平均太阳日计算的秒为时间单位。”,并于1874年正式提出厘米-克-秒制,分别为长度、重量、时间的基本单位。定义了<strong>秒为平均太阳日的1/86,400</strong>。</p>
<p>1956年秒被定义为<strong>以1900年历元(即历书时1900年1月1日正午12时)算起的回归年的31,556,925.9747分之一为一秒</strong>。此定义于1960年国际度量衡会议通过,即为<strong>历书秒</strong>。历元是指天文学变量作为参考的一些时刻点。</p>
<h2 id="UTC">UTC</h2><p><strong>协调世界时(Coordinated Universal Time)是目前最主要的世界标准时间</strong>,以<strong>TAI</strong>时为基础,又尽量靠近<strong>UT1</strong>时。但在早期UTC旨在靠近更高精度的世界时UT2的。UT时依据地球自转而测得,但是地球自转在不断变慢,而且平时还会受潮汐力影响,这样秒就会变长,而UTC的参考基准是TAI,很稳定的。故而需要在某些时候给UTC加1秒或减1秒来保证UTC时与UT1时的差值在0.9秒以内,这就是<strong>闰秒</strong>。</p>
<p>PS: 如果你此时质疑了为何Coordinated Universal Time要缩写为UTC,证明你是个细心的人。</p>
<p>1960年,美国海军天文台、英国格林尼治皇家天文台以及英国国家物理实验室协调了它们的无线电广播,由此时间的步长和频率的变化得到了协调,这样产生的时间尺度也被正式命名为“<strong>协调世界时</strong>”。1961年,国际时间局开始在国际上协调不同的UTC时间。</p>
<p>有趣的是直到1967年国际天文学联合会才采用“协调世界时”这个命名,因为英语国家想坚持命名为CUT(Coordinated Universal Time)而法语国家想命名为TUC(Temps Universel Coordonné)。几番争执后相互妥协成了UTC。真是有趣,不仅时间是不同计时系统相互协调的结果,连命名也是协调的结果。</p>
<h2 id="GPST">GPST</h2><p><strong>全球定位系统时(GPS Time)</strong>,GPS由24颗卫星组成,可以向全球范围内提供定位、测速、高精度授时等功能。GPST是由星载原子钟和地面监控中心组成的一种原子时系统。起点是1980年1月6日0时,此时刻与UTC对齐。</p>
<p>虽然以TAI时为基础的UTC时与GPST时都是使用原子钟,但是还是有微小差异的。差异一来自组成各自计时系统原子钟的数量,TAI为全球范围的240多个,而GPST为20多个。差异二是GPST所用的原子钟在高速飞行的卫星上时间长度会发生变化,所以还需要使用《相对论》进行卫星时间修正。差异三在UTC为了和UT1保持较小的误差,还得跳秒。</p>
<p>下面是几种时间的直观对比(<a href="http://www.leapsecond.com/java/gpsclock.htm" target="_blank" rel="external">参考资料</a>):<br><img src="http://imgj.metasotalaw.cn/static/images/time.png" alt="time"></p>
<p>通过上图发现几种时间的差异之处。UTC与GMT看起来已经相差两秒,可能是网络原因导致的,实际上它们应该相差在1秒之内。大多数时候打开看应该是GMT慢了1秒。</p>
<p>国内的北斗导航系统也能提供授时服务,同样是卫星上载有原子钟。早期的北斗卫星采用瑞士进口原子钟,目前已经开始使用国产原子钟。截止2016年6月12日,已经发射23颗北斗卫星,其中21颗在进行服务。</p>
<h2 id="24小时、60分钟的由来">24小时、60分钟的由来</h2><p>为什么一年分为4季而非5季?分为12个月而非13个月?为什么平年是365天?为什么每隔4年是一个闰年?为什么闰年是366天?为什么每天分为24小时而非20小时?为什么每小时分为60分钟而不是100分钟?为什么每分钟分为60秒而不是70秒?</p>
<p>如果你跟我一样都疑惑过以上问题并且想知道答案的话,那继续往下看吧。本节估计是本文最有趣的一节了。</p>
<p>首先解决简单的问题。一年有365天是因为天文观测到比较精确地是每经过365.25个昼夜就是地球围绕太阳公转一年。而要隔4年就闰年的原因也来自于此。每4年的前3年并不会将那0.25天算上,不方便,所以第4年的时候就凑个整为366天。</p>
<p>为什么一年要分为四个季节呢?可能是对大自然比较粗略的感知,极冷和极热分别为冬夏两个季节,而这两个季节之间又有两个不冷不热的季节连接,故而有了春夏秋冬四个季节。也许还有其他地理方面的观察直接推动了将季节划分为4个。一年分为12个月也比较容易知道是为什么,观察一年里月球的运动即可得到12这个数值。</p>
<p>然而为何要将一天划分为24个小时?</p>
<p>埃及有出土约公元前1500多年的日晷,呈T型,置于地上将日出至日落划分为12份。为什么划分采用12这个数值的原因有几个。一是月球绕地球的转动周期。二是人类除了大拇指之外的关节数,方便数数。三是埃及人还观察到天上特定的36颗恒星可以将天空分为相等的部分,依此作为测量系统。天黑时可以依次看见18颗星星,但是黄昏和黎明时各占3颗星星不容易看见,天空纯黑的时候可以看到12颗。黑夜被划分为12份就有了,白天再同理划分为12份。也就有了24小时的雏形。</p>
<p>那么为什么每个小时被划分为60分钟呢?在很长的时期,是没有分钟这种划分的,因为没有普世而精确的工具。按60来划分大概是从希腊托勒密那代(公元前100年左右)开始,从古巴比伦传进来的。据说古巴比伦人是跟苏美人(约公元前3000~2000年)学的。</p>
<p>无独有偶,中国的天干地支计时法,大概出现于公元前一千多年,每天也被分为12个时辰,每个时辰被划分为两小部分。而天干地支的特定组合是60,比如用于天干地支纪年法,60年一甲子,一个轮回。</p>
<p>不论是从苏美人那里学的,还是相隔千万里的中华文明,为何都用12、60这些数值?有种说法是源于我们的手掌,一只手用大拇指数关节,就是12,另一只手用手指做进位,5个12就是60。</p>
<p>而且我们现在发现,用60真的太方便了。60的约数有1,2,3,4,5,6,10,12,15,20,30,我们可以很轻易地把60分钟划分为各种相等的组合。可以划分为2个30分钟,也可以划分为4个15分钟等等。</p>
<p>在写此文中,还阅读了一些我们老祖先发明的太极八卦相关的资料。无极生太极,太极生两仪,两仪生四象,四象生八卦。八卦又可以演绎为六十四卦。这实际就是一个二进制系统。然而天干地支这些和它结合在一起,2进制(及其衍生的八进制、十六进制)、10进制、60进制都有体现。很是了不得,以后再探究这点。</p>
<p>数,是人类认识宇宙的最基本最重要工具。谁对数的研究和认识走在人类前沿,那么那个文明也将走在世界最前沿。</p>
<p>你,心里有没有数?</p>
]]></content>
<summary type="html">
<![CDATA[<p>今天一位同事问道:“你写入ES的时间字段是考虑过时区处理了的吗?”</p>
<p>原来是我们有一个可能部署在全球各地的软件系统,这个系统会记录各种服务监控指标存入ES(一种数据存档索引系统)。今日那位同事发现他获取的是北京时间,而存入ES系统后却比北京时间晚了8个小时。<br>]]>
</summary>
<category term="-杂谈" scheme="http://aju.space/tags/%E6%9D%82%E8%B0%88/"/>
</entry>
<entry>
<title><![CDATA[如何在Python里应用SOLID原则]]></title>
<link href="http://aju.space/2016/06/17/use-S-O-L-I-D-in-python.html"/>
<id>http://aju.space/2016/06/17/use-S-O-L-I-D-in-python.html</id>
<published>2016-06-17T04:10:31.000Z</published>
<updated>2016-06-17T10:12:52.627Z</updated>
<content type="html"><![CDATA[<p>如今OOP编程大行其道。不少人以为用编程语言里的class关键字定义一个类,然后用类创建一个对象就是OOP了。肤浅!</p>
<p>OOP编程很贴近人们的正常思维方式,所以容易被接受,而且应用也很广泛。的确,这给编程带来了很大的好处。但并不是任何人都能深谙OOP的本质。简略证明如下:如果把“女朋友”作为一个类,你自己的女朋友就是你的对象,是“女朋友”类的具体的实例。如果你能熟练掌握“女朋友”这个类的特性以及方法,还不能创建一个实例么?你没女朋友,说明你没吃透女朋友类,进而说明你肯定没理解好OOP。但创建了一个实例出来也并不能证明你就吃透了这个类。<br><a id="more"></a></p>
<p>以上是闲话。</p>
<p>是否感觉你在OOP时总是要么很随意的定义class然后创建object,要么在生搬硬套所谓的设计模式?</p>
<p>是否感觉你的程序真的很脆弱(比如一天的告警邮件就是几千封)?</p>
<p>是否觉得灵活性差可扩展性不足(比如要增加新功能,不能通过某种优雅的方式插入现在的系统,而是copy一份类似的代码改一改)?</p>
<p>是否觉得子模块/子系统之间依赖关系混乱,粘黏性强(你渴望用TDD或unittest来保证系统每次迭代的质量,但你发现基于你的代码难以写testcase,为啥?因为依赖混乱、粘黏性强 独立的程序单元基本没有,没有unit请问怎么unittest?)?</p>
<p>如果我们的代码存在以上任何一种问题,包括没提到的一些导致系统烂的问题,如果你还在用OOP的话,那么好好根据本文思考一下你的代码该如何改进。</p>
<h2 id="SOLID是什么?">SOLID是什么?</h2><p><strong>SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)</strong>是由罗伯特·C·马丁(其著作有《敏捷软件开发——原则、模式与实践》、《Clean Code》)在21世纪早期引入的记忆术首字母缩略字,<strong>指代了面向对象编程和面向对象设计的五个基本原则。</strong></p>
<p>SOLID被典型的应用于测试驱动开发(TDD,TDD也并不那么美好,以后再说),并且是敏捷开发以及自适应软件开发的基本原则的重要组成部分。</p>
<p>如果你们在践行敏捷开发和尝试TDD,那么有什么理由不掌握这五个几本原则呢?这五个原则并完全是罗伯特·C·马丁原创的,别弄混了。</p>
<p><strong>但是,原则并不是规则,更不是教条,原则对智者来说是指导,对愚者来说是遵从。</strong> SOLID以及本文只起到抛砖引玉的作用。</p>
<table>
<thead>
<tr>
<th style="text-align:center">首字母</th>
<th>指代</th>
<th>概念</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">S</td>
<td>单一功能原则</td>
<td>对象应该仅具有一种单一功能</td>
</tr>
<tr>
<td style="text-align:center">O</td>
<td>开闭原则</td>
<td>软件体应该是对于扩展开放的,但是对于修改封闭的</td>
</tr>
<tr>
<td style="text-align:center">L</td>
<td>里氏替换原则</td>
<td>程序中的对象应该是可以在不改变程序正确性的前提下被它的子类对象所替换的</td>
</tr>
<tr>
<td style="text-align:center">I</td>
<td>接口隔离原则</td>
<td>多个特定客户端接口要好于一个宽泛用途的接口</td>
</tr>
<tr>
<td style="text-align:center">D</td>
<td>依赖反转原则</td>
<td>高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口;抽象接口不应该依赖于具体实现,而具体实现则应该依赖于抽象接口。</td>
</tr>
</tbody>
</table>
<h2 id="为什么要用SOLID原则指导OOP?">为什么要用SOLID原则指导OOP?</h2><p><strong>容易编写易于维护的、复用率高的、易于测试的OO代码。</strong> 为了达到这个目的,你可以自己写上数十万行代码并研究总结出类似的规律,也可以先在这个原则的指导下试试看。</p>
<h2 id="Talk_is_cheap-_Show_me_the_code-">Talk is cheap. Show me the code.</h2><p>现在我们要用OOP来实现一段最基本的 “洗车服务” 代码。需求如下:<br><figure class="highlight haml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">洗车服务</span><br><span class="line"> -<span class="ruby"> 洗车作业任务</span><br><span class="line"></span> 汽车进入洗车机时</span><br><span class="line"> 注册洗车任务</span><br><span class="line"> -<span class="ruby"> 顾客通知</span><br><span class="line"></span> 洗车完毕</span><br><span class="line"> 向顾客发出消息通知</span><br><span class="line"> -<span class="ruby"> 报表</span><br><span class="line"></span> 客户端发出报表请求时</span><br><span class="line"> 向该顾客展示他的所有洗车信息</span><br></pre></td></tr></table></figure></p>
<p>也许你的代码其中一段如下:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CarWashService</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, sms_sender)</span>:</span></span><br><span class="line"> self.persistence = {}</span><br><span class="line"> self.sms_sender = sms_sender</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">require_car_wash</span><span class="params">(self, car, customer)</span>:</span></span><br><span class="line"> service_id = uuid.uuid4().hex</span><br><span class="line"> self.persistence[service_id] = (car, customer)</span><br><span class="line"> <span class="keyword">return</span> service_id</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">wash_completed</span><span class="params">(self, service_id)</span>:</span></span><br><span class="line"> car, costomer = self.persistence[service_id]</span><br><span class="line"> self.sms_sender.send(mobile_phone=customer.mobile_phone,</span><br><span class="line"> text=<span class="string">'Car %{car.plate} washed'</span>.format(car=car))</span><br></pre></td></tr></table></figure></p>
<h2 id="单一职责原则">单一职责原则</h2><p>为什么要遵循这个原则?交警在路边可以去劝阻路边打架斗殴的,在民警未到时也应该去劝阻,但交警就该去劝架了吗?道路交通怎么办?让民警又干什么?在纠纷频发的地方如果只有交警而无民警,那是治安体制有问题没在那里安插民警,而不是交警袖手旁观。</p>
<p>所以,<strong>分清楚你“必须做”和“可以做”的事情</strong>。每个角色做好必须做的事情就很好了。如果还有一些事情没人做,那就创造角色让他去做。</p>
<p>那么以单一职责原则来看上面的代码有什么问题?<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CarWashService</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, sms_sender)</span>:</span></span><br><span class="line"> self.persistence = {} <span class="comment"># A</span></span><br><span class="line"> self.sms_sender = sms_sender <span class="comment"># B</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">require_car_wash</span><span class="params">(self, car, customer)</span>:</span></span><br><span class="line"> service_id = uuid.uuid4().hex</span><br><span class="line"> self.persistence[service_id] = (car, customer) <span class="comment"># A</span></span><br><span class="line"> <span class="keyword">return</span> service_id</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">wash_completed</span><span class="params">(self, service_id)</span>:</span></span><br><span class="line"> car, costomer = self.persistence[service_id] <span class="comment"># A</span></span><br><span class="line"> self.sms_sender.send(mobile_phone=customer.mobile_phone,</span><br><span class="line"> text=<span class="string">'Car %{car.plate} washed'</span>.format(car=car)) <span class="comment"># B</span></span><br></pre></td></tr></table></figure></p>
<p>经过一点改造后,写出了下面的代码:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CarWashService</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, notifier, repository)</span>:</span></span><br><span class="line"> self.notifier = notifier</span><br><span class="line"> self.repository = repository</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">enter_in_car_wash</span><span class="params">(self, car, customer)</span>:</span></span><br><span class="line"> job = CarWashJob(car, costomer)</span><br><span class="line"> self.repository.put(job)</span><br><span class="line"> <span class="keyword">return</span> job</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">wash_completed</span><span class="params">(self, service_id)</span>:</span></span><br><span class="line"> car_wash_job = self.repository.find_by_id(service_id)</span><br><span class="line"> self.notifier.job_completed(car_wash_job)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">service_by_customer</span><span class="params">(self, customer)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> self.repository.find_by_customer(customer)</span><br></pre></td></tr></table></figure></p>
<h2 id="依赖反转原则">依赖反转原则</h2><p>为何要遵循这个原则?你会把台灯电线直接焊接在墙上的电线上吗?</p>
<p><strong>高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。</strong>台灯是高层次模块,电路是低层次模块,台灯依赖于台灯的插头而非电线以获取电源;电路依赖于插座为外部提供电源而非直接将电线暴露出去。</p>
<p><strong>抽象接口不应该依赖于具体实现,而具体实现应该依赖于抽象接口</strong>。三针插头可以被台灯用,也可以被冰箱用,所以抽象接口(插头)并不依赖于背后的具体实现(台灯/冰箱)。而冰箱因为功率较大,一定要有能接地线的三针插头,所以具体实现依赖于抽象接口。</p>
<p>在Python中,编译或程序启动时的依赖是import语句的内容,而运行时的依赖就是被调用的函数、方法等。</p>
<p>也许你写出了类似下面的代码:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CarWashService</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, repository)</span>:</span></span><br><span class="line"> self.repository = repository</span><br><span class="line"> <span class="comment"># self.notifier = SmsNotifier()</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">enter_in_car_wash</span><span class="params">(self, car, customer)</span>:</span></span><br><span class="line"> job = CarWashJob(car, customer)</span><br><span class="line"> self.repository.put(job)</span><br><span class="line"> <span class="keyword">return</span> job</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">wash_completed</span><span class="params">(self, service_id)</span>:</span></span><br><span class="line"> car_wash_job = self.repository.find_by_id(service_id)</span><br><span class="line"> SmsNotifier.send_sms(car_wash_job) <span class="comment">#有问题</span></span><br><span class="line"> <span class="comment"># self.notifier.send_sms(car_wash_job)</span></span><br></pre></td></tr></table></figure></p>
<p>这段代码的问题主要出在直接调用<code>SmsNotifier</code>类方法那行。如果你是按被注释的那两行写的,还是存在如下同样的问题。</p>
<p><strong>问题一是全局状态问题</strong>,直接使用了全局变量<code>SmsNotifier</code>,我们说过写代码能使用局部变量的就别使用全局变量,一是全局变量不使用时并不会被释放内存,二是全局变量对其他对象来说都是可见的,也是可修改的,会给程序带来更多的不确定性。</p>
<p><strong>问题二在于隐式依赖问题</strong>。并不能从<code>CarWashService</code>的构造方法或初始化方法中知道它依赖了<code>SmsNotifier</code>类,这对代码的可读性和易于理解性都会带来障碍。比如在别的模块中使用了<code>CarWashService</code>类的时候,不层层追查的话,神仙才知道它还依赖了<code>SmsNotifier</code>。</p>
<p><strong>问题三是依赖于具体的实现</strong>。<code>CarWashService</code>对象直接依赖了具体的<code>send_sms</code>方法。这样做能够实现通知的需求,但是<strong>扩展性差</strong>。如果又要同时用电话、邮件、微信等通知方式,又该要来改这里的老代码了。从现实世界中的例子来看,在较大的机构中都会有类似“传达室”的小机构。要怎么改进就显而易见了。</p>
<p>改进后的代码如下:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CarWashService</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, notifier, repository)</span>:</span></span><br><span class="line"> self.repository = repository</span><br><span class="line"> self.notifier = notifier</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">enter_in_car_wash</span><span class="params">(self, car, customer)</span>:</span></span><br><span class="line"> job = CarWashJob(car, customer)</span><br><span class="line"> self.repository.put(job)</span><br><span class="line"> <span class="keyword">return</span> job</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">wash_completed</span><span class="params">(self, service_id)</span>:</span></span><br><span class="line"> car_wash_job = self.repository.find_by_id(service_id)</span><br><span class="line"> self.notifier.job_completed(car_wash_job)</span><br></pre></td></tr></table></figure></p>
<p>上面通过<strong>依赖注入</strong>的方式将<code>notifier</code>对象作为初始化参数传递给<code>CarWashService</code>类。这种写法化解了上面提到的三种负面影响。</p>
<p><strong>依赖注入是实现依赖反转的一种方式,两者并不等同。</strong>两个存在依赖关系的对象A、B,A使用B的服务,B可以向A提供服务,我们并不让A直接使用B,而是将B传递给A,使B成为A的一部分。这就是依赖注入。</p>
<p>其实<strong>依赖反转</strong>也相当于应用了<strong>适配器模式</strong>,举例中的插座和插头是抽象接口,也就是台灯电线和供电电线的适配器,传达室也是消息发送人和消息接收人的适配器。示例代码中被抽象出来的notifier就是适配器对象。洗车服务只知道洗完车要通知,但是具体用哪些途径通知,当前结束的任务该通知给谁,在什么时间通知,都由<code>notifier</code>去完成。程序的耦合性会进一步降低,灵活性进一步增强。</p>
<h2 id="开闭原则">开闭原则</h2><p>为何要遵循开闭原则?当你想增加自己的御寒能力只用在身体外加衣服而非做个开胸手术。软件体也一样,观察人体这个造物者的完美之作,把它的规律用在软件体上,就可以造出更完美的软件。<strong>好的设计可以让你在为系统新增功能时添加新代码即可而无需修改老代码。</strong></p>
<p>洗车服务的数据需要得到保存,可能保存在内存、文件、数据库等等。但这些功能都是几乎一致的,所以你很可能写出了如下抽象类,希望其他子类都来继承它。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">InterfaceJobReository</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">put</span><span class="params">(self, job)</span>:</span></span><br><span class="line"> <span class="keyword">raise</span> NotImplementedError()</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">find_by_id</span><span class="params">(self, job_id)</span>:</span></span><br><span class="line"> <span class="keyword">raise</span> NotImplementedError()</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">find_by_customer</span><span class="params">(self, customer)</span>:</span></span><br><span class="line"> <span class="keyword">raise</span> NotImplementedError()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">InMemoryJobReository</span><span class="params">(InterfaceJobReository)</span>:</span></span><br><span class="line"> <span class="string">"""注意这里的继承"""</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line"> self._storage = {}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">put</span><span class="params">(self, job)</span>:</span></span><br><span class="line"> self._storage[job.service_id] = job</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">find_by_id</span><span class="params">(self, job_id)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> self._storage.get(job_id)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">find_by_customer</span><span class="params">(self, customer)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> [job <span class="keyword">for</span> job <span class="keyword">in</span> self._storage.values()</span><br><span class="line"> <span class="keyword">if</span> job.has_customer(customer)]</span><br></pre></td></tr></table></figure></p>
<p>以上写法这是符合开闭原则的,因为对于扩展成用文件保存时,只需要另外增加一个<code>InFileJobReository</code>类并写出相关实现就好了,并不会动任何一行已有代码。</p>
<p><strong>秉承鸭子类型的理念,我们还可以简化代码,可以无需写那个抽象类</strong>。只需要让子类都继承Python的<code>object</code>类,例如<code>InMemoryJobReository(object)</code>, 剩下的一个字符都不用变,也能达到同样效果,但这似乎也留下了更多犯错误的可能,其中平衡点自行拿捏。</p>
<h2 id="里氏替换原则">里氏替换原则</h2><p>为何要遵循这个原则?古猿作为基类,直立人是古猿的后代,现代人是直立人的后代,现代人可以代替直立人这是很自然的事情。这是自然法则和规律,为什么不可以应用到软件中来?如果你有一天看到个看起来像现代人,叫起来也像现代人,而她却需要充电,那她肯定是基于错误的基类生成出来的。<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">InMemoryJobReository</span><span class="params">(dict)</span>:</span></span><br><span class="line"> <span class="string">"""注意这里的继承"""</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">put</span><span class="params">(self, job)</span>:</span></span><br><span class="line"> self[job.service_id] = job</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">find_by_id</span><span class="params">(self, job_id)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> self.get(job_id)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">find_by_customer</span><span class="params">(self, customer)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> [job <span class="keyword">for</span> job <span class="keyword">in</span> self.values()</span><br><span class="line"> <span class="keyword">if</span> job.has_customer(customer)]</span><br></pre></td></tr></table></figure></p>
<p>原先是直接使用<code>dict</code>类的对象赋给<code>self._storage</code>,来完成工作。现在继承了<code>dict</code>类,相当于原来直接使用父类<code>dict</code>,而现在使用的是<code>dict</code>的子类。现在<code>InMemoryJobReository</code>的对象完全可以代替父类了。</p>
<p>Python在接口实现时并不强制性继承。就算A和B是完全不相关的类,你还可以通过<code>abc</code>模块来将A类注册给B类,让A成为B的虚拟子类。如此强大灵活,比起东拼西凑,多多考虑如何提高到代码的重用率。</p>
<h2 id="接口隔离原则">接口隔离原则</h2><p>为何要这么做?人嘴巴和鼻孔的功能应该不一样吧?否则的话,全人体只需要一个孔就行了(嘿~ 嘿~ 嘿~)。</p>
<p>本节就没有代码了,因为示例只直接地讲了一个接口,不过我们在这过程中拆分了一些接口,不是吗?</p>
<p>很多时候,根据这五条原则多多设计后再写代码,就能写出很好的代码了。这些基本原则在很多情况下也是相互促进,相互兼容,相互满足的。这过程中针对实际情况的不同你自己做的变通,也就相当于是运用了各类设计模式。比如在依赖反转一节中提到的适配器模式,控制反转模式等等。</p>
]]></content>
<summary type="html">
<![CDATA[<p>如今OOP编程大行其道。不少人以为用编程语言里的class关键字定义一个类,然后用类创建一个对象就是OOP了。肤浅!</p>
<p>OOP编程很贴近人们的正常思维方式,所以容易被接受,而且应用也很广泛。的确,这给编程带来了很大的好处。但并不是任何人都能深谙OOP的本质。简略证明如下:如果把“女朋友”作为一个类,你自己的女朋友就是你的对象,是“女朋友”类的具体的实例。如果你能熟练掌握“女朋友”这个类的特性以及方法,还不能创建一个实例么?你没女朋友,说明你没吃透女朋友类,进而说明你肯定没理解好OOP。但创建了一个实例出来也并不能证明你就吃透了这个类。<br>]]>
</summary>
<category term="OOP" scheme="http://aju.space/tags/OOP/"/>
<category term="Python" scheme="http://aju.space/tags/Python/"/>
</entry>
<entry>
<title><![CDATA[Mesosphere DCOS 常见问题]]></title>
<link href="http://aju.space/2016/04/27/Mesosphere-DCOS-Questions.html"/>
<id>http://aju.space/2016/04/27/Mesosphere-DCOS-Questions.html</id>
<published>2016-04-27T21:20:01.000Z</published>
<updated>2016-04-28T03:25:24.242Z</updated>
<content type="html"><![CDATA[<p>本文摘自Mesosphere 官网,可以从这些常见问题中明确Mesosphere DOCS的定位以及作用。并可一瞥其架构。<br><a id="more"></a></p>
<h2 id="Mesosphere_DCOS_和_Apache_Mesos有何区别?">Mesosphere DCOS 和 Apache Mesos有何区别?</h2><p>DCOS 是一个建立在开源Mesos之上的强大的有商业支持的软件产品。主要改进包括命令行和Web界面,很容易打包与安装,它还有不断壮大的技术合作伙伴生态系统。</p>
<h2 id="DCOS集群和私有云或虚拟化有何区别?">DCOS集群和私有云或虚拟化有何区别?</h2><p>DCOS是下一代的私有云。它和虚拟机之类的软件不同,因为DCOS可以组织任何形式的服务器——物理机,虚拟机,云——作为单一的共享资源池。它的Mesos内核会动态分配必要的计算、存储、网络资源,而不是预先配置的机器镜像。</p>
<h2 id="DCOS与Kuneretes、Docker这类容器技术有何区别?">DCOS与Kuneretes、Docker这类容器技术有何区别?</h2><p>DCOS对Kubernetes和Docker都支持。因为DCOS在服务器级别管理基础设施,它是运行在Linux服务器之上的,它可以大规模地启动Docker容器。Mesosphere和Google合作把Kuneretes引入DCOS。Kuneretes直接和DCOS API交互,就像任何其他的DCOS服务一样。</p>
<h2 id="我可以在DCOS里运行什么类型的工作负载?">我可以在DCOS里运行什么类型的工作负载?</h2><p>DCOS里可以运行几乎任何类型的工作负载。它支持诸如Cassandra和MemSQL这类的数据库服务,Hadoop 和 Spark这类大数据系统,也支持Marathon 和 Kubernetes这种编排服务用于托管长期运行的云应用。</p>
<h2 id="我可以编写自己的运行在DCOS上的分布式服务吗?">我可以编写自己的运行在DCOS上的分布式服务吗?</h2><p>可以。Mesosphere DCOS目前支持所有Mesos框架,并且很快将会推出软件开发工具包(SDK)以便更容易得编写新服务或者集成现有服务,即利用DCOS固有的伸缩性、灵活性和高可用性。</p>
<h2 id="Mesos和DCOS有哪些常见使用案例?">Mesos和DCOS有哪些常见使用案例?</h2><p>尽管DCOS支持几乎所有类型的工作负载,但特别流行用它支持PaSS产品,大数据分析(例如Hadoop, Spark 和 Kafka),还有HDFS、Cassandra 和 MemSQL这类有状态服务。即将支持更多样化的服务,包括MySQL这类传统数据库服务。</p>
<h2 id="我可以下载社区版的DCOS并在本地运行吗?">我可以下载社区版的DCOS并在本地运行吗?</h2><p>DCOS社区版目前还不支持下载,也不支持在本地服务器上运行。用户需要这种功能时应该联系Mesosphere了解DCOS企业版。</p>
<h2 id="我没有数千台服务器时使用DOCS还有意义吗?">我没有数千台服务器时使用DOCS还有意义吗?</h2><p>有。尽管像Twitter这种Mesos大用户受到了很多关注,但很多Mesos和DCOS早期用户在几百个或十几个节点上也能成功运行。DCOS为小型集群提供了便利,并且当你需要的时候还可以扩展至数万台节点。</p>
<h2 id="Mesosphere_DCOS开源吗?">Mesosphere DCOS开源吗?</h2><p>DCOS是不开源的,但它是建立在开源基础之上的,包括Apache Mesos, Marathon 和 Chronos。DCOS可以运行与集成许多流行的开源技术。</p>
]]></content>
<summary type="html">
<![CDATA[<p>本文摘自Mesosphere 官网,可以从这些常见问题中明确Mesosphere DOCS的定位以及作用。并可一瞥其架构。<br>]]>
</summary>
<category term="DCOS" scheme="http://aju.space/tags/DCOS/"/>
<category term="Mesos" scheme="http://aju.space/tags/Mesos/"/>
<category term="cloud" scheme="http://aju.space/tags/cloud/"/>
</entry>
<entry>
<title><![CDATA[管理多个SSH Key以支持多个Git账号]]></title>
<link href="http://aju.space/2016/04/20/use-multi-ssh-key-for-git.html"/>
<id>http://aju.space/2016/04/20/use-multi-ssh-key-for-git.html</id>
<published>2016-04-20T01:30:02.000Z</published>
<updated>2016-04-20T07:40:35.912Z</updated>
<content type="html"><![CDATA[<p>SSH Key 可以方便地解决系统登录的问题,不用每次提交代码时输入相关Git平台的登录密码。</p>
<p>由于各种各样的原因,程序猿一般都有多台电脑或多个开发系统环境,同样也有多个Git账户,最起码的可能有一个GitHub账户加公司内部的一个GitLab账户。可能不同的账户有不同的用户名,邮箱等。</p>
<h2 id="1-_生成SSH密钥">1. 生成SSH密钥</h2><ul>
<li><h4 id="生成默认公钥">生成默认公钥</h4>先确认系统中是否已有默认的SSH密钥(使用的是公钥,之后都称公钥)存在。</li>
</ul>
<figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat ~<span class="regexp">/.ssh/id</span>_rsa.pub</span><br></pre></td></tr></table></figure>
<p>如果已经有了则不用生成默认的公钥了,如果没有,则按照如下命令生成。生成后再用上述<code>cat</code>命令查看公钥。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa -C <span class="string">"<span class="variable">$your_email</span>"</span></span><br></pre></td></tr></table></figure>
<p>将上述的<code>$your_email</code>替换为你想使用的默认email地址。如我在工作电脑上生成默认公钥时邮箱使用的是公司内部邮箱。在私人电脑上则用的是本人Gmail邮箱。</p>
<a id="more"></a>
<ul>
<li><h4 id="生成更多的公钥">生成更多的公钥</h4>按如下命令生成新的公钥,注意这次要天加输出文件的名字,以免覆盖默认公钥。</li>
</ul>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa -C <span class="string">'$another_email'</span> <span class="operator">-f</span> github</span><br></pre></td></tr></table></figure>
<p>如需更多的SSH公钥按上述命令生成即可。<strong>注意输出文件名字不要相同,无需加<code>.pub</code>后缀</strong>。</p>
<h2 id="2-_多个操作系统用SSH_Key访问同一个Git平台">2. 多个操作系统用SSH Key访问同一个Git平台</h2><p>最常见的情况是我们需要在“工作电脑”和“私人电脑”中都能够方便的向同一个GitHub仓库提交代码。</p>
<p>进入<code>.ssh</code>目录,把该目录下的<code>github</code>以及<code>github.pub</code>拷贝至其他系统中的<code>.ssh</code>目录下,然后将公钥的内容复制,添加到GitHub的账户即可。gitlab、coding.net等平台类似。</p>
<p>除了拷贝的方式共用一份公钥,也可以在不同的系统里另外生成公钥,然后再逐个添加至Git平台。</p>
<h2 id="3-_同一个操作系统用不同的SSH公钥访问不同的Git平台">3. 同一个操作系统用不同的SSH公钥访问不同的Git平台</h2><ul>
<li><h4 id="创建ssh配置文件">创建ssh配置文件</h4><figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">touch ~<span class="regexp">/.ssh/config</span></span><br></pre></td></tr></table></figure>
</li>
<li><h4 id="添加ssh配置">添加ssh配置</h4><figure class="highlight crmsh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Host github <span class="comment"># ssh别名,随意取</span></span><br><span class="line"> HostName github.com <span class="comment"># 主机名</span></span><br><span class="line"> Port <span class="number">22</span> <span class="comment"># SSH端口号</span></span><br><span class="line"> PreferredAuthentications publickey <span class="comment"># 认证类型</span></span><br><span class="line"> <span class="keyword">User</span> <span class="title">git</span> <span class="comment"># 用户</span></span><br><span class="line"> IdentityFile ~/.ssh/github</span><br></pre></td></tr></table></figure>
</li>
</ul>
<p>将上述配置内容添加至 <code>~/.ssh/config</code> 文件即可。注意按需修改配置内容,勿一成不变地复制。要添加更多的Git平台账户时继续在config文件中按上述格式追加配置即可。</p>
<h2 id="4-_遇到的问题">4. 遇到的问题</h2><p>当添加配置完成后通过如下命令验证是否可以用公钥访问:</p>
<figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">ssh</span> -T git<span class="variable">@github</span>.com <span class="comment"># 这里的github取自上述 config 文件中的 Host 值</span></span><br></pre></td></tr></table></figure>
<ul>
<li><h4 id="配置文件权限错误">配置文件权限错误</h4>提示如下错误:</li>
</ul>
<figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Bad owner <span class="keyword">or</span> permissions <span class="function_start"><span class="keyword">on</span></span> /home/vagrant/.ssh/config</span><br></pre></td></tr></table></figure>
<p>原因是config文件的所有者或权限有问题。按如下命令修复:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> ~/.ssh/</span><br><span class="line">chmod <span class="number">600</span> *</span><br></pre></td></tr></table></figure>
<p>再执行<code>ssh -T github</code>得到如下结果:</p>
<figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hi denglj! You've successfully authenticated, <span class="keyword">but</span> GitHub <span class="keyword">does</span> <span class="keyword">not</span> provide shell access.</span><br></pre></td></tr></table></figure>
<ul>
<li><h4 id="访问github无权限">访问github无权限</h4>请参考资料:<a href="https://help.github.com/articles/error-permission-denied-publickey/" target="_blank" rel="external">https://help.github.com/articles/error-permission-denied-publickey/</a></li>
</ul>
]]></content>
<summary type="html">
<![CDATA[<p>SSH Key 可以方便地解决系统登录的问题,不用每次提交代码时输入相关Git平台的登录密码。</p>
<p>由于各种各样的原因,程序猿一般都有多台电脑或多个开发系统环境,同样也有多个Git账户,最起码的可能有一个GitHub账户加公司内部的一个GitLab账户。可能不同的账户有不同的用户名,邮箱等。</p>
<h2 id="1-_生成SSH密钥">1. 生成SSH密钥</h2><ul>
<li><h4 id="生成默认公钥">生成默认公钥</h4>先确认系统中是否已有默认的SSH密钥(使用的是公钥,之后都称公钥)存在。</li>
</ul>
<figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat ~<span class="regexp">/.ssh/id</span>_rsa.pub</span><br></pre></td></tr></table></figure>
<p>如果已经有了则不用生成默认的公钥了,如果没有,则按照如下命令生成。生成后再用上述<code>cat</code>命令查看公钥。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa -C <span class="string">"<span class="variable">$your_email</span>"</span></span><br></pre></td></tr></table></figure>
<p>将上述的<code>$your_email</code>替换为你想使用的默认email地址。如我在工作电脑上生成默认公钥时邮箱使用的是公司内部邮箱。在私人电脑上则用的是本人Gmail邮箱。</p>]]>
</summary>
<category term="git" scheme="http://aju.space/tags/git/"/>
<category term="ssh" scheme="http://aju.space/tags/ssh/"/>
</entry>
<entry>
<title><![CDATA[Python性能优化之记忆化(Memoization)]]></title>
<link href="http://aju.space/2016/01/22/python-memoization.html"/>
<id>http://aju.space/2016/01/22/python-memoization.html</id>
<published>2016-01-22T06:43:16.000Z</published>
<updated>2016-04-20T06:00:22.894Z</updated>
<content type="html"><![CDATA[<h2 id="记忆化(Memoization)介绍">记忆化(Memoization)介绍</h2><p>简单说来 Memoization 是一种利用缓存来加速函数调用的技术手段,将消耗较大的调用结果存储起来,当再次遇到相同调用时就从缓存读取结果而无需重新计算。<br><strong>有一个限制条件是该函数必须是纯函数式的</strong>,相当于函数式编程中的不可变性,即输入一致时输出一定不会改变。比如计算平方的函数就满足这种条件,<code>square(3)</code> 的结果永远是<code>9</code>,所以我们才能把它的结果存储起来,下次需要知道<code>3</code>的平方的结果时,无需计算,直接从内存中读取就好了。<br><a id="more"></a><br>而那些依赖于可变的参数的函数,则不可使用这种方式进行加速。比如获取系统当前登录用户数量的函数,也许参数都是固定的,但返回值却是可变的,这是由系统中即时登录的用户数量决定的,缓存起来就没意义了。</p>
<p>这里的缓存介质,可以是内存,可以是硬盘上的文件,可以是数据库,总之,能使之加速则认为是有效的。我们经常提及的 <code>memcached</code> 分布式内存对象缓存系统也是一种基于记忆化(memoization)的优化方式。今天不讲 <code>memcached</code> ,而是一种更简便小巧的办法,然而,它们的基本原理是一样的,它们都实现了LRU(Least Recently Used)缓存算法, 只缓存最少的最近使用过的数据。</p>
<h2 id="Python内置函数缓存装饰器">Python内置函数缓存装饰器</h2><p><strong><code>@functools.lru_cache(maxsize=128, typed=False)</code></strong>,<code>maxsize</code> 参数是指最大缓存多少个调用,如果赋值为 <code>None</code> 则是无限制缓存,且关闭 LRU 功能。<code>typed</code> 参数控制函数参数类型不同时是否单独缓存。设置为<code>True</code>时, 例如<code>f(3)</code>和<code>f(3.0)</code>将会区别对待。</p>
<p>下面这个例子是缓存静态web内容的:</p>
<figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> functools <span class="keyword">import</span> lru_cache</span><br><span class="line"></span><br><span class="line"><span class="decorator">@lru_cache(maxsize=32)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_pep</span><span class="params">(num)</span>:</span></span><br><span class="line"> <span class="string">'Retrieve text of a Python Enhancement Proposal'</span></span><br><span class="line"> resource = <span class="string">'http://www.python.org/dev/peps/pep-%04d/'</span> % num</span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> <span class="keyword">with</span> urllib.request.urlopen(resource) <span class="keyword">as</span> s:</span><br><span class="line"> <span class="keyword">return</span> s.read()</span><br><span class="line"> <span class="keyword">except</span> urllib.error.HTTPError:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'Not Found'</span></span><br><span class="line"></span><br><span class="line"><span class="prompt">>>> </span><span class="keyword">for</span> n <span class="keyword">in</span> <span class="number">8</span>, <span class="number">290</span>, <span class="number">308</span>, <span class="number">320</span>, <span class="number">8</span>, <span class="number">218</span>, <span class="number">320</span>, <span class="number">279</span>, <span class="number">289</span>, <span class="number">320</span>, <span class="number">9991</span>:</span><br><span class="line"><span class="prompt">... </span> pep = get_pep(n)</span><br><span class="line"><span class="prompt">... </span> print(n, len(pep))</span><br><span class="line"></span><br><span class="line"><span class="prompt">>>> </span>get_pep.cache_info()</span><br><span class="line">CacheInfo(hits=<span class="number">3</span>, misses=<span class="number">8</span>, maxsize=<span class="number">32</span>, currsize=<span class="number">8</span>)</span><br></pre></td></tr></table></figure>
<p><code>lru_cache</code>装饰器是Python3.2新增的,Python3.3新增了<code>typed</code>可选参数。</p>
<h2 id="Python2的实现">Python2的实现</h2><p>Python2中没有<code>lru_cache</code>装饰器,但是我们可以自己实现,非常简单,如下:<br><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">_CACHE_VALUES = {}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">memoized</span><span class="params">(func)</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">wrapper</span><span class="params">(x)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> _CACHE_VALUES.get(x, func(x))</span><br><span class="line"> <span class="keyword">return</span> wrapper</span><br><span class="line"></span><br><span class="line"><span class="decorator">@memoized</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">addtwo</span><span class="params">(x)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> x+<span class="number">2</span></span><br></pre></td></tr></table></figure></p>
<p>但我们上面这种写法太简单了,连缓存命中率这些统计都没有,而且也不通用。但明白了基本原理,我们要扩展也就不难了。更加复杂的装饰器实现可以参考<a href="http://ajucs.com/2016/01/14/dive-into-python-decorator.html" target="_blank" rel="external">《深入浅出Python装饰器》</a>。<br>更偷懒一点,你可以把Python源码中的lru_cahce函数相关代码拷贝到项目中,<a href="https://github.com/python/cpython/blob/77a475ebaa65aa5a85287cbe782c552039c9f507/Lib/functools.py#L391" target="_blank" rel="external">参考这里</a>。</p>
]]></content>
<summary type="html">
<![CDATA[<h2 id="记忆化(Memoization)介绍">记忆化(Memoization)介绍</h2><p>简单说来 Memoization 是一种利用缓存来加速函数调用的技术手段,将消耗较大的调用结果存储起来,当再次遇到相同调用时就从缓存读取结果而无需重新计算。<br><strong>有一个限制条件是该函数必须是纯函数式的</strong>,相当于函数式编程中的不可变性,即输入一致时输出一定不会改变。比如计算平方的函数就满足这种条件,<code>square(3)</code> 的结果永远是<code>9</code>,所以我们才能把它的结果存储起来,下次需要知道<code>3</code>的平方的结果时,无需计算,直接从内存中读取就好了。<br>]]>
</summary>
<category term="Python" scheme="http://aju.space/tags/Python/"/>
<category term="性能优化" scheme="http://aju.space/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
</entry>
<entry>
<title><![CDATA[深入浅出Python装饰器]]></title>
<link href="http://aju.space/2016/01/14/dive-into-python-decorator.html"/>
<id>http://aju.space/2016/01/14/dive-into-python-decorator.html</id>
<published>2016-01-14T05:52:34.000Z</published>
<updated>2016-04-20T06:00:22.894Z</updated>
<content type="html"><![CDATA[<p>有朋友说看到《Python CookBook》的装饰器部分有些迷糊,究其原因是没有清晰地理解Python装饰器是什么,能干什么。于是有了本文。看此文时,从头到尾跟着思路走,应该会对Python装饰器有更明白深刻的认识。新手不要跳读,新手不要跳读,新手不要跳读。<br>本文假定你已经明白了Python中什么叫做函数,变量的作用域、解析规则及生存周期。阿驹还是简单阐述一下:</p>
<ul>
<li><strong>作用域</strong>: 变量起作用的范围, 也称之为命名空间。以方法、类、函数、模块、包、Python内置环境(当前Python解释器的Python Path里所包含的)这几个的程序结构为单元,以<code>大结构</code>包含<code>小结构</code>,<code>外层</code>包含<code>内层</code>的方式,把变量放置在这样一个个的空间中,一个程序结构单元就对应一个作用域(命名空间)。每个变量,不加global和nonlocal关键字时,只属于声明该变量的这层作用域。<a id="more"></a></li>
<li><strong>变量解析规则</strong>: 根据上述可知,作用域是可以相互独立,也可以是相互嵌套存在的。而变量的解析,是从自身所在的作用域出发,逐层往上寻找。并不能去查找和自己独立并列的作用域。</li>
<li><strong>变量生存周期</strong>:变量随着它所在的程序结构单元(方法、类、函数、模块、包)的调用而生,随着调用结束而消。调用开始前和结束后试图操作该作用域内的变量,都将出错。</li>
</ul>
<p><strong>一定要深刻理解以上三点。一定要深刻理解以上三点。一定要深刻理解以上三点。</strong><br>在正式讲解装饰器之前解释上面三个概念的原因是装饰器就是根据这三个概念玩的花样。也是《Python之禅》中最后一句话“Namespaces are one honking great idea – let’s do more of those!”的一个具体体现。</p>
<h2 id="Python函数的特点">Python函数的特点</h2><ul>
<li>是对象,与其他对象无异,具有属性,可以作为参数传递、返回等</li>
<li>函数里可以再声明函数(嵌套)</li>
</ul>
<h2 id="函数闭包">函数闭包</h2><p>一个来自于Scheme的概念,被诸多函数式编程语言实现。也许你听说过装饰器是基于闭包实现的,那么闭包的本质是什么呢?我们先不对闭包下定义。(翠花儿,上代码!)<br><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">outer</span><span class="params">()</span>:</span></span><br><span class="line"> x, y, z = <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">inner</span><span class="params">()</span>:</span></span><br><span class="line"> a, b = <span class="number">4</span>, <span class="number">5</span></span><br><span class="line"> print(<span class="string">"inner's vars sum:"</span>, a+b)</span><br><span class="line"> print(<span class="string">"use var y of outer:"</span>, y)</span><br><span class="line"> <span class="keyword">return</span> inner</span><br><span class="line">f = outer()</span><br><span class="line">f()</span><br><span class="line"><span class="comment"># 输出结果</span></span><br><span class="line">(<span class="string">"inner's vars sum:"</span>, <span class="number">9</span>)</span><br><span class="line">(<span class="string">'use var y of outer:'</span>, <span class="number">2</span>)</span><br></pre></td></tr></table></figure></p>
<p>仔细看上述代码。在inner中使用变量y时,根据解析规则,因为inner自己的作用域中未找到,故而到上层的outer的作用域中查找,打印的两行结果也是正确的。<br><strong>但是,但是,但是</strong>,有一个问题,我们说过变量的生存周期。不是说函数调用结束时,变量就销毁了吗? <code>f = outer()</code> 执行完时按理说x, y, z及内部的inner函数都应当被销毁了呀?!你也许会想,调用结束时把inner返回给了<code>f</code>呀,当然能执行。思考一个问题,<code>f</code> 是 <code>inner</code> 吗?换个问法,这里等号赋值,是把<code>inner</code>的引用传递给了<code>f</code>吗?<br><code>f</code> 不是 <code>inner</code> ,因为inner只包含其def声明开始,函数体结束为止的内容,其作用域中只有a,b两个变量,在我们调用f时,却正确打印出了outer作用域中的变量。<br>似乎,<code>f</code> 是 <code>inner</code> 加上 <code>y</code> 的合体。对了,这就引出了闭包的概念,<code>f</code>是一个闭包。下面来给闭包概念做个确切的表述。</p>
<blockquote>
<p>嵌套定义在非全局作用域中的函数,当它的外部函数被调用就会生成一个闭包,闭包中包含了这个子函数本身的代码与其依赖的外部变量的引用。</p>
</blockquote>
<p>注:Python的一个py文件就是一个模块,就是一个全局作用域。闭包不是这个子函数, 而是这个子程序与其依赖的外部变量构成的整体。这个整体构成了一个新的封闭的作用域,所以叫闭包。<strong>上例中,inner子程序并没依赖外部的x和z,所以这个闭包中不包含x,z。</strong>也可以把闭包当做一个新的函数来对待,不过这个新函数的逻辑代码还是原来的子函数代码,但其作用域,却包含了原子函数的变量和其依赖的外部变量。</p>
<p><strong>另外,每次调用外部函数,其内部函数都会被重新定义,就会生成一个新的闭包。</strong> 这是同一个装饰器可以作用于不同的函数的基础。如下例,其本质不是分别传递参数1,2给inner,而是生成了两个能打印各自数字的闭包。<br><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">outer</span><span class="params">(x)</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">inner</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="keyword">print</span> x</span><br><span class="line"> <span class="keyword">return</span> inner</span><br><span class="line">print1 = outer(<span class="number">1</span>)</span><br><span class="line">print2 = outer(<span class="number">2</span>)</span><br><span class="line">print1()</span><br><span class="line"><span class="comment"># 输出</span></span><br><span class="line"><span class="number">1</span></span><br><span class="line">print2()</span><br><span class="line"><span class="comment"># 输出</span></span><br><span class="line"><span class="number">2</span></span><br></pre></td></tr></table></figure></p>
<h2 id="“纯手工”装饰器">“纯手工”装饰器</h2><p>在上例中,我们给<code>outer</code>函数传递的参数是一个整数,然后对这个整数进行了处理(某种装饰和加强,虽然这里只是print了一下)。如果我们传递的是一个函数呢?那它就成了装饰器!我们可以在inner内部执行额外的操作,再返回一个闭包。而这个返回的闭包,就是原函数被装饰后的版本(代替加强版)。<strong>装饰器的本质就是函数闭包,或者说利用了函数闭包的特性。</strong><br><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">outer</span><span class="params">(function)</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">inner</span><span class="params">()</span>:</span></span><br><span class="line"> print(<span class="string">"执行function之前可以进行额外操作"</span>)</span><br><span class="line"> result = function()</span><br><span class="line"> print(<span class="string">"执行function之后还可以进行额外操作"</span>)</span><br><span class="line"> result *= <span class="number">2</span> <span class="comment"># 对function的返回值本身进行额外操作</span></span><br><span class="line"> <span class="keyword">return</span> result <span class="comment"># 返回‘加强’后的结果</span></span><br><span class="line"> <span class="keyword">return</span> inner</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">wait_for_deco</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">1024</span></span><br><span class="line"></span><br><span class="line">decorated = outer(wait_for_deco)</span><br><span class="line"><span class="keyword">print</span> decorated()</span><br><span class="line"><span class="comment"># 输出</span></span><br><span class="line"><span class="number">2048</span></span><br></pre></td></tr></table></figure></p>
<p>上例就是纯手工实现的一个最简单的装饰器。装饰器函数<code>outer</code>并没有修改被装饰函数<code>wait_for_deco</code>,但我们调用被装饰后的<code>decorated</code>函数闭包却能够得到原函数的加强版结果,还能进行额外的操作。<br>为了让返回的闭包函数看起来就像是原函数的加强版,我们只需要像下面这么做。<br><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wait_for_deco = outer(wait_for_deco)</span><br></pre></td></tr></table></figure></p>
<p>为了简化代码,Python为我们提供了装饰符<code>@</code>,只需要在<code>wait_for_deco</code>上面加上<code>@outer</code>就可以了。<strong>实际上装饰符<code>@</code>就仅仅是帮我们自动地把返回的闭包函数名字替换为原函数的名字。</strong> 使返回后的新函数(闭包)看起来就是原函数,不过是加强了的。<br><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># outer 函数不变</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用装饰符简化代码</span></span><br><span class="line"><span class="decorator">@outer</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">wait_for_deco</span><span class="params">()</span>:</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">1024</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">print</span> wait_for_deco()</span><br><span class="line"><span class="comment"># 输出</span></span><br><span class="line"><span class="number">2048</span></span><br></pre></td></tr></table></figure></p>
<p>以上,装饰器的来龙去脉就讲清楚了。但是它太简单了,被装饰的函数没有参数,装饰器也没有参数。接下来就是对装饰器进行扩展。比如写出能接受任何函数的装饰器,以及装饰器本身可以带参数,以及用类作装饰器,还有装饰器的实际应用。</p>
<h2 id="支持带参数的函数">支持带参数的函数</h2><p>刚才我们提到,装饰器最终返回的是一个闭包,而这个闭包可以看做一个函数,它是原函数的加强版。即是,调用原函数,变成了调用这个被装饰后的闭包。那么原函数的参数如果能够按原样传递给这个闭包函数的话,那么在装饰器中我们应该在其内部函数的定义中按原函数的格式写上参数。这样调用这个闭包时就可以按原来的样子传递参数了。<br><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"># 例如为下面的函数写一个装饰器,应该在内部的wapper中按原样传递参数</span><br><span class="line">def decorator(func):</span><br><span class="line"> def wrapper(x, y)</span><br><span class="line"> ret = func(x, y) # 原函数的返回值</span><br><span class="line"> return ret*2 # 原函数的结果“加强”后再返回</span><br><span class="line"> return wrapper</span><br><span class="line"></span><br><span class="line">@decorator</span><br><span class="line">def wait_for_deco(x, y):</span><br><span class="line"> return x + y</span><br><span class="line"></span><br><span class="line">print(wait_for_deco(1, 2))</span><br><span class="line"></span><br><span class="line"># 输出</span><br><span class="line">6</span><br></pre></td></tr></table></figure></p>
<p>按照上面这种写法虽然可以传参了但有个缺陷,参数个数不确定的函数就没法使用这个装饰器了。比如,原函数有x, y, z三个参数,也想有把结果放大两倍的装饰器呢?能写出通用装饰器吗?能。<br><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">def decorator(func):</span><br><span class="line"> def wrapper(*args, **kwargs)</span><br><span class="line"> ret = func(*args, **kwargs)</span><br><span class="line"> return ret*2</span><br><span class="line"> return wrapper</span><br><span class="line"></span><br><span class="line">@decorator</span><br><span class="line">def wait_for_deco_a(x, y):</span><br><span class="line"> return x + y</span><br><span class="line"></span><br><span class="line">@decorator</span><br><span class="line">def wait_for_deco_b(x, y, z):</span><br><span class="line"> return x + y + z</span><br><span class="line"></span><br><span class="line">print(wait_for_deco_a(1, 2))</span><br><span class="line">6</span><br><span class="line">print(wait_for_deco_b(1, 2, 3))</span><br><span class="line">12</span><br></pre></td></tr></table></figure></p>
<p>现在我们可以让装饰器装饰任何形式传参的函数了。而以上两个被装饰的原函数也可以根据任意参数的匹配来简化为一个函数,不属于本文探讨的内容了。<code>*args, **kwargs</code> 的具体使用方法和原理,这是Python基础内容,不明白的可以看《Python学习手册》作用域和参数那一章。</p>
<h2 id="装饰器带参数">装饰器带参数</h2><p>前文中讲解了,装饰符<code>@</code>只是帮我们把返回的闭包名字替换为了和原函数一样的名字。像下面这种操作:<br><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">after_decorated = decorator(origin_func)</span><br><span class="line"><span class="string">'@'</span>符号做就是</span><br><span class="line">origin_func = decorator(origin_func) <span class="comment"># 得到的是已装饰后的闭包函数</span></span><br></pre></td></tr></table></figure></p>
<p>我们只需要记住一点,最终装饰器需要返回一个<strong>可调用的</strong>对象(闭包函数),我们才能把原函数作为第一个参数传进去。而要装饰器支持参数,类似于下面这样:<br><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="decorator">@decorator(args)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">wait_for_deco</span><span class="params">(x, y)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> x + y</span><br></pre></td></tr></table></figure></p>
<p>按照我们上面讲的<code>@</code>的作用,解释器会把上面这个带参数的装饰器像下面这样执行:<br><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wait_for_deco = decorator(args)(x, y)</span><br></pre></td></tr></table></figure></p>
<p>聪明的你已经想到了,<code>decrator(args)</code>返回的是最终需要的装饰器就好了。所以,带参数的装饰器就需要写成下面这样:<br><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">decorator</span><span class="params">(name)</span>:</span></span><br><span class="line"> print(<span class="string">"在这里使用装饰器的name参数:"</span>, name)</span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">wrapper</span><span class="params">(func)</span>:</span></span><br><span class="line"> print(<span class="string">"在这里也可用装饰器的name参数:"</span>, name)</span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">_wrapper</span><span class="params">(*args, **kwargs)</span>:</span></span><br><span class="line"> print(<span class="string">"这里还可使用装饰器的name参数:"</span>, name)</span><br><span class="line"> ret = func(*args, **kwargs) <span class="comment"># 这里进行原函数的计算</span></span><br><span class="line"> <span class="keyword">return</span> ret*<span class="number">2</span></span><br><span class="line"> <span class="keyword">return</span> _wrapper <span class="comment"># 返回可调用对象,_wrapper可以接受原函数的参数</span></span><br><span class="line"> <span class="keyword">return</span> wrapper <span class="comment"># 返回真正的装饰器,接受原函数作为第一个参数</span></span><br><span class="line"></span><br><span class="line"><span class="decorator">@decorator('haha')</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">wait_for_deco</span><span class="params">(x, y)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> x + y</span><br></pre></td></tr></table></figure></p>
<h2 id="用类(class)作装饰器">用类(class)作装饰器</h2><h2 id="同时使用多个装饰器">同时使用多个装饰器</h2><h2 id="装饰器实例">装饰器实例</h2>]]></content>
<summary type="html">
<![CDATA[<p>有朋友说看到《Python CookBook》的装饰器部分有些迷糊,究其原因是没有清晰地理解Python装饰器是什么,能干什么。于是有了本文。看此文时,从头到尾跟着思路走,应该会对Python装饰器有更明白深刻的认识。新手不要跳读,新手不要跳读,新手不要跳读。<br>本文假定你已经明白了Python中什么叫做函数,变量的作用域、解析规则及生存周期。阿驹还是简单阐述一下:</p>
<ul>
<li><strong>作用域</strong>: 变量起作用的范围, 也称之为命名空间。以方法、类、函数、模块、包、Python内置环境(当前Python解释器的Python Path里所包含的)这几个的程序结构为单元,以<code>大结构</code>包含<code>小结构</code>,<code>外层</code>包含<code>内层</code>的方式,把变量放置在这样一个个的空间中,一个程序结构单元就对应一个作用域(命名空间)。每个变量,不加global和nonlocal关键字时,只属于声明该变量的这层作用域。]]>
</summary>
<category term="Python" scheme="http://aju.space/tags/Python/"/>
</entry>
<entry>
<title><![CDATA[Python入门、基础书籍简评与推荐]]></title>
<link href="http://aju.space/2015/12/21/getting-started-with-python-books.html"/>
<id>http://aju.space/2015/12/21/getting-started-with-python-books.html</id>
<published>2015-12-21T06:33:34.000Z</published>
<updated>2016-04-20T06:00:22.894Z</updated>
<content type="html"><![CDATA[<p>本文对Python网友讨论较多的几本基础入门书籍做个简评,给新手朋友们一个参考。本文仅代表阿驹的个人对它们的意见,不一定完全正确,但站在阿驹的角度看来,是很中肯的。</p>
<h3 id="《简明Python教程》">《简明Python教程》</h3><ul>
<li>英文原版名为《A Byte of Python》。它非常简明扼要地介绍了Python语言基础的各个方面。</li>
<li>适合群体:<ol>
<li>仅仅临时使用Python的新手,非专业性地临时运用十来行简单代码就能自动化处理一些工作。</li>
<li>非常熟练编程的老手,深谙编程本质,换语言不过是换工具而已,通过简要地提点,其他诸多方面自然通晓,辅以浏览官方文档和《Python Cookbook》此类书籍,就和Python熟手无异。<a id="more"></a>
</li>
</ol>
</li>
</ul>
<h3 id="《Python基础教程》">《Python基础教程》</h3><ul>
<li>英文原版名为《Beginning Python: from Novice to Professional》,直译过来可以叫《Python入门:从新手到专业》。从如何安装Python运行环境,到一些计算机程序专业术语的解释,再到Python基本数据结构、语法的讲解,再到面向对象程序设计基础, 再是IO、GUI、数据库、网络编程、程序测试、Web开发, Python的C、Java、C#扩展的介绍。还有几个小的练习项目。<br>中规中矩的目录结构与基础体系介绍,像网络、IO、GUI及其之后的章节都是浅尝辄止。但有一定的宽度,对Python进行了较为广泛地涉猎。</li>
<li>适合群体:<ol>
<li>计算机相关专业学生(应该是面对各项专业科目考试都应付自如的水平),混日子的学生就不要把自己划为此列了。</li>
<li>有对编程有较为正确认知的人,比如偶尔要写写小脚本的运维人员,需要更全面掌握Python的。</li>
</ol>
</li>
</ul>
<h3 id="《Python核心编程》">《Python核心编程》</h3><ul>
<li>我初览此书的第一印象:较为全面,由浅入深,也不是很深,比上述基础教程深些。其第一章介绍Python时就谈及程序的健壮性、可移植性、快速原型开发、Python与其他语言的比较,可见并不是为0基础,也不是为编程初学者准备的。<br>再是第二印象,对中文版的:翻译不妥当的地方较多,印刷错误较多。加之中文内容大多数都来自网络社区,而“译者”只署自己一人之名的行径确实有待商榷。既然内容来自网络社区,那么,翻译的水平自然是参差不齐,读起来觉得行文风格不一的怪异也就难免了。故而不推荐中文版。<br>原版倒是针对有编程经验的同学很有帮助。</li>
<li>适合群体:<ol>
<li>英语阅读能力良好的有其他语言实践编程经验(非学校二三十行的家庭作业)的人,并期望较为熟练掌握Python运用于自己实际工程项目。</li>
</ol>
</li>
</ul>
<h3 id="《Python学习手册》">《Python学习手册》</h3><ul>
<li>详细,详细,详细。对Python语言基础(不含标准库)进行了非常详细地介绍与深入剖析,让你明白Python语言基础中“是什么”,“为什么”,“怎么样”。其内容包括什么是动态语言,什么是函数,什么是类,如何设计类,如何设计模块,什么是异常与处理异常……</li>
<li>适合群体:<ol>
<li>0基础,对新事物接受能力较强,需要真的有耐心咀嚼书中内容,并动手实践练习的人。</li>
<li>有任何层次的编程基础,也希望明白Python语言各个细节“为什么是那样”的人。</li>
</ol>
</li>
</ul>
<h3 id="《像计算机科学家一样思考Python》">《像计算机科学家一样思考Python》</h3><ul>
<li>第一章就告诉了你程序的本质,什么是调试,如何调试,什么是运行时错误,什么是语法错误。后续章节开始介绍了Python的基本数据类型,Python代码如何编写,接口应该怎么设计,对面向对象编程也进行了专业的讲解,另外附带Tkinter GUI编程的介绍。<br>这本书从专业地角度引导读者学习Python,像在介绍列表之类的基本数据类型,还会阐明它为什么会这样,在内存中会怎样,Python为何这样实现,以及在介绍数据结构的同时附带介绍了和数据结构相关的一些算法。</li>
<li>适合群体:<ol>
<li>程序设计理论知识相当扎实的学生;</li>
<li>程序设计理论知识匮乏及对算法在程序中的运用理解浅显的在职编程人员。</li>
</ol>
</li>
</ul>
<h3 id="《Python_Cookbook》">《Python Cookbook》</h3><ul>
<li>Python实践运用中相当多的操作技巧,这些都是可以直接运用于工程项目中的,涉及Python的各方面。</li>
<li>适合群体:<ol>
<li>掌握了Python基础,实践过程中感觉自己能力尚有欠缺的人。</li>
</ol>
</li>
</ul>
<h3 id="《深入理解Python》">《深入理解Python》</h3><ul>
<li>英文原版名为《Dive Into Python》。从零零散散地各个方面说明的强大功能或者是更好的一些细节技巧,比如and和or运算如何优化之类的。不过此书不太推荐,老了,网络翻译也一般,深度也不如《Pytho Cookbook》和《Effective Python: 59 Specific Ways to Write Better Python》。《编写高质量代码:改善Python程序的91个建议》这本网络博文搜集整理的书籍就是拾”DIP”和”EP”这两本书的牙慧。</li>
<li>适合群体:<ol>
<li>掌握了Python基础,想要写出更Pythonic代码的人。记住,阿驹实际上已经推荐的是《Effective Python》了。</li>
</ol>
</li>
</ul>
<h3 id="《深入浅出Python》">《深入浅出Python》</h3><ul>
<li>英文原版名为《Head First Python》, “Head First”系列书籍封面阿驹一眼看上去太cute了,感觉不是自己的菜,所以此系列书籍几乎很少阅读。至于Python这本,粗略浏览了一下,有基础介绍,但会从基础引导读者思考在工程实践中可能的运用。</li>
<li>适合群体:<ol>
<li>最好有计算机专业基础和一门较为熟练的编程语言,初入职场从事Python编程的同学应该会有所用。</li>
</ol>
</li>
</ul>
<h3 id="《笨办法学Python》">《笨办法学Python》</h3><ul>
<li>以输入是什么,运行程序,在屏幕上输出结果应该是什么这样的方式来教学。算是手把手之类的教学。以Python语言基础为主。书后“怎么学习任何一门编程语言”这节,没有编程基础的新手应该看看。</li>
<li>适合群体:<ol>
<li>编程0基础的,但有自学能力的。</li>
<li>程序设计上你可以是0基础,但对计算机和软件的认识不能是0基础,至少,应该对中小学计算机教材基础里面提到的知识点都知其然知其一些所以然。</li>
</ol>
</li>
</ul>
<h3 id="其他">其他</h3><p>以下书籍没有去认真了解过,连目录、前言、以及开始章节都没仔细研究过,也就不评了。不过我觉得,真的是对计算机和编程一窍不通,只是为了了解编程而学习的人,可以看看以下书籍,找一本自己喜欢的。</p>
<ul>
<li>《可爱的Python》</li>
<li>《父与子的编程之旅:与小卡特一起学Python》</li>
<li>《趣学Python》</li>
<li>《与孩子一起学编程》</li>
</ul>
]]></content>
<summary type="html">
<![CDATA[<p>本文对Python网友讨论较多的几本基础入门书籍做个简评,给新手朋友们一个参考。本文仅代表阿驹的个人对它们的意见,不一定完全正确,但站在阿驹的角度看来,是很中肯的。</p>
<h3 id="《简明Python教程》">《简明Python教程》</h3><ul>
<li>英文原版名为《A Byte of Python》。它非常简明扼要地介绍了Python语言基础的各个方面。</li>
<li>适合群体:<ol>
<li>仅仅临时使用Python的新手,非专业性地临时运用十来行简单代码就能自动化处理一些工作。</li>
<li>非常熟练编程的老手,深谙编程本质,换语言不过是换工具而已,通过简要地提点,其他诸多方面自然通晓,辅以浏览官方文档和《Python Cookbook》此类书籍,就和Python熟手无异。]]>
</summary>
<category term="Python" scheme="http://aju.space/tags/Python/"/>
</entry>
<entry>
<title><![CDATA[简单明了地选择Python GUI库]]></title>
<link href="http://aju.space/2015/12/08/how-to-choose-python-GUI-toolkit-library.html"/>
<id>http://aju.space/2015/12/08/how-to-choose-python-GUI-toolkit-library.html</id>
<published>2015-12-08T08:40:50.000Z</published>
<updated>2016-04-20T06:00:22.894Z</updated>
<content type="html"><![CDATA[<p>可用于Python GUI程序开发,优秀且更新“跟得上时代”的库有 <strong>wxPython</strong> 、<strong>PyQT</strong>(PySide)、<strong>Kivy</strong> 、<strong>Libavg</strong> ,当然还有 Python 内置的 <strong>Tkinter</strong> 。像 PyGUI 、PyGTK 、PySWT 等,由于上次更新距现在太为久远且文档、学习资料、稳定性等诸多考虑,阿驹认为它们并不适合于开发一个正式的项目,就不作介绍。</p>
<h2 id="各种_Python_GUI_库简介">各种 Python GUI 库简介</h2><h3 id="1-_wxPython">1. wxPython</h3><ul>
<li>跨平台,Windows / Unix / unix-like / OS X</li>
<li>包装的是 <a href="http://wxwidgets.org/" target="_blank" rel="external">wxWidgets</a>,C++写成的</li>
<li>完全开源,可以任意用于自己的开源或商业项目</li>
<li>不支持 Python3.x (新启了一个名为 Phoenix 项目,支持 Python 3.x,尚未正式发布)</li>
<li>学习资料《wxPython in Action》、《wxPython 2.8 Application Development Cookbook》,均有中文版</li>
<li>有第三方提供的图形化界面设计工具,但还是有瑕疵,不如PyQT的强大</li>
<li>看起来非常本地化地界面<a id="more"></a>
</li>
</ul>
<h3 id="2-_PyQT">2. PyQT</h3><ul>
<li>跨平台</li>
<li>对QT的包装,QT也是C++实现的</li>
<li>多种授权协议可选,若要开发商业软件,需要花钱购买商业授权</li>
<li>有自带的功能强大的图形界面设计工具</li>
<li>支持Python 2 & 3</li>
<li>缺乏成体系的中文学习资料,英文资料足够丰富</li>
<li>QT 的内存模型和 Python 的内存模型不一致,使用不当可能会导致内存泄露</li>
<li>打包后的程序体积较为庞大</li>
</ul>
<h3 id="3-_Tkinter">3. Tkinter</h3><ul>
<li>Python内置GUI库</li>
<li>简单易用</li>
<li>功能性较 PyQT 和 wxPython 差很多</li>
<li>支持Python 2 & 3</li>
<li>有第三方提供的图形化界面设计工具</li>
<li>有一定量的中文学习资料,英文资料足够丰富</li>
<li>看起来不那么本地化地界面</li>
</ul>
<h3 id="4-_Libavg">4. Libavg</h3><ul>
<li>跨平台</li>
<li>用于以多媒体(图象、文本、音频、视频、数码输出、矢量制图)为主的GUI程序开发高级库(“高级”二字在计算机文档中,通常指相对于“计算机/软件底层”来说的一种抽象概念,“高级”绝不等同于“功能更强,高大上”)</li>
<li>支持多点触控、手势识别的系统和设备</li>
<li>基于LGPL协议</li>
<li>支持Python 2 & 3</li>
<li>几乎没有中文学习资料,英文学习资料也较少,就官方文档较为丰富</li>
</ul>
<h3 id="5-_Kivy">5. Kivy</h3><ul>
<li>跨平台,除了PC机平台,还支持<strong>Android / iOS / Raspberry Pi</strong></li>
<li>支持多点触控</li>
<li>核心基于OpenGL ES2,可利用GPU加速</li>
<li>支持Python 2 & 3,不过Python 3的还不那么完善,如果要用于iOS和Android的开发,使用Python 2.7</li>
<li>中文学习资料非常少,英文文档足够丰富</li>
</ul>
<h2 id="如何选择GUI库">如何选择GUI库</h2><ul>
<li>新手,仅仅像尝尝Python GUI编程的乐趣: Tkinter</li>
<li>想开发基于PC,功能复杂的商业项目,愿意花钱购买版权: PyQT</li>
<li>想开发基于PC,功能复杂的商业项目,不愿意花钱购买版权,考虑Python3支持: Kivy</li>
<li>想开发基于PC,功能复杂的商业项目,不愿意花钱购买版权,暂不考虑Python3,要有良好的本地化表现: wxPython</li>
<li>想开发基于PC,功能强大,能快速可视化设计的开源软件:PyQT</li>
<li>想开发基于PC,功能复杂的软件,阅读英文资料非常吃力: wxPython</li>
<li>想开发移动应用,掌上游戏之类的:Kivy</li>
<li>想开发丰富的多媒体软件:Libavg</li>
</ul>
]]></content>
<summary type="html">
<![CDATA[<p>可用于Python GUI程序开发,优秀且更新“跟得上时代”的库有 <strong>wxPython</strong> 、<strong>PyQT</strong>(PySide)、<strong>Kivy</strong> 、<strong>Libavg</strong> ,当然还有 Python 内置的 <strong>Tkinter</strong> 。像 PyGUI 、PyGTK 、PySWT 等,由于上次更新距现在太为久远且文档、学习资料、稳定性等诸多考虑,阿驹认为它们并不适合于开发一个正式的项目,就不作介绍。</p>
<h2 id="各种_Python_GUI_库简介">各种 Python GUI 库简介</h2><h3 id="1-_wxPython">1. wxPython</h3><ul>
<li>跨平台,Windows / Unix / unix-like / OS X</li>
<li>包装的是 <a href="http://wxwidgets.org/">wxWidgets</a>,C++写成的</li>
<li>完全开源,可以任意用于自己的开源或商业项目</li>
<li>不支持 Python3.x (新启了一个名为 Phoenix 项目,支持 Python 3.x,尚未正式发布)</li>
<li>学习资料《wxPython in Action》、《wxPython 2.8 Application Development Cookbook》,均有中文版</li>
<li>有第三方提供的图形化界面设计工具,但还是有瑕疵,不如PyQT的强大</li>
<li>看起来非常本地化地界面]]>
</summary>
<category term="GUI" scheme="http://aju.space/tags/GUI/"/>
<category term="Kivy" scheme="http://aju.space/tags/Kivy/"/>
<category term="PyQT" scheme="http://aju.space/tags/PyQT/"/>
<category term="Python" scheme="http://aju.space/tags/Python/"/>
<category term="wxPython" scheme="http://aju.space/tags/wxPython/"/>
</entry>
<entry>
<title><![CDATA[Python web 加密下载链接的实现]]></title>
<link href="http://aju.space/2015/12/03/implemention-of-encrypt-download-links.html"/>
<id>http://aju.space/2015/12/03/implemention-of-encrypt-download-links.html</id>
<published>2015-12-03T10:22:28.000Z</published>
<updated>2016-04-20T06:00:22.894Z</updated>