forked from sixu05202004/pythontutorial3
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclasses.html
More file actions
777 lines (673 loc) · 89.1 KB
/
classes.html
File metadata and controls
777 lines (673 loc) · 89.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>9. 类 — Python tutorial 3.5.2 documentation</title>
<link href='https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic|Roboto+Slab:400,700|Inconsolata:400,700&subset=latin,cyrillic' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="top" title="Python tutorial 3.5.2 documentation" href="index.html"/>
<link rel="next" title="10. Python 标准库概览" href="stdlib.html"/>
<link rel="prev" title="8. 错误和异常" href="errors.html"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js"></script>
</head>
<body class="wy-body-for-nav" role="document">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-nav-search">
<a href="index.html" class="fa fa-home"> Python tutorial</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="appetite.html">1. 开胃菜</a></li>
<li class="toctree-l1"><a class="reference internal" href="interpreter.html">2. 使用 Python 解释器</a><ul>
<li class="toctree-l2"><a class="reference internal" href="interpreter.html#tut-invoking">2.1. 调用 Python 解释器</a></li>
<li class="toctree-l2"><a class="reference internal" href="interpreter.html#tut-interp">2.2. 解释器及其环境</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="introduction.html">3. Python 简介</a><ul>
<li class="toctree-l2"><a class="reference internal" href="introduction.html#tut-calculator">3.1. 将 Python 当做计算器</a></li>
<li class="toctree-l2"><a class="reference internal" href="introduction.html#tut-firststeps">3.2. 编程的第一步</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="controlflow.html">4. 深入 Python 流程控制</a><ul>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#if">4.1. if 语句</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#for">4.2. for 语句</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#range">4.3. range() 函数</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#break-continue-else">4.4. break 和 continue 语句, 以及循环中的 else 子句</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#pass">4.5. pass 语句</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#tut-functions">4.6. 定义函数</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#tut-defining">4.7. 深入 Python 函数定义</a></li>
<li class="toctree-l2"><a class="reference internal" href="controlflow.html#tut-codingstyle">4.8. 插曲:编码风格</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="datastructures.html">5. 数据结构</a><ul>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-morelists">5.1. 关于列表更多的内容</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#del">5.2. del 语句</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-tuples">5.3. 元组和序列</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-sets">5.4. 集合</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-dictionaries">5.5. 字典</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-loopidioms">5.6. 循环技巧</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-conditions">5.7. 深入条件控制</a></li>
<li class="toctree-l2"><a class="reference internal" href="datastructures.html#tut-comparing">5.8. 比较序列和其它类型</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="modules.html">6. 模块</a><ul>
<li class="toctree-l2"><a class="reference internal" href="modules.html#tut-moremodules">6.1. 深入模块</a></li>
<li class="toctree-l2"><a class="reference internal" href="modules.html#tut-standardmodules">6.2. 标准模块</a></li>
<li class="toctree-l2"><a class="reference internal" href="modules.html#dir">6.3. dir() 函数</a></li>
<li class="toctree-l2"><a class="reference internal" href="modules.html#tut-packages">6.4. 包</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="inputoutput.html">7. 输入和输出</a><ul>
<li class="toctree-l2"><a class="reference internal" href="inputoutput.html#tut-formatting">7.1. 格式化输出</a></li>
<li class="toctree-l2"><a class="reference internal" href="inputoutput.html#tut-files">7.2. 文件读写</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="errors.html">8. 错误和异常</a><ul>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-syntaxerrors">8.1. 语法错误</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-exceptions">8.2. 异常</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-handling">8.3. 异常处理</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-raising">8.4. 抛出异常</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-userexceptions">8.5. 用户自定义异常</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-cleanup">8.6. 定义清理行为</a></li>
<li class="toctree-l2"><a class="reference internal" href="errors.html#tut-cleanup-with">8.7. 预定义清理行为</a></li>
</ul>
</li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">9. 类</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#tut-object">9.1. 术语相关</a></li>
<li class="toctree-l2"><a class="reference internal" href="#python">9.2. Python 作用域和命名空间</a></li>
<li class="toctree-l2"><a class="reference internal" href="#tut-firstclasses">9.3. 初识类</a></li>
<li class="toctree-l2"><a class="reference internal" href="#tut-remarks">9.4. 一些说明</a></li>
<li class="toctree-l2"><a class="reference internal" href="#tut-inheritance">9.5. 继承</a></li>
<li class="toctree-l2"><a class="reference internal" href="#tut-private">9.6. 私有变量</a></li>
<li class="toctree-l2"><a class="reference internal" href="#tut-odds">9.7. 补充</a></li>
<li class="toctree-l2"><a class="reference internal" href="#tut-exceptionclasses">9.8. 异常也是类</a></li>
<li class="toctree-l2"><a class="reference internal" href="#tut-iterators">9.9. 迭代器</a></li>
<li class="toctree-l2"><a class="reference internal" href="#tut-generators">9.10. 生成器</a></li>
<li class="toctree-l2"><a class="reference internal" href="#tut-genexps">9.11. 生成器表达式</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="stdlib.html">10. Python 标准库概览</a><ul>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-os-interface">10.1. 操作系统接口</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-file-wildcards">10.2. 文件通配符</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-command-line-arguments">10.3. 命令行参数</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-stderr">10.4. 错误输出重定向和程序终止</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-string-pattern-matching">10.5. 字符串正则匹配</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-mathematics">10.6. 数学</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-internet-access">10.7. 互联网访问</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-dates-and-times">10.8. 日期和时间</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-data-compression">10.9. 数据压缩</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-performance-measurement">10.10. 性能度量</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-quality-control">10.11. 质量控制</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib.html#tut-batteries-included">10.12. “瑞士军刀”</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="stdlib2.html">11. 标准库浏览 – Part II</a><ul>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-output-formatting">11.1. 输出格式</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-templating">11.2. 模板</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-binary-formats">11.3. 使用二进制数据记录布局</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-multi-threading">11.4. 多线程</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-logging">11.5. 日志</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-weak-references">11.6. 弱引用</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-list-tools">11.7. 列表工具</a></li>
<li class="toctree-l2"><a class="reference internal" href="stdlib2.html#tut-decimal-fp">11.8. 十进制浮点数算法</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="venv.html">12. 虚拟环境和包</a><ul>
<li class="toctree-l2"><a class="reference internal" href="venv.html#id2">12.1. 简介</a></li>
<li class="toctree-l2"><a class="reference internal" href="venv.html#id4">12.2. 创建虚拟环境</a></li>
<li class="toctree-l2"><a class="reference internal" href="venv.html#pip">12.3. 使用 pip 管理包</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="whatnow.html">13. 接下来?</a></li>
<li class="toctree-l1"><a class="reference internal" href="interactive.html">14. 交互式输入行编辑历史回溯</a><ul>
<li class="toctree-l2"><a class="reference internal" href="interactive.html#tab">14.1. Tab 补全和历史记录</a></li>
<li class="toctree-l2"><a class="reference internal" href="interactive.html#tut-commentary">14.2. 其它交互式解释器</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="floatingpoint.html">15. 浮点数算法:争议和限制</a><ul>
<li class="toctree-l2"><a class="reference internal" href="floatingpoint.html#tut-fp-error">15.1. 表达错误</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="appendix.html">16. 附录</a><ul>
<li class="toctree-l2"><a class="reference internal" href="appendix.html#tut-interac">16.1. 交互模式</a></li>
</ul>
</li>
</ul>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" role="navigation" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Python tutorial</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html">Docs</a> »</li>
<li>9. 类</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/classes.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document">
<div class="section" id="tut-classes">
<span id="id1"></span><h1>9. 类<a class="headerlink" href="#tut-classes" title="Permalink to this headline">¶</a></h1>
<p>Python 的类机制通过最小的新语法和语义在语言中实现了类。它是 C++ 或者 Modula-3 语言中类机制的混合。就像模块一样,Python 的类并没有在用户和定义之间设立绝对的屏障,而是依赖于用户不去“强行闯入定义”的优雅。另一方面,类的大多数重要特性都被完整的保留下来:类继承机制允许多重继承,派生类可以覆盖(override)基类中的任何方法或类,可以使用相同的方法名称调用基类的方法。对象可以包含任意数量的私有数据。</p>
<p>用 C++ 术语来讲,所有的类成员(包括数据成员)都是公有( <em>public</em> )的(其它情况见下文 <a class="reference internal" href="#tut-private"><span class="std std-ref">私有变量</span></a>),所有的成员函数都是虚( <em>virtual</em> )的。用 Modula-3 的术语来讲,在成员方法中没有简便的方式引用对象的成员:方法函数在定义时需要以引用的对象做为第一个参数,调用时则会隐式引用对象。像在 Smalltalk 中一个,类也是对象。这就提供了导入和重命名语义。不像 C++ 和 Modula-3 中那样,大多数带有特殊语法的内置操作符(算法运算符、下标等)都可以针对类的需要重新定义。</p>
<p>在讨论类时,没有足够的得到共识的术语,我会偶尔从 Smalltalk 和 C++ 借用一些。我比较喜欢用 Modula-3 的用语,因为比起 C++,Python 的面向对象语法更像它,但是我想很少有读者听过这个。</p>
<div class="section" id="tut-object">
<span id="id2"></span><h2>9.1. 术语相关<a class="headerlink" href="#tut-object" title="Permalink to this headline">¶</a></h2>
<p>对象具有特性,并且多个名称(在多个作用于中)可以绑定在同一个对象上。在其它语言中被称为别名。在对 Python 的第一印象中这通常会被忽略,并且当处理不可变基础类型(数字,字符串,元组)时可以被放心的忽略。但是,在调用列表、字典这类可变对象,或者大多数程序外部类型(文件,窗体等)描述实体时,别名对 Python 代码的语义便具有(有意而为)影响。这通常有助于程序的优化,因为在某些方面别名表现的就像是指针。例如,你可以轻易的传递一个对象,因为通过继承只是传递一个指针。并且如果一个方法修改了一个作为参数传递的对象,调用者可以接收这一变化——这消除了两种不同的参数传递机制的需要,像 Pascal 语言。</p>
</div>
<div class="section" id="python">
<span id="tut-scopes"></span><h2>9.2. Python 作用域和命名空间<a class="headerlink" href="#python" title="Permalink to this headline">¶</a></h2>
<p>在介绍类之前,我首先介绍一些有关 Python 作用域的规则。类的定义非常巧妙的运用了命名空间,要完全理解接下来的知识,需要先理解作用域和命名空间的工作原理。另外,这一切的知识对于任何高级 Python 程序员都非常有用。</p>
<p>让我们从一些定义说起。</p>
<p><em>命名空间</em> 是从命名到对象的映射。当前命名空间主要是通过 Python 字典实现的,不过通常不关心具体的实现方式(除非出于性能考虑),以后也有可能会改变其实现方式。以下有一些命名空间的例子:内置命名(像 <a class="reference external" href="https://docs.python.org/3/library/functions.html#abs">abs()</a> 这样的函数,以及内置异常名)集,模块中的全局命名,函数调用中的局部命名。某种意义上讲对象的属性集也是一个命名空间。关于命名空间需要了解的一件很重要的事就是不同命名空间中的命名没有任何联系,例如两个不同的模块可能都会定义一个名为 <code class="docutils literal"><span class="pre">maximize</span></code> 的函数而不会发生混淆-用户必须以模块名为前缀来引用它们。</p>
<p>顺便提一句,我称 Python 中任何一个“.”之后的命名为 <em>属性</em> --例如,表达式 <code class="docutils literal"><span class="pre">z.real</span></code> 中的 <code class="docutils literal"><span class="pre">real</span></code> 是对象 <code class="docutils literal"><span class="pre">z</span></code> 的一个属性。严格来讲,从模块中引用命名是引用属性:表达式 <code class="docutils literal"><span class="pre">modname.funcname</span></code> 中,<code class="docutils literal"><span class="pre">modname</span></code> 是一个模块对象,<code class="docutils literal"><span class="pre">funcname</span></code> 是它的一个属性。因此,模块的属性和模块中的全局命名有直接的映射关系:它们共享同一命名空间!<a class="footnote-reference" href="#id27" id="id3">[1]</a></p>
<p>属性可以是只读过或写的。后一种情况下,可以对属性赋值。你可以这样作: <code class="docutils literal"><span class="pre">modname.the_answer</span> <span class="pre">=</span> <span class="pre">42</span></code> 。可写的属性也可以用 <a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#del">del</a> 语句删除。例如: <code class="docutils literal"><span class="pre">del</span> <span class="pre">modname.the_answer</span></code> 会从 <code class="docutils literal"><span class="pre">modname</span></code> 对象中删除 <code class="xref py py-attr docutils literal"><span class="pre">the_answer</span></code> 属性。</p>
<p>不同的命名空间在不同的时刻创建,有不同的生存期。包含内置命名的命名空间在 Python 解释器启动时创建,会一直保留,不被删除。模块的全局命名空间在模块定义被读入时创建,通常,模块命名空间也会一直保存到解释器退出。由解释器在最高层调用执行的语句,不管它是从脚本文件中读入还是来自交互式输入,都是 <a class="reference external" href="https://docs.python.org/3/library/__main__.html#module-__main__">__main__</a> 模块的一部分,所以它们也拥有自己的命名空间(内置命名也同样被包含在一个模块中,它被称作 <a class="reference external" href="https://docs.python.org/3/library/builtins.html#module-builtins">builtins</a> )。</p>
<p>当调用函数时,就会为它创建一个局部命名空间,并且在函数返回或抛出一个并没有在函数内部处理的异常时被删除。(实际上,用遗忘来形容到底发生了什么更为贴切。)当然,每个递归调用都有自己的局部命名空间。</p>
<p><em>作用域</em> 就是一个 Python 程序可以直接访问命名空间的正文区域。这里的直接访问意思是一个对名称的错误引用会尝试在命名空间内查找。尽管作用域是静态定义,在使用时他们都是动态的。每次执行时,至少有三个命名空间可以直接访问的作用域嵌套在一起:</p>
<ul>
<li><p class="first">包含局部命名的使用域在最里面,首先被搜索;其次搜索的是中层的作用域,这里包含了同级的函数;</p>
<p>最后搜索最外面的作用域,它包含内置命名。</p>
</li>
<li><p class="first">首先搜索最内层的作用域,它包含局部命名任意函数包含的作用域,是内层嵌套作用域搜索起点,包含非局部,但是也非全局的命名</p>
</li>
<li><p class="first">接下来的作用域包含当前模块的全局命名</p>
</li>
<li><p class="first">最外层的作用域(最后搜索)是包含内置命名的命名空间</p>
</li>
</ul>
<p>如果一个命名声明为全局的,那么对它的所有引用和赋值会直接搜索包含这个模块全局命名的作用域。如果要重新绑定最里层作用域之外的变量,可以使用 <a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#nonlocal">nonlocal</a> 语句;如果不声明为 nonlocal,这些变量将是只读的(对这样的变量赋值会在最里面的作用域创建一个新的局部变量,外部具有相同命名的那个变量不会改变)。</p>
<p>通常,局部作用域引用当前函数的命名。在函数之外,局部作用域与全局使用域引用同一命名空间:模块命名空间。类定义也是局部作用域中的另一个命名空间。</p>
<p>重要的是作用域决定于源程序的意义:一个定义于某模块中的函数的全局作用域是该模块的命名空间,而不是该函数的别名被定义或调用的位置,了解这一点非常重要。另一方面,命名的实际搜索过程是动态的,在运行时确定的——然而,Python 语言也在不断发展,以后有可能会成为静态的“编译”时确定,所以不要依赖动态解析!(事实上,局部变量已经是静态确定了。)</p>
<p>Python 的一个特别之处在于:如果没有使用 <a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#global">global</a> 语法,其赋值操作总是在最里层的作用域。赋值不会复制数据,只是将命名绑定到对象。删除也是如此:<code class="docutils literal"><span class="pre">del</span> <span class="pre">x</span></code> 只是从局部作用域的命名空间中删除命名 <code class="docutils literal"><span class="pre">x</span></code> 。事实上,所有引入新命名的操作都作用于局部作用域。特别是 <a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#import">import</a> 语句和函数定将模块名或函数绑定于局部作用域(可以使用 <a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#global">global</a> 语句将变量引入到全局作用域)。</p>
<p><a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#global">global</a> 语句用以指明某个特定的变量为全局作用域,并重新绑定它。<a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#nonlocal">nonlocal</a> 语句用以指明某个特定的变量为封闭作用域,并重新绑定它。</p>
<div class="section" id="tut-scopeexample">
<span id="id4"></span><h3>9.2.1. 作用域和命名空间示例<a class="headerlink" href="#tut-scopeexample" title="Permalink to this headline">¶</a></h3>
<p>以下是一个示例,演示了如何引用不同作用域和命名空间,以及 <a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#global">global</a> 和 <a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#nonlocal">nonlocal</a> 如何影响变量绑定:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">scope_test</span><span class="p">():</span>
<span class="k">def</span> <span class="nf">do_local</span><span class="p">():</span>
<span class="n">spam</span> <span class="o">=</span> <span class="s2">"local spam"</span>
<span class="k">def</span> <span class="nf">do_nonlocal</span><span class="p">():</span>
<span class="k">nonlocal</span> <span class="n">spam</span>
<span class="n">spam</span> <span class="o">=</span> <span class="s2">"nonlocal spam"</span>
<span class="k">def</span> <span class="nf">do_global</span><span class="p">():</span>
<span class="k">global</span> <span class="n">spam</span>
<span class="n">spam</span> <span class="o">=</span> <span class="s2">"global spam"</span>
<span class="n">spam</span> <span class="o">=</span> <span class="s2">"test spam"</span>
<span class="n">do_local</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"After local assignment:"</span><span class="p">,</span> <span class="n">spam</span><span class="p">)</span>
<span class="n">do_nonlocal</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"After nonlocal assignment:"</span><span class="p">,</span> <span class="n">spam</span><span class="p">)</span>
<span class="n">do_global</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"After global assignment:"</span><span class="p">,</span> <span class="n">spam</span><span class="p">)</span>
<span class="n">scope_test</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"In global scope:"</span><span class="p">,</span> <span class="n">spam</span><span class="p">)</span>
</pre></div>
</div>
<p>以上示例代码的输出为:</p>
<div class="highlight-none"><div class="highlight"><pre><span></span>After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
</pre></div>
</div>
<p>注意:<em>local</em> 赋值语句是无法改变 <em>scope_test</em> 的 <em>spam</em> 绑定。<a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#nonlocal">nonlocal</a> 赋值语句改变了 <em>scope_test</em> 的 <em>spam</em> 绑定,并且 <a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#global">global</a> 赋值语句从模块级改变了 spam 绑定。</p>
<p>你也可以看到在 <a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#global">global</a> 赋值语句之前对 <em>spam</em> 是没有预先绑定的。</p>
</div>
</div>
<div class="section" id="tut-firstclasses">
<span id="id5"></span><h2>9.3. 初识类<a class="headerlink" href="#tut-firstclasses" title="Permalink to this headline">¶</a></h2>
<p>类引入了一些新语法:三种新的对象类型和一些新的语义。</p>
<div class="section" id="tut-classdefinition">
<span id="id6"></span><h3>9.3.1. 类定义语法<a class="headerlink" href="#tut-classdefinition" title="Permalink to this headline">¶</a></h3>
<p>类定义最简单的形式如下:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">ClassName</span><span class="p">:</span>
<span class="o"><</span><span class="n">statement</span><span class="o">-</span><span class="mi">1</span><span class="o">></span>
<span class="o">.</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="o"><</span><span class="n">statement</span><span class="o">-</span><span class="n">N</span><span class="o">></span>
</pre></div>
</div>
<p>类的定义就像函数定义( <a class="reference external" href="https://docs.python.org/3/reference/compound_stmts.html#def">def</a> 语句),要先执行才能生效。(你当然可以把它放进 <a class="reference external" href="https://docs.python.org/3/reference/compound_stmts.html#if">if</a> 语句的某一分支,或者一个函数的内部。)</p>
<p>习惯上,类定义语句的内容通常是函数定义,不过其它语句也可以,有时会很有用,后面我们再回过头来讨论。类中的函数定义通常包括了一个特殊形式的参数列表,用于方法调用约定——同样我们在后面讨论这些。</p>
<p>进入类定义部分后,会创建出一个新的命名空间,作为局部作用域。因此,所有的赋值成为这个新命名空间的局部变量。特别是函数定义在此绑定了新的命名。</p>
<p>类定义完成时(正常退出),就创建了一个 <em>类对象</em>。基本上它是对类定义创建的命名空间进行了一个包装;我们在下一节进一步学习类对象的知识。原始的局部作用域(类定义引入之前生效的那个)得到恢复,类对象在这里绑定到类定义头部的类名(例子中是 <code class="xref py py-class docutils literal"><span class="pre">ClassName</span></code> )。</p>
</div>
<div class="section" id="tut-classobjects">
<span id="id7"></span><h3>9.3.2. 类对象<a class="headerlink" href="#tut-classobjects" title="Permalink to this headline">¶</a></h3>
<p>类对象支持两种操作:属性引用和实例化。</p>
<p><em>属性引用</em> 使用和 Python 中所有的属性引用一样的标准语法:<code class="docutils literal"><span class="pre">obj.name</span></code>。类对象创建后,类命名空间中所有的命名都是有效属性名。所以如果类定义是这样:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">MyClass</span><span class="p">:</span>
<span class="sd">"""A simple example class"""</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">12345</span>
<span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="s1">'hello world'</span>
</pre></div>
</div>
<p>那么 <code class="docutils literal"><span class="pre">MyClass.i</span></code> 和 <code class="docutils literal"><span class="pre">MyClass.f</span></code> 是有效的属性引用,分别返回一个整数和一个方法对象。也可以对类属性赋值,你可以通过给 <code class="docutils literal"><span class="pre">MyClass.i</span></code> 赋值来修改它。 <code class="xref py py-attr docutils literal"><span class="pre">__doc__</span></code> 也是一个有效的属性,返回类的文档字符串:<code class="docutils literal"><span class="pre">"A</span> <span class="pre">simple</span> <span class="pre">example</span> <span class="pre">class"</span></code>。</p>
<p>类的 <em>实例化</em> 使用函数符号。只要将类对象看作是一个返回新的类实例的无参数函数即可。例如(假设沿用前面的类):</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="n">x</span> <span class="o">=</span> <span class="n">MyClass</span><span class="p">()</span>
</pre></div>
</div>
<p>以上创建了一个新的类 <em>实例</em> 并将该对象赋给局部变量 <code class="docutils literal"><span class="pre">x</span></code>。</p>
<p>这个实例化操作(“调用”一个类对象)来创建一个空的对象。很多类都倾向于将对象创建为有初始状态的。因此类可能会定义一个名为 <code class="xref py py-meth docutils literal"><span class="pre">__init__()</span></code> 的特殊方法,像下面这样:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="p">[]</span>
</pre></div>
</div>
<p>类定义了 <code class="xref py py-meth docutils literal"><span class="pre">__init__()</span></code> 方法的话,类的实例化操作会自动为新创建的类实例调用 <code class="xref py py-meth docutils literal"><span class="pre">__init__()</span></code> 方法。所以在下例中,可以这样创建一个新的实例:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="n">x</span> <span class="o">=</span> <span class="n">MyClass</span><span class="p">()</span>
</pre></div>
</div>
<p>当然,出于弹性的需要,<code class="xref py py-meth docutils literal"><span class="pre">__init__()</span></code> 方法可以有参数。事实上,参数通过 <code class="xref py py-meth docutils literal"><span class="pre">__init__()</span></code> 传递到类的实例化操作上。例如,</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">Complex</span><span class="p">:</span>
<span class="gp">... </span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">realpart</span><span class="p">,</span> <span class="n">imagpart</span><span class="p">):</span>
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">r</span> <span class="o">=</span> <span class="n">realpart</span>
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">i</span> <span class="o">=</span> <span class="n">imagpart</span>
<span class="gp">...</span>
<span class="gp">>>> </span><span class="n">x</span> <span class="o">=</span> <span class="n">Complex</span><span class="p">(</span><span class="mf">3.0</span><span class="p">,</span> <span class="o">-</span><span class="mf">4.5</span><span class="p">)</span>
<span class="gp">>>> </span><span class="n">x</span><span class="o">.</span><span class="n">r</span><span class="p">,</span> <span class="n">x</span><span class="o">.</span><span class="n">i</span>
<span class="go">(3.0, -4.5)</span>
</pre></div>
</div>
</div>
<div class="section" id="tut-instanceobjects">
<span id="id8"></span><h3>9.3.3. 实例对象<a class="headerlink" href="#tut-instanceobjects" title="Permalink to this headline">¶</a></h3>
<p>现在我们可以用实例对象作什么?实例对象唯一可用的操作就是属性引用。有两种有效的属性名。</p>
<p><em>数据属性</em> 相当于 Smalltalk 中的“实例变量”或 C++ 中的“数据成员”。和局部变量一样,数据属性不需要声明,第一次使用时它们就会生成。例如,如果 <code class="docutils literal"><span class="pre">x</span></code> 是前面创建的 <code class="xref py py-class docutils literal"><span class="pre">MyClass</span></code> 实例,下面这段代码会打印出 16 而在堆栈中留下多余的东西:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="n">x</span><span class="o">.</span><span class="n">counter</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">while</span> <span class="n">x</span><span class="o">.</span><span class="n">counter</span> <span class="o"><</span> <span class="mi">10</span><span class="p">:</span>
<span class="n">x</span><span class="o">.</span><span class="n">counter</span> <span class="o">=</span> <span class="n">x</span><span class="o">.</span><span class="n">counter</span> <span class="o">*</span> <span class="mi">2</span>
<span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="o">.</span><span class="n">counter</span><span class="p">)</span>
<span class="k">del</span> <span class="n">x</span><span class="o">.</span><span class="n">counter</span>
</pre></div>
</div>
<p>另一种为实例对象所接受的引用属性是 <em>方法</em>。方法是“属于”一个对象的函数。(在 Python 中,方法不止是类实例所独有:其它类型的对象也可有方法。例如,链表对象有 append,insert,remove,sort 等等方法。然而,在后面的介绍中,除非特别说明,我们提到的方法特指类方法)</p>
<p id="index-0">实例对象的有效名称依赖于它的类。按照定义,类中所有(用户定义)的函数对象对应它的实例中的方法。所以在我们的例子中,<code class="docutils literal"><span class="pre">x.f</span></code> 是一个有效的方法引用,因为 <code class="docutils literal"><span class="pre">MyClass.f</span></code> 是一个函数。但 <code class="docutils literal"><span class="pre">x.i</span></code> 不是,因为 <code class="docutils literal"><span class="pre">MyClass.i</span></code> 不是函数。不过 <code class="docutils literal"><span class="pre">x.f</span></code> 和 <code class="docutils literal"><span class="pre">MyClass.f</span></code> 不同,它是一个 <em>方法对象</em> ,不是一个函数对象。</p>
</div>
<div class="section" id="tut-methodobjects">
<span id="id9"></span><h3>9.3.4. 方法对象<a class="headerlink" href="#tut-methodobjects" title="Permalink to this headline">¶</a></h3>
<p>通常,方法通过右绑定方式调用:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="n">x</span><span class="o">.</span><span class="n">f</span><span class="p">()</span>
</pre></div>
</div>
<p>在 <code class="xref py py-class docutils literal"><span class="pre">MyClass</span></code> 示例中,这会返回字符串 <code class="docutils literal"><span class="pre">'hello</span> <span class="pre">world'</span></code>。然而,也不是一定要直接调用方法。 <code class="docutils literal"><span class="pre">x.f</span></code> 是一个方法对象,它可以存储起来以后调用。例如:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="n">xf</span> <span class="o">=</span> <span class="n">x</span><span class="o">.</span><span class="n">f</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">xf</span><span class="p">())</span>
</pre></div>
</div>
<p>会不断的打印 <code class="docutils literal"><span class="pre">hello</span> <span class="pre">world</span></code>。</p>
<p>调用方法时发生了什么?你可能注意到调用 <code class="docutils literal"><span class="pre">x.f()</span></code> 时没有引用前面标出的变量,尽管在 <code class="xref py py-meth docutils literal"><span class="pre">f()</span></code> 的函数定义中指明了一个参数。这个参数怎么了?事实上如果函数调用中缺少参数,Python 会抛出异常--甚至这个参数实际上没什么用……</p>
<p>实际上,你可能已经猜到了答案:方法的特别之处在于实例对象作为函数的第一个参数传给了函数。在我们的例子中,调用 <code class="docutils literal"><span class="pre">x.f()</span></code> 相当于 <code class="docutils literal"><span class="pre">MyClass.f(x)</span></code> 。通常,以 <em>n</em> 个参数的列表去调用一个方法就相当于将方法的对象插入到参数列表的最前面后,以这个列表去调用相应的函数。</p>
<p>如果你还是不理解方法的工作原理,了解一下它的实现也许有帮助。引用非数据属性的实例属性时,会搜索它的类。如果这个命名确认为一个有效的函数对象类属性,就会将实例对象和函数对象封装进一个抽象对象:这就是方法对象。以一个参数列表调用方法对象时,它被重新拆封,用实例对象和原始的参数列表构造一个新的参数列表,然后函数对象调用这个新的参数列表。</p>
</div>
<div class="section" id="tut-class-and-instance-variables">
<span id="id10"></span><h3>9.3.5. 类和实例变量<a class="headerlink" href="#tut-class-and-instance-variables" title="Permalink to this headline">¶</a></h3>
<p>一般来说,实例变量用于对每一个实例都是唯一的数据,类变量用于类的所有实例共享的属性和方法:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Dog</span><span class="p">:</span>
<span class="n">kind</span> <span class="o">=</span> <span class="s1">'canine'</span> <span class="c1"># class variable shared by all instances</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span> <span class="c1"># instance variable unique to each instance</span>
<span class="o">>>></span> <span class="n">d</span> <span class="o">=</span> <span class="n">Dog</span><span class="p">(</span><span class="s1">'Fido'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">e</span> <span class="o">=</span> <span class="n">Dog</span><span class="p">(</span><span class="s1">'Buddy'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">d</span><span class="o">.</span><span class="n">kind</span> <span class="c1"># shared by all dogs</span>
<span class="s1">'canine'</span>
<span class="o">>>></span> <span class="n">e</span><span class="o">.</span><span class="n">kind</span> <span class="c1"># shared by all dogs</span>
<span class="s1">'canine'</span>
<span class="o">>>></span> <span class="n">d</span><span class="o">.</span><span class="n">name</span> <span class="c1"># unique to d</span>
<span class="s1">'Fido'</span>
<span class="o">>>></span> <span class="n">e</span><span class="o">.</span><span class="n">name</span> <span class="c1"># unique to e</span>
<span class="s1">'Buddy'</span>
</pre></div>
</div>
<p>正如在 <a class="reference internal" href="#tut-object"><span class="std std-ref">术语相关</span></a> 讨论的, <a class="reference external" href="https://docs.python.org/3/glossary.html#term-mutable">可变</a> 对象,例如列表和字典,的共享数据可能带来意外的效果。例如,下面代码中的 <em>tricks</em> 列表不应该用作类变量,因为所有的 <em>Dog</em> 实例将共享同一个列表:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Dog</span><span class="p">:</span>
<span class="n">tricks</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1"># mistaken use of a class variable</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
<span class="k">def</span> <span class="nf">add_trick</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">trick</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">tricks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">trick</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">d</span> <span class="o">=</span> <span class="n">Dog</span><span class="p">(</span><span class="s1">'Fido'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">e</span> <span class="o">=</span> <span class="n">Dog</span><span class="p">(</span><span class="s1">'Buddy'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">d</span><span class="o">.</span><span class="n">add_trick</span><span class="p">(</span><span class="s1">'roll over'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">e</span><span class="o">.</span><span class="n">add_trick</span><span class="p">(</span><span class="s1">'play dead'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">d</span><span class="o">.</span><span class="n">tricks</span> <span class="c1"># unexpectedly shared by all dogs</span>
<span class="p">[</span><span class="s1">'roll over'</span><span class="p">,</span> <span class="s1">'play dead'</span><span class="p">]</span>
</pre></div>
</div>
<p>这个类的正确设计应该使用一个实例变量:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Dog</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
<span class="bp">self</span><span class="o">.</span><span class="n">tricks</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1"># creates a new empty list for each dog</span>
<span class="k">def</span> <span class="nf">add_trick</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">trick</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">tricks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">trick</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">d</span> <span class="o">=</span> <span class="n">Dog</span><span class="p">(</span><span class="s1">'Fido'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">e</span> <span class="o">=</span> <span class="n">Dog</span><span class="p">(</span><span class="s1">'Buddy'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">d</span><span class="o">.</span><span class="n">add_trick</span><span class="p">(</span><span class="s1">'roll over'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">e</span><span class="o">.</span><span class="n">add_trick</span><span class="p">(</span><span class="s1">'play dead'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">d</span><span class="o">.</span><span class="n">tricks</span>
<span class="p">[</span><span class="s1">'roll over'</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">e</span><span class="o">.</span><span class="n">tricks</span>
<span class="p">[</span><span class="s1">'play dead'</span><span class="p">]</span>
</pre></div>
</div>
</div>
</div>
<div class="section" id="tut-remarks">
<span id="id11"></span><h2>9.4. 一些说明<a class="headerlink" href="#tut-remarks" title="Permalink to this headline">¶</a></h2>
<p>数据属性会覆盖同名的方法属性。为了避免意外的名称冲突,这在大型程序中是极难发现的 Bug,使用一些约定来减少冲突的机会是明智的。可能的约定包括:大写方法名称的首字母,使用一个唯一的小字符串(也许只是一个下划线)作为数据属性名称的前缀,或者方法使用动词而数据属性使用名词。</p>
<p>数据属性可以被方法引用,也可以由一个对象的普通用户(客户)使用。换句话说,类不能用来实现纯净的数据类型。事实上,Python 中不可能强制隐藏数据——一切基于约定(如果需要,使用 C 编写的 Python 实现可以完全隐藏实现细节并控制对象的访问。这可以用来通过 C 语言扩展 Python)。</p>
<p>客户应该谨慎的使用数据属性——客户可能通过践踏他们的数据属性而使那些由方法维护的常量变得混乱。注意:只要能避免冲突,客户可以向一个实例对象添加他们自己的数据属性,而不会影响方法的正确性——再次强调,命名约定可以避免很多麻烦。</p>
<p>从方法内部引用数据属性(或其他方法)并没有快捷方式。我觉得这实际上增加了方法的可读性:当浏览一个方法时,在局部变量和实例变量之间不会出现令人费解的情况。</p>
<p>一般,方法的第一个参数被命名为 <code class="docutils literal"><span class="pre">self</span></code>。这仅仅是一个约定:对 Python 而言,名称 <code class="docutils literal"><span class="pre">self</span></code> 绝对没有任何特殊含义。(但是请注意:如果不遵循这个约定,对其他的 Python 程序员而言你的代码可读性就会变差,而且有些 <em>类查看器</em> 程序也可能是遵循此约定编写的。)</p>
<p>类属性的任何函数对象都为那个类的实例定义了一个方法。函数定义代码不一定非得定义在类中:也可以将一个函数对象赋值给类中的一个局部变量。例如:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="c1"># Function defined outside the class</span>
<span class="k">def</span> <span class="nf">f1</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">x</span><span class="o">+</span><span class="n">y</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">C</span><span class="p">:</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">f1</span>
<span class="k">def</span> <span class="nf">g</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="s1">'hello world'</span>
<span class="n">h</span> <span class="o">=</span> <span class="n">g</span>
</pre></div>
</div>
<p>现在 <code class="docutils literal"><span class="pre">f</span></code>, <code class="docutils literal"><span class="pre">g</span></code> 和 <code class="docutils literal"><span class="pre">h</span></code> 都是类 <code class="xref py py-class docutils literal"><span class="pre">C</span></code> 的属性,引用的都是函数对象,因此它们都是 <code class="xref py py-class docutils literal"><span class="pre">C</span></code> 实例的方法-- <code class="docutils literal"><span class="pre">h</span></code> 严格等于 <code class="docutils literal"><span class="pre">g</span></code> 。要注意的是这种习惯通常只会迷惑程序的读者。</p>
<p>通过 <code class="docutils literal"><span class="pre">self</span></code> 参数的方法属性,方法可以调用其它的方法:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Bag</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">addtwice</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
</pre></div>
</div>
<p>方法可以像引用普通的函数那样引用全局命名。与方法关联的全局作用域是包含类定义的模块。(类本身永远不会做为全局作用域使用。)尽管很少有好的理由在方法 中使用全局数据,全局作用域确有很多合法的用途:其一是方法可以调用导入全局作用域的函数和方法,也可以调用定义在其中的类和函数。通常,包含此方法的类也会定义在这个全局作用域,在下一节我们会了解为何一个方法要引用自己的类。</p>
<p>每个值都是一个对象,因此每个值都有一个 类( <em>class</em> ) (也称为它的 类型( <em>type</em> ) ),它存储为 <code class="docutils literal"><span class="pre">object.__class__</span></code> 。</p>
</div>
<div class="section" id="tut-inheritance">
<span id="id12"></span><h2>9.5. 继承<a class="headerlink" href="#tut-inheritance" title="Permalink to this headline">¶</a></h2>
<p>当然,如果一种语言不支持继承就,“类”就没有什么意义。派生类的定义如下所示:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">DerivedClassName</span><span class="p">(</span><span class="n">BaseClassName</span><span class="p">):</span>
<span class="o"><</span><span class="n">statement</span><span class="o">-</span><span class="mi">1</span><span class="o">></span>
<span class="o">.</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="o"><</span><span class="n">statement</span><span class="o">-</span><span class="n">N</span><span class="o">></span>
</pre></div>
</div>
<p>命名 <code class="xref py py-class docutils literal"><span class="pre">BaseClassName</span></code> (示例中的基类名)必须与派生类定义在一个作用域内。除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">DerivedClassName</span><span class="p">(</span><span class="n">modname</span><span class="o">.</span><span class="n">BaseClassName</span><span class="p">):</span>
</pre></div>
</div>
<p>派生类定义的执行过程和基类是一样的。构造派生类对象时,就记住了基类。这在解析属性引用的时候尤其有用:如果在类中找不到请求调用的属性,就搜索基类。如果基类是由别的类派生而来,这个规则会递归的应用上去。</p>
<p>派生类的实例化没有什么特殊之处: <code class="docutils literal"><span class="pre">DerivedClassName()</span></code> (示列中的派生类)创建一个新的类实例。方法引用按如下规则解析:搜索对应的类属性,必要时沿基类链逐级搜索,如果找到了函数对象这个方法引用就是合法的。</p>
<p>派生类可能会覆盖其基类的方法。因为方法调用同一个对象中的其它方法时没有特权,基类的方法调用同一个基类的方法时,可能实际上最终调用了派生类中的覆盖方法。(对于 C++ 程序员来说,Python 中的所有方法本质上都是 <code class="docutils literal"><span class="pre">虚</span></code> 方法。)</p>
<p>派生类中的覆盖方法可能是想要扩充而不是简单的替代基类中的重名方法。有一个简单的方法可以直接调用基类方法,只要调用: <code class="docutils literal"><span class="pre">BaseClassName.methodname(self,</span> <span class="pre">arguments)</span></code>。有时这对于客户也很有用。(要注意只有 <code class="docutils literal"><span class="pre">BaseClassName</span></code> 在同一全局作用域定义或导入时才能这样用。)</p>
<p>Python 有两个用于继承的函数:</p>
<ul>
<li><p class="first">函数 <a class="reference external" href="https://docs.python.org/3/library/functions.html#isinstance">isinstance()</a> 用于检查实例类型: <code class="docutils literal"><span class="pre">isinstance(obj,</span> <span class="pre">int)</span></code> 只有在 <code class="docutils literal"><span class="pre">obj.__class__</span></code> 是 <a class="reference external" href="https://docs.python.org/3/library/functions.html#int">int</a> 或其它从 <a class="reference external" href="https://docs.python.org/3/library/functions.html#int">int</a> 继承的类型</p>
</li>
<li><p class="first">函数 <a class="reference external" href="https://docs.python.org/3/library/functions.html#issubclass">issubclass()</a> 用于检查类继承: <code class="docutils literal"><span class="pre">issubclass(bool,</span> <span class="pre">int)</span></code> 为 <code class="docutils literal"><span class="pre">True</span></code>,因为 <a class="reference external" href="https://docs.python.org/3/library/functions.html#bool">bool</a> 是 <a class="reference external" href="https://docs.python.org/3/library/functions.html#int">int</a> 的子类。</p>
<p>然而, <code class="docutils literal"><span class="pre">issubclass(float,</span> <span class="pre">int)</span></code> 为 <code class="docutils literal"><span class="pre">False</span></code>,因为 <a class="reference external" href="https://docs.python.org/3/library/functions.html#float">float</a> 不是 <a class="reference external" href="https://docs.python.org/3/library/functions.html#int">int</a> 的子类。</p>
</li>
</ul>
<div class="section" id="tut-multiple">
<span id="id13"></span><h3>9.5.1. 多继承<a class="headerlink" href="#tut-multiple" title="Permalink to this headline">¶</a></h3>
<p>Python 同样有限的支持多继承形式。多继承的类定义形如下例:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">DerivedClassName</span><span class="p">(</span><span class="n">Base1</span><span class="p">,</span> <span class="n">Base2</span><span class="p">,</span> <span class="n">Base3</span><span class="p">):</span>
<span class="o"><</span><span class="n">statement</span><span class="o">-</span><span class="mi">1</span><span class="o">></span>
<span class="o">.</span>
<span class="o">.</span>
<span class="o">.</span>
<span class="o"><</span><span class="n">statement</span><span class="o">-</span><span class="n">N</span><span class="o">></span>
</pre></div>
</div>
<p>在大多数情况下,在最简单的情况下,你能想到的搜索属性从父类继承的深度优先,左到右,而不是搜索两次在同一个类层次结构中,其中有一个重叠。因此,如果在 <code class="xref py py-class docutils literal"><span class="pre">DerivedClassName</span></code> (示例中的派生类)中没有找到某个属性,就会搜索 <code class="xref py py-class docutils literal"><span class="pre">Base1</span></code>,然后(递归的)搜索其基类,如果最终没有找到,就搜索 <code class="xref py py-class docutils literal"><span class="pre">Base2</span></code>,以此类推。</p>
<p>实际上,<a class="reference external" href="https://docs.python.org/3/library/functions.html#super">super()</a> 可以动态的改变解析顺序。这个方式可见于其它的一些多继承语言,类似 call-next-method,比单继承语言中的 super 更强大 。</p>
<p>动态调整顺序十分必要的,因为所有的多继承会有一到多个菱形关系(指有至少一个祖先类可以从子类经由多个继承路径到达)。例如,所有的 new-style 类继承自 <a class="reference external" href="https://docs.python.org/3/library/functions.html#object">object</a> ,所以任意的多继承总是会有多于一条继承路径到达 <a class="reference external" href="https://docs.python.org/3/library/functions.html#object">object</a> 。</p>
<p>为了防止重复访问基类,通过动态的线性化算法,每个类都按从左到右的顺序特别指定了顺序,每个祖先类只调用一次,这是单调的(意味着一个类被继承时不会影响它祖先的次序)。总算可以通过这种方式使得设计一个可靠并且可扩展的多继承类成为可能。进一步的内容请参见 <a class="reference external" href="http://www.python.org/download/releases/2.3/mro/">http://www.python.org/download/releases/2.3/mro/</a> 。</p>
</div>
</div>
<div class="section" id="tut-private">
<span id="id14"></span><h2>9.6. 私有变量<a class="headerlink" href="#tut-private" title="Permalink to this headline">¶</a></h2>
<p>只能从对像内部访问的“私有”实例变量,在 Python 中不存在。然而,也有一个变通的访问用于大多数 Python 代码:以一个下划线开头的命名(例如 <code class="docutils literal"><span class="pre">_spam</span></code> )会被处理为 API 的非公开部分(无论它是一个函数、方法或数据成员)。它会被视为一个实现细节,无需公开。</p>
<p>因为有一个正当的类私有成员用途(即避免子类里定义的命名与之冲突),Python 提供了对这种结构的有限支持,称为 <em class="dfn">name mangling</em> (命名编码) 。任何形如 <code class="docutils literal"><span class="pre">__spam</span></code> 的标识(前面至少两个下划线,后面至多一个),被替代为 <code class="docutils literal"><span class="pre">_classname__spam</span></code> ,去掉前导下划线的 <code class="docutils literal"><span class="pre">classname</span></code> 即当前的类名。此语法不关注标识的位置,只要求在类定义内。</p>
<p>名称重整是有助于子类重写方法,而不会打破组内的方法调用。例如:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Mapping</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">iterable</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">items_list</span> <span class="o">=</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">__update</span><span class="p">(</span><span class="n">iterable</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">iterable</span><span class="p">):</span>
<span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">iterable</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">items_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
<span class="n">__update</span> <span class="o">=</span> <span class="n">update</span> <span class="c1"># private copy of original update() method</span>
<span class="k">class</span> <span class="nc">MappingSubclass</span><span class="p">(</span><span class="n">Mapping</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">update</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">keys</span><span class="p">,</span> <span class="n">values</span><span class="p">):</span>
<span class="c1"># provides new signature for update()</span>
<span class="c1"># but does not break __init__()</span>
<span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">keys</span><span class="p">,</span> <span class="n">values</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">items_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
</pre></div>
</div>
<p>需要注意的是编码规则设计为尽可能的避免冲突,被认作为私有的变量仍然有可能被访问或修改。在特定的场合它也是有用的,比如调试的时候。</p>
<p>要注意的是代码传入 <code class="docutils literal"><span class="pre">exec()</span></code>, <code class="docutils literal"><span class="pre">eval()</span></code> 时不考虑所调用的类的类名,视其为当前类,这类似于 <code class="docutils literal"><span class="pre">global</span></code> 语句的效应,已经按字节编译的部分也有同样的限制。这也同样作用于 <code class="docutils literal"><span class="pre">getattr()</span></code>, <code class="docutils literal"><span class="pre">setattr()</span></code> 和 <code class="docutils literal"><span class="pre">delattr()</span></code>,像直接引用 <code class="docutils literal"><span class="pre">__dict__</span></code> 一样。</p>
</div>
<div class="section" id="tut-odds">
<span id="id15"></span><h2>9.7. 补充<a class="headerlink" href="#tut-odds" title="Permalink to this headline">¶</a></h2>
<p>有时类似于 Pascal 中“记录(record)”或 C 中“结构(struct)”的数据类型很有用,它将一组已命名的数据项绑定在一起。一个空的类定义可以很好的实现它:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Employee</span><span class="p">:</span>
<span class="k">pass</span>
<span class="n">john</span> <span class="o">=</span> <span class="n">Employee</span><span class="p">()</span> <span class="c1"># Create an empty employee record</span>
<span class="c1"># Fill the fields of the record</span>
<span class="n">john</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s1">'John Doe'</span>
<span class="n">john</span><span class="o">.</span><span class="n">dept</span> <span class="o">=</span> <span class="s1">'computer lab'</span>
<span class="n">john</span><span class="o">.</span><span class="n">salary</span> <span class="o">=</span> <span class="mi">1000</span>
</pre></div>
</div>
<p>某一段 Python 代码需要一个特殊的抽象数据结构的话,通常可以传入一个类,事实上这模仿了该类的方法。例如,如果你有一个用于从文件对象中格式化数据的函数,你可以定义一个带有 <code class="xref py py-meth docutils literal"><span class="pre">read()</span></code> 和 <code class="xref py py-meth docutils literal"><span class="pre">readline()</span></code> 方法的类,以此从字符串缓冲读取数据,然后将该类的对象作为参数传入前述的函数。</p>
<p>实例方法对象也有属性:<code class="docutils literal"><span class="pre">m.__self__</span></code> 是一个实例方法所属的对象,而 <code class="docutils literal"><span class="pre">m.__func__</span></code> 是这个方法对应的函数对象。</p>
</div>
<div class="section" id="tut-exceptionclasses">
<span id="id16"></span><h2>9.8. 异常也是类<a class="headerlink" href="#tut-exceptionclasses" title="Permalink to this headline">¶</a></h2>
<p>用户自定义异常也可以是类。利用这个机制可以创建可扩展的异常体系。</p>
<p>以下是两种新的,有效的(语义上的)异常抛出形式,使用 <a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#raise">raise</a> 语句:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">raise</span> <span class="n">Class</span>
<span class="k">raise</span> <span class="n">Instance</span>
</pre></div>
</div>
<p>第一种形式中,<code class="docutils literal"><span class="pre">instance</span></code> 必须是 <a class="reference external" href="https://docs.python.org/3/library/functions.html#type">type</a> 或其派生类的一个实例。第二种形式是以下形式的简写:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">raise</span> <span class="n">Class</span><span class="p">()</span>
</pre></div>
</div>
<p>发生的异常其类型如果是 <a class="reference external" href="https://docs.python.org/3/reference/compound_stmts.html#except">except</a> 子句中列出的类,或者是其派生类,那么它们就是相符的(反过来说--发生的异常其类型如果是异常子句中列出的类的基类,它们就不相符)。例如,以下代码会按顺序打印 B,C,D:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">B</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
<span class="k">pass</span>
<span class="k">class</span> <span class="nc">C</span><span class="p">(</span><span class="n">B</span><span class="p">):</span>
<span class="k">pass</span>
<span class="k">class</span> <span class="nc">D</span><span class="p">(</span><span class="n">C</span><span class="p">):</span>
<span class="k">pass</span>
<span class="k">for</span> <span class="n">cls</span> <span class="ow">in</span> <span class="p">[</span><span class="n">B</span><span class="p">,</span> <span class="n">C</span><span class="p">,</span> <span class="n">D</span><span class="p">]:</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">cls</span><span class="p">()</span>
<span class="k">except</span> <span class="n">D</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"D"</span><span class="p">)</span>
<span class="k">except</span> <span class="n">C</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"C"</span><span class="p">)</span>
<span class="k">except</span> <span class="n">B</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"B"</span><span class="p">)</span>
</pre></div>
</div>
<p>要注意的是如果异常子句的顺序颠倒过来( <code class="docutils literal"><span class="pre">execpt</span> <span class="pre">B</span></code> 在最前),它就会打印 B,B,B--第一个匹配的异常被触发。</p>
<p>打印一个异常类的错误信息时,先打印类名,然后是一个空格、一个冒号,然后是用内置函数 <a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#str">str()</a> 将类转换得到的完整字符串。</p>
</div>
<div class="section" id="tut-iterators">
<span id="id17"></span><h2>9.9. 迭代器<a class="headerlink" href="#tut-iterators" title="Permalink to this headline">¶</a></h2>
<p>现在你可能注意到大多数容器对象都可以用 <a class="reference external" href="https://docs.python.org/3/reference/compound_stmts.html#for">for</a> 遍历:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">for</span> <span class="n">element</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">element</span><span class="p">)</span>
<span class="k">for</span> <span class="n">element</span> <span class="ow">in</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="n">element</span><span class="p">)</span>
<span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="p">{</span><span class="s1">'one'</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'two'</span><span class="p">:</span><span class="mi">2</span><span class="p">}:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
<span class="k">for</span> <span class="n">char</span> <span class="ow">in</span> <span class="s2">"123"</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">open</span><span class="p">(</span><span class="s2">"myfile.txt"</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="n">line</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s1">''</span><span class="p">)</span>
</pre></div>
</div>
<p>这种形式的访问清晰、简洁、方便。迭代器的用法在 Python 中普遍而且统一。在后台, <a class="reference external" href="https://docs.python.org/3/reference/compound_stmts.html#for">for</a> 语句在容器对象中调用 <a class="reference external" href="https://docs.python.org/3/library/functions.html#iter">iter()</a> 。该函数返回一个定义了 <a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#iterator.__next__">__next__()</a> 方法的迭代器对象,它在容器中逐一访问元素。没有后续的元素时, <a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#iterator.__next__">__next__()</a> 抛出一个 <a class="reference external" href="https://docs.python.org/3/library/exceptions.html#StopIteration">StopIteration</a> 异常通知 <a class="reference external" href="https://docs.python.org/3/reference/compound_stmts.html#for">for</a> 语句循环结束。你可以是用内建的 <a class="reference external" href="https://docs.python.org/3/library/functions.html#next">next()</a> 函数调用 <a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#iterator.__next__">__next__()</a> 方法;以下是其工作原理的示例:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">s</span> <span class="o">=</span> <span class="s1">'abc'</span>
<span class="gp">>>> </span><span class="n">it</span> <span class="o">=</span> <span class="nb">iter</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="gp">>>> </span><span class="n">it</span>
<span class="go"><iterator object at 0x00A1DB50></span>
<span class="gp">>>> </span><span class="nb">next</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
<span class="go">'a'</span>
<span class="gp">>>> </span><span class="nb">next</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
<span class="go">'b'</span>
<span class="gp">>>> </span><span class="nb">next</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
<span class="go">'c'</span>
<span class="gp">>>> </span><span class="nb">next</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
<span class="gt">Traceback (most recent call last):</span>
File <span class="nb">"<stdin>"</span>, line <span class="m">1</span>, in <span class="n">?</span>
<span class="nb">next</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
<span class="gr">StopIteration</span>
</pre></div>
</div>
<p>了解了迭代器协议的后台机制,就可以很容易的给自己的类添加迭代器行为。定义一个 <a class="reference external" href="https://docs.python.org/3/reference/datamodel.html#object.__iter__">__iter__()</a> 方法,使其返回一个带有 <a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#iterator.__next__">__next__()</a> 方法的对象。如果这个类已经定义了 <a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#iterator.__next__">__next__()</a> ,那么 <a class="reference external" href="https://docs.python.org/3/reference/datamodel.html#object.__iter__">__iter__()</a> 只需要返回 <code class="docutils literal"><span class="pre">self</span></code>:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Reverse</span><span class="p">:</span>
<span class="sd">"""Iterator for looping over a sequence backwards."""</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="n">data</span>
<span class="bp">self</span><span class="o">.</span><span class="n">index</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">__iter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span>
<span class="k">def</span> <span class="nf">__next__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">index</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">StopIteration</span>
<span class="bp">self</span><span class="o">.</span><span class="n">index</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">index</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">index</span><span class="p">]</span>
</pre></div>
</div>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">rev</span> <span class="o">=</span> <span class="n">Reverse</span><span class="p">(</span><span class="s1">'spam'</span><span class="p">)</span>
<span class="gp">>>> </span><span class="nb">iter</span><span class="p">(</span><span class="n">rev</span><span class="p">)</span>
<span class="go"><__main__.Reverse object at 0x00A1DB50></span>
<span class="gp">>>> </span><span class="k">for</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">rev</span><span class="p">:</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
<span class="gp">...</span>
<span class="go">m</span>
<span class="go">a</span>
<span class="go">p</span>
<span class="go">s</span>
</pre></div>
</div>
</div>
<div class="section" id="tut-generators">
<span id="id23"></span><h2>9.10. 生成器<a class="headerlink" href="#tut-generators" title="Permalink to this headline">¶</a></h2>
<p><a class="reference external" href="https://docs.python.org/3/glossary.html#term-generator">Generator</a> 是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,需要返回数据的时候使用 <a class="reference external" href="https://docs.python.org/3/reference/simple_stmts.html#yield">yield</a> 语句。每次 <a class="reference external" href="https://docs.python.org/3/library/functions.html#next">next()</a> 被调用时,生成器回复它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)。以下示例演示了生成器可以很简单的创建出来:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">reverse</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">):</span>
<span class="k">yield</span> <span class="n">data</span><span class="p">[</span><span class="n">index</span><span class="p">]</span>
</pre></div>
</div>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="k">for</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">reverse</span><span class="p">(</span><span class="s1">'golf'</span><span class="p">):</span>
<span class="gp">... </span> <span class="nb">print</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
<span class="gp">...</span>
<span class="go">f</span>
<span class="go">l</span>
<span class="go">o</span>
<span class="go">g</span>
</pre></div>
</div>
<p>前一节中描述了基于类的迭代器,它能作的每一件事生成器也能作到。因为自动创建了 <a class="reference external" href="https://docs.python.org/3/reference/datamodel.html#object.__iter__">__iter__()</a> 和 <a class="reference external" href="https://docs.python.org/3/reference/expressions.html#generator.__next__">__next__()</a> 方法,生成器显得如此简洁。</p>
<p>另一个关键的功能在于两次执行之间,局部变量和执行状态都自动的保存下来。这使函数很容易写,而且比使用 <code class="docutils literal"><span class="pre">self.index</span></code> 和 <code class="docutils literal"><span class="pre">self.data</span></code> 之类的方式更清晰。</p>
<p>除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 <a class="reference external" href="https://docs.python.org/3/library/exceptions.html#StopIteration">StopIteration</a> 异常。综上所述,这些功能使得编写一个正规函数成为创建迭代器的最简单方法。</p>
</div>
<div class="section" id="tut-genexps">
<span id="id26"></span><h2>9.11. 生成器表达式<a class="headerlink" href="#tut-genexps" title="Permalink to this headline">¶</a></h2>
<p>有时简单的生成器可以用简洁的方式调用,就像不带中括号的链表推导式。这些表达式是为函数调用生成器而设计的。生成器表达式比完整的生成器定义更简洁,但是没有那么多变,而且通常比等价的链表推导式更容易记。</p>
<p>例如:</p>
<div class="highlight-default"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="nb">sum</span><span class="p">(</span><span class="n">i</span><span class="o">*</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span> <span class="c1"># sum of squares</span>
<span class="go">285</span>
<span class="gp">>>> </span><span class="n">xvec</span> <span class="o">=</span> <span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">30</span><span class="p">]</span>
<span class="gp">>>> </span><span class="n">yvec</span> <span class="o">=</span> <span class="p">[</span><span class="mi">7</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
<span class="gp">>>> </span><span class="nb">sum</span><span class="p">(</span><span class="n">x</span><span class="o">*</span><span class="n">y</span> <span class="k">for</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">xvec</span><span class="p">,</span> <span class="n">yvec</span><span class="p">))</span> <span class="c1"># dot product</span>
<span class="go">260</span>
<span class="gp">>>> </span><span class="kn">from</span> <span class="nn">math</span> <span class="k">import</span> <span class="n">pi</span><span class="p">,</span> <span class="n">sin</span>
<span class="gp">>>> </span><span class="n">sine_table</span> <span class="o">=</span> <span class="p">{</span><span class="n">x</span><span class="p">:</span> <span class="n">sin</span><span class="p">(</span><span class="n">x</span><span class="o">*</span><span class="n">pi</span><span class="o">/</span><span class="mi">180</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">91</span><span class="p">)}</span>
<span class="gp">>>> </span><span class="n">unique_words</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">word</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">page</span> <span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">line</span><span class="o">.</span><span class="n">split</span><span class="p">())</span>
<span class="gp">>>> </span><span class="n">valedictorian</span> <span class="o">=</span> <span class="nb">max</span><span class="p">((</span><span class="n">student</span><span class="o">.</span><span class="n">gpa</span><span class="p">,</span> <span class="n">student</span><span class="o">.</span><span class="n">name</span><span class="p">)</span> <span class="k">for</span> <span class="n">student</span> <span class="ow">in</span> <span class="n">graduates</span><span class="p">)</span>
<span class="gp">>>> </span><span class="n">data</span> <span class="o">=</span> <span class="s1">'golf'</span>
<span class="gp">>>> </span><span class="nb">list</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">))</span>
<span class="go">['f', 'l', 'o', 'g']</span>
</pre></div>
</div>
<p class="rubric">Footnotes</p>
<table class="docutils footnote" frame="void" id="id27" rules="none">
<colgroup><col class="label" /><col /></colgroup>
<tbody valign="top">
<tr><td class="label"><a class="fn-backref" href="#id3">[1]</a></td><td>有一个例外。模块对象有一个隐秘的只读对象,名为 <code class="xref py py-attr docutils literal"><span class="pre">__dict__</span></code> ,它返回用于实现模块命名空间的字典,命名 <code class="xref py py-attr docutils literal"><span class="pre">__dict__</span></code> 是一个属性而非全局命名。显然,使用它违反了命名空间实现的抽象原则,应该被严格限制于调试中。</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
<a href="stdlib.html" class="btn btn-neutral float-right" title="10. Python 标准库概览">Next <span class="fa fa-arrow-circle-right"></span></a>
<a href="errors.html" class="btn btn-neutral" title="8. 错误和异常"><span class="fa fa-arrow-circle-left"></span> Previous</a>
</div>
<hr/>
<div role="contentinfo">
<p>
© Copyright 2013, D.D.
</p>
</div>
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'./',
VERSION:'3.5.2',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'.html',
HAS_SOURCE: true
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<script type="text/javascript" src="_static/js/theme.js"></script>
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.StickyNav.enable();
});
</script>
</body>
</html>