tongsiying

阅读|运动|自律

0%

python-fishc

FISHC-note

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
掘墓人的小铲子  https://juemuren4449.com/categories
http://www.webhuang.cn/
https://www.cnblogs.com/wongbingming/p/9028851.html
https://www.bbsmax.com/R/E35pMOEydv/
---------------------------------------------------------------------------------------------------
集合:在我的世界里,你就是唯一
>>>numl={}
>>>type(numl)
<class 'dict>
>>num2={1,2,3,4,5}
>>>type(num2)
<class'set>

你们确实没有眼花,在Python3里,如果用大括号括起一堆数字但没有体现映射关系,那么Python就会认为这堆玩意儿就是个集合.
好,说回主题,集合在Python中几乎起到的所有作用就是两个字:唯一.举个例子:
>>>num={1,2,3,4,5,4,3,2,1}
>>>num
{1,2,3,4,5}
大家看到,我们根本不需要做什么,集合就会帮我们把重复的数据清理掉,这样是不是很方便呢?但要注意的是,集合是无序的,也就是你不能试图去索引集合中的某一个元素:
>>>num[2]
Traceback(most recent call last):
File"<pyshel1#81>",1ine 1,in<module>
num[2]
TypeError:'set'object does not support indexing
---------------------------------------------------------------------------------------------------
创建集合
创建集合有两种方法:一种是直接把一堆元素用大括号({})括起来;另一种是用set()。
>>>set1={"小甲鱼","小鱿鱼","小护士","小甲鱼"}
>>>set2=set(["小甲鱼","小鱿鱼","小护士","小甲鱼"])
>>>set1==set2
True
现在要求去除列表[1,2,3,4,5,5,3,1,0]中重复的元素.如果还没有学习过集合,你可能会这么写:
>>>1ist1=[1,2,3,4,5,5,3,1,0]
>>temp=list1[:]
>>list1.clear()
>>>for each in temp:
if each not in list1:
1ist1.append(each)
>>>list1
[1,2,3,4,5,0]
当你学习了集合之后,就可以这么干:
>>>list1=[1,2,3,4,5,5,3,1,0]
>>>list1=list(set(list1))
>>>list1
[0,1,2,3,4,5]

看,知识才是第一生产力!不过大家发现没有?由于set()创造了的集合内部是无序的,所以再调用list()将无序的集合转换成列表就不能保证原来的列表的顺序了(这里Python好心办坏事儿,把0放到前边了),所以如果关注列表中元素的前后顺序问题,使用set()这个函数时就要提高警惕啦!
---------------------------------------------------------------------------------------------------
访问集合
由于集合中的元素是无序的,所以并不能像序列那样用下标来进行访问,但是可以使用代把集合中的数据一个个读取出来:
>>> set1 ={1, 2, 3, 4, 5, 4, 3, 2, 1, 0}
>> for each in setl:
print(each, end ='')
0 1 2 3 4 5

当然也可以使用in和not in判断一个元素是否在集合中已经存在:
>>0 in setl
True
>> 'oo' in set1
False
>>>'xx' not in set1
True

使用add()方法可以为集合添加元素,使用remove()方法可以删除集合中已知的元素:
>set1.add(6)
>> set1
(0, 1, 2, 3, 4, 5, 6}
>> set1.remove(5)
>> set1
{0, 1, 2, 3, 4, 6}

---------------------------------------------------------------------------------------------------
不可变集合
有些时候希望集合中的数据具有稳定性,也就是说,像元组一样不能随意地增加或删除集合中的元素.那么我们可以定义不可变集合,这里使用的是frozenset()函数,没错,就是把元素给frozen(冰冻)起来:
>>>set1 = frozenset({1,2,3,4,5})
>> set1.add(6)
Traceback(most recent call last):
File"<pyshell#112>",1ine 1,in <module>
set1.add(6)
AttributeError:'frozenset'obiect has no attribute'add
---------------------------------------------------------------------------------------------------
永久存储
在你编写代码的时候,操作系统为了更快地做出啊应,把所有当前时数据都放在内存中,因为内存和CPU数据传输的速度要比在硬盘和CPU之间传输的速度快很多倍.
但内存有一个天生的不足,就是一旦断电就没戏,所以小甲鱼在这里再一次呼吁广大未来即将成为伟大程序员的读者们:请养成一个优雅的习惯,随时使用快捷键Ctrl+S保存你的数据.
由于Windows是以扩展名来指出文件是什么类型,所以相信很多习惯使用Windows的朋友很快就反应过来了,.exe是可执行文件格式,,txt是文本文件,.ppt是PowerPoint的专用格式 所有这些都称为文件.
---------------------------------------------------------------------------------------------------
打开文件
在Python 中,使用open()这个函数来打开文件并返回文件对象:open(file,mode ='r',buffering =-1,encoding = None,errors = None,newline = None,closefd =True,opener = None)
open()这个函数有很多参数,但作为初学者的我们,只需要先关注第一个和第二个参数即可.第一个参数是传入的文件名,如果只有文件名,不带路径的话,那么Python会在当前文件夹中去找到该文件并打开.

有的读者可能会问:如果我要打开的文件事实上并不存在呢?
那就要看第二个参数了,第二个参数指定文件打开模式
打开模式 执行操作
'r' 以只读方式打开文件(默认)
'w' 以写入的方式打开文件,会覆盖已存在的文件
'x' 如果文件已经存在,使用此模式打开将引发异常
'a' 以写入模式打开,如果文件存在,则在末尾追加写入
'b' 以二进制模式打开文件
't' 以文本模式打开(默认)
'+' 可读写模式(可添加到其他模式中使用)
'U' 通用换行符支持

使用open()成功打开一个文件之后,它会返回一个文件对象,拿到这个文件对象,就可以读取或修改这个文件:
>>#先将record.txt文件放到Python的根目录下(如C:\Python34)
>> f=open("record.txt")
没有消息就是好消息,说明我们的文件成功被打开了.
---------------------------------------------------------------------------------------------------
文件对象的方法
表8-2文件对象方法
文件对象的方法 执行操作
closeC) 关闭文件
read(size=-1) 从文件读取size个字符,当未给定size或给定负值的时候,读取剩余的所有字符,然后作为字符申返回
readlin 以写人模式打开,如果文件存在,则在末尾追加写入
write(str) 将字符串str写入文件
writelines(seq) 向文件写入字符串序列seq,seq应该是一个返回字符串的可迭代对象
seek(offset,from) 在文件中移动文件指针,从from(0代表文件起始位置,1代表当前位置,2代表文件末尾)偏移offset个字节
tell() 返回当前在文件中的位置
---------------------------------------------------------------------------------------------------
文件的关闭
Python拥有垃圾收集机制,会在文件对象的引用计数降至零的时候自动关闭文件,所以在Python编程里,如果忘记关闭文件并不会造成内存泄露那么危险的结果.

但并不是说就可以不要关闭文件,如果你对文件进行了写入操作,那么应该在完成写入之后关闭文件.因为Python可能会缓存你写入的数据,如果中途发生类似断电之类的事故,那些缓存的数据根本就不会写入到文件中.所以,为了安全起见,要养成使用完文件后立刻关闭的好习惯

---------------------------------------------------------------------------------------------------
文件的读取和定位
文件的读取方法很多,可以使用文件对象的read()和readline()方法,也可以直接list(f)或者直接使用迭代来读取。read()是按字节为单位读取,如果不设置参数,那么会全部读取出来,文件指针指向文件末尾。tell()方法可以告诉你当前文件指针的位置:
>>> f.read()
'小客服:小甲鱼,有个好评很好笑哈.\n小甲鱼:哦?\n小客服:"有了小甲鱼,以后妈妈再也不用担心我的学习了~"\n小甲鱼:哈哈哈,我看到啊,我还发微博了呢~\n小客服:嗯嗯,我看了你的微博r~\n小甲鱼:OK~\n小客服:那个有条回复"左手拿着小甲鱼,右手拿着打火机,哪里不会点哪里,so easy^"\n小甲鱼:TT'

>>> f.tell()
284

刚才提到的文件指针是啥?你可以认为它是一个"书签",起到定位的作用。使用seek()方法可以调整文件指针的位置。
seek(offset,from)方法有两个参数,表示从from(0代表文件起始位置,1代表当前位置,2代表文件末尾)偏移offset字节。因此将文件指针设置到文件起始位置,使用seek(0,0)即可:
>>> f.tell()
284
>>> f.seek(0,0)
0
>>> f.read(5)
'小客服:小'
>>> f.tell()
9
(注:因为1个中文字符占用2个字节的空间,所以4个中文加1个英文冒号刚好到位置9。)

readline()方法用于在文件中读取一整行,就是从文件指针的位置向后读取,直到遇到换行符(\n)结束:
>>>f.readline()
'甲鱼,有个好评很好笑哈.\n'

此前介绍过列表的强大,说什么都可以往里放,这不,也可以把整个文件的内容放到列表中:
>>> list(f)
['小甲鱼:哦?\n',小客服:"有了小甲鱼,以后妈妈再也不用担心我的学习了~"\n','小甲鱼:哈哈哈,我看到叮,我还发微博了呢~\n',,小客服:嗯嗯,我看了你的微博r~\n',,小甲鱼:OK~\n',小客服那个有条回复"左手拿着小甲鱼,右手拿着打火机,哪里不会点哪里,so easy ~-"\n','小甲鱼:TT']

对于迭代读取文本文件中的每一行,有些读者可能会这么写:
>>> f.seek(0,0)
0
>> lines = 1ist(f)
>> for each line in lines:
print(each line)

这样写并没有错,但给人的感觉就像是你拿酒精灯去烧开水,水是烧得开,不过效率不是很高.因为文件对象自身是支持迭代的,所以没必要绕圈子,直接使用for语句把内容迭代读取出来即可:
>>> f.seek(0,0)
0
>>> for each_line in f:
print(each_line)
---------------------------------------------------------------------------------------------------
文件的写入
如果需要写入文件,请确保之前的打开模式有'w'或'a',否则会出错:
>>>f = open("record.txt")
>>>f.write("这是一段待写人的数据")
Traceback(most recent call last):
File"< pyshell#135 >",line 1,in <module>
f.write("这是一段待写人的数据")
io.UnsupportedOperation:not writable
>> f.close()
>>> f = open("record.txt","w")
>> f.writel("这是一段待写人的数据")
10
>>> f.close()

然而一定要小心的是:使用,w'模式写入文件,此前的文件内容会被全部删除!
如果要在原来的内容上追加,一定要使用'a'模式打开文件哦

---------------------------------------------------------------------------------------------------
一个任务
本节要求读者朋友独立来完成一个任务-将文件(record2.txt)中的数据进行分割并按照以下规则保存起来:
(1)将小甲鱼的对话单独保存为boy-*.txt的文件(去掉"小甲鱼:").
(2)将小客服的对话单独保存为girl-*.txt的文件(去掉"小客服:").
(3)文件中总共有三段对话,分别保存为boy_1.txt,girl_1.txt,boy_2.txt,girl_2.txt,boy_3.txt,girl_3.txt共6个文件(提示:文件中不同的对话间已经使用"====================================="分割)。
大家一定要自己先动动手再参考答案:
#p8-1.py
count =1
boy = []
girl = []
f = open('record.txt')
for each_line in f:
if each_line[:6] != '== == ==':
(role, line_spoken) = each_line.split(':', 1)
if role =='小甲鱼':
boy. append(line_spoken)
if role = '小客服':
girl. append(line_spoken)
else:
file_name_boy = 'boy_' + str(count) + '.txt'
file_name_girl = 'girl_' + str(count) + '.txt'
boy_file = open(file_name_boy, 'w')
girl_file = open(file_name girl, 'w')
boy_file.writelines ( boy)
girl_file.writelines(girl)
boy =[]
girl =[]
count += 1
file_name_boy = "boy_' + str(count) + '.txt
file_name_girl = 'girl_'+ str(count)+ '.txt
boy_file =open(file_name_boy, 'w')
girl_file = open(file_name_girl, 'w')
boy_file.writelines(boy)
girl_file.writelines(girl)
boy_file.close()
girl_file.close()

事实上可以利用函数封装得更好看一些:
#p82.py
def save_file(boy,girl,count):
file_name_boy ='boy'+ str(count) + '.txt
file_name_girl ='girl'+ str(count) + '.txt'
boy_file = open(file_name_boy,'w')
girl_file = open(file_name-girl,'w')
boy_file.writelines(boy)
girl_file.writelines(girl)
boy_file.close()
girl_file.close()

def split_file(file_name):
count = 1
boy =[]
girl =[]
f= open(file_name)
for each_line in f:
if each_line[:6] != '== == ==':
(role,line_spoken)= each_line.split(':',1)
if role == '小甲鱼':
boy.append(line_spoken)
if role =='小客服':
girl.append(line_spoken)
else:
save_file(boy,girl,count)
boy =[]
girl =[]
count += 1
save_file(boy,girl,count)
f.close()
split file('record.txt')

---------------------------------------------------------------------------------------------------
文件系统:介绍一个高大上的东西
模块是什么?其实我们写的每一个源代码文件(*.py)都是一个模块。Python自身带有非常多实用的模块,在日常编程中,如果能够熟练地掌握它们,将事半功倍
比如刚开始介绍的文字小游戏,里边就用random模块的randint()函数来生成随机数.
然而要使用这个randint()函数,直接就调用可不行:
>>random.randint(0,9)
Traceback(most recent call last):
File"<pyshell#140>",line 1,in <module>
random.randint(0,9)
NameError:name'random'is not defined
正确的做法应该是先使用import语句导入模块,然后再使用:
>> import random
>> random.randint(0,9)
3
>> random.randint(0,9)
1
>> random.randint(0,9)
8
首先要介绍的是高大上的OS模块,OS就是Operating System的缩写,意思是操作系统,之所以说OS模块高大上,是因为对于文件系统的访问,Python一般是通过OS模块来实现的.我们所知道常用的操作系统就有Windows,Mac OS,Linux,UNIX等,这些操作系统底层对于文件系统的访问工作原理是不一样的,因此你可能就要针对不同的系统来考虑使用哪些文件系统模块…这样的做法是非常不友好且麻烦的,因为这意味着当你的程序运行环境一旦改变,你就要相应地去修改大量的代码来应付.

但是Python是跨平台的语言,也就是说,同样的源代码在不同的操作系统不需要修改就可以同样实现.有了OS模块,不需要关心什么操作系统下使用什么模块,OS模块会帮你选择正确的模块并调用。


表8-3 Os模块中关于文件/目录常用的函数使用方法
函数名 使用方法
getcwd() 返回当前工作目录
chdir(path) 改变工作目录
listdir(path='.') 列举指定目录中的文件名(.'表示当前目录,'.'表示上一级目录)
mkdir(path) 创建单层目录,如该目录已存在抛出异常
makedirs(path) 递归创建多层目录,如果该目录已存在则抛出异常,注意:'E:\allb和'E:\allc'并不会冲突
remove(path) 删除文件
rmdir(path) 删除单层目录,如果该目录非空则抛出异常
removedirs(path) 递归删除目录,从子目录到父目录逐层尝试删除,遇到目录非空则抛出异常
rename(old,new) 将文件old重命名为new
system(command) 运行系统的shell命令
以下是支持路径操作中常用到的一些定义,支持所有平台
os.curdir 指代当前目录('.')
os.pardir 指代上一级目录(..')
os.sep 输出操作系统特定的路径分隔符(在Windows下为',Linux下为'/1)
os.linesep 当前平台使用的行终止符(在Windows下为"\rn',Linux下为"\n')
os.name 指代当前使用的操作系统(包括,posix','nt','mac','os2','ce','java')
---------------------------------------------------------------------------------------------------
getcwd()
在有些情况下我们需要获得应用程序当前的工作目录(比如要保存临时文件),那么可以使用getcwd()函数获得:
>>>import os
>>>os.getcwd()
'C:\\Fython34'
---------------------------------------------------------------------------------------------------
chdir(path)
用chdir()函数可以改变当前工作目录,比如可以切换到E盘:
>>>os.chdir("E:\\")
>>> os.getcwd()
"E:\\"
---------------------------------------------------------------------------------------------------
listdir(path='.')
有时候你可能需要知道当前目录下有哪些文件和子目录,那么listdir()函数可以帮你列举出来。path参数用于指定列举的目录,默认值是'.',代表根目录,也可以使用'..'代表上一层目录:
>>> os.listdir()
['$ RECYCLE.BIN','Arduino','Systen Volume Information','工作室,'工具箱,'鱼c光盘','鱼c工作室编程教学']
>>> os.listdir("C:\\")
['$ 360Section','s Recycle.Bin',"360SANDBOX','Boot','bootmgr','BOOTNXT,'DRHyperbootSync
'Documents and Settings','hiberfil.sys','Intel','iSee','mfg','MSOCache','OneDriveT'emp'pagefile.sys','PerfLogs','Program Files','Program Files(x86)','ProgramData'.'Python27',"Python34','Recovery','Recovery.txt','swapfile.sys','System Volume Information','Userst',"Windows']


2020/3/10
---------------------------------------------------------------------------------------------------
4.mkdir(path)
mkdir()函数用于创建文件夹,如果该文件夹存在,则抛出FileExistsError异常:
>> os.mkdir("test")
>>> os.listdir()
['$ RECYCLE.BIN','Arduino','System Volume Information','test','工作室','工具箱','鱼c光盘','鱼C工作室编程教学']
>> os.mkdir("test")
Traceback(most recent call last):
File"<pyshell#156>",line 1,in <module>
os.mkdir("test")
FileExistsError:[WinError 183]当文件已存在时,无法创建该文件.:'test'

---------------------------------------------------------------------------------------------------
5.makedirs(path)
makedirs()函数可以用于创建多层目录:
>> os.makedirs(r".\a\b\c")
---------------------------------------------------------------------------------------------------
6.remove(path)、rmdir(path)和removedirs(path)
remove()函数用于删除指定的文件,注意是删除文件,不是删除目录.如果要删除目录,则用rmdir()函数;如果要删除多层目录,则用removedirs()函数.
>> os.listdir()
['a',"b','test.txt']
>>>#当前工作目录结构为a\b\c,b\,test.txt
>> os.remove("test.txt")
>> os.rmdir("b")
>> os.removedirs(r"a\b\c")
>>> os.listdir()
[]

---------------------------------------------------------------------------------------------------
7.rename(old,new)
rename()函数用于重命名文件或文件夹:
>>> os.listdir()
['a','a.txt']
>>> os.rename("a","b")
>> os.rename("a.txt","b.txt")
> os.listdir()
['b','b.txt']
---------------------------------------------------------------------------------------------------
8.system(command)
几乎每个操作系统都会提供一些小工具,system()函数用于使用这些小工具:
>> os.systen("calc")#calc是Windows系统自带的计算器回车后即弹出计算器
---------------------------------------------------------------------------------------------------
9.walk(top)
最后是walk()函数,这个函数在有些时候确实非常有用,可以省去你很多麻烦.该函娄的作用是遍历top参数指定路径下的所有子目录,并将结果返回一个三元组(路径,[包含目录],[包含文件]),来看下面的例子:
>> for i in os.walk("test"):
print(i)
("test',['a','b','c',[])
('test\\a',[],['a.txt'])
('test\\b',['bl','b2'],['b.txt'])
("test\\b\\b1',[],['bl.txt'])
('test\\b\\b2',[],['b2.txt'])
('test\\c',['cl'], [])
('test\\c\\cl',['c11'], [])
('test\\c\\cl\\cll',[],['c11.txt'])

另外path模块还提供了一些很实用的定义,分别是:os.curdir表示当前目录;os.pardir表示上一级目录('..');os.sep表示路径的分隔符,比如Windows系统下为'\\',Linux下为'/';os.linesep表示当前平台使用的行终止符(在Windows下为'\r\n',Linux下为'\n');os.name表示当前使用的操作系统.
另一个强大的模块是os.path,它可以完成一些针对路径名的操作.表8-4列举了os.path中常用到的函数使用方法.

表8-4 os.path模块中关于路径常用的函数使用方法
函数名 使用方法
basename(path) 去掉目录路径,单独返回文件名
dirname(path) 去掉文件名,单独返回目录路径
join(path[,path2[,...) 将path1和path2各部分组合成一个路径名
split(path) 分割文件名与路径,返回(f-path,f-name)元组.如果完全使用目录,它也会将最后一个目录作为文件名分离,且不会判断文件或者目录是否存在
plitext(path) 分离文件名与扩展名,返回(f-name,f-extension)元组
getsize(file) 返回指定文件的尺寸,单位是字节
getatime(file) 返回指定文件最近的访问时间(浮点型秒数,可用time模块的gmtime()或localtime()函数换算)
getctime(file) 返回指定文件的创建时间(浮点型秒数,可用time模块的gmtime()或localtime()函数换算)
getmtime(file) 返回指定文件最新的修改时间(浮点型秒数,可用time模块的gmtime()或localtime()函数换算)
以下为函数返回True或False
exists(path) 判断指定路径(目录或文件)是否存在
isabs(path) 判断指定路径是否为绝对路径
isdir(path) 判断指定路径是否存在且是一个目录
isfile(path) 判断指定路径是否存在且是一个文件
islink(path) 判断指定路径是否存在且是一个符号链接
smount(path) 判断指定路径是否存在且是一个挂载点
samefile(pathl,path2) 判断path1和path2两个路径是否指向同一个文件
---------------------------------------------------------------------------------------------------
10.basename(path)和dirname(path)
basename()和dirname()函数分别用于获得文件名和路径名:
>> os.path.dirname(r"a\b\test.txt")
'a\\b'
>> os.path.basename(r"a\b\text.txt")
'text.txt'
---------------------------------------------------------------------------------------------------
11.join(path1[,path2[,...]])
join()函数跟BIF的那个join()函数不同,os.path.join()是用于将路径名和文件名组合成一个完整的路径:
>>>os.path.join(r"C:\Python34\Test","FishC.txt")
"C:\\Python34\\Test\\FishC.txt'
---------------------------------------------------------------------------------------------------
12.split(path)和splitext(path)
split()和splitext()函数都用于分割路径,split()函数分割路径和文件名(如果完全使用目录,它也会将最后一个目录作为文件名分离,且不会判断文件或者目录是否存在);splitext()函数则是用于分割文件名和扩展名:
>>>os.path.split(r"a\b\test.txt")
('a\\b','test.txt')
>>>os.path.splitext(r"a\b\test.txt")
('a\\b\test','.txt')
---------------------------------------------------------------------------------------------------
13.getsize(file)
getsize()函数用于获取文件的尺寸,返回值是以字节为单位:
>>> os.chdir(r"C:\Python34")
>> os.path.getsize("python.exe")
40960
---------------------------------------------------------------------------------------------------
14.getatime(file)、getctime(file)和getmtime(file)
getatime()、getctime()和getmtime()分别用于获得文件的最近访问时间、创建时间和修改时间.不过返回值是浮点型秒数,可用time模块的gmtime()或localtime()函数换算:
>>>import time
>>>temp= time.localtime(os.path.getatime("python.exe"))
>>> print("python.exe被访问的时间是:",time.strftime("%d %b %Y %H:%M:%S",temp))
python.exe被访问的时间是:27 May 2015 21:16:59
>> temp= time.localtime(os.path.getctime("python.exe"))
>>> print("python.exe被创建的时间是:",time.strftime("%d %b %Y %H: %M :%S",temp))
python.exe被创建的时间是:24 Feb 2015 22:44:44
>> temp = time.localtime(os,path.getmtime("python.exe"))
>>> print("python.exe被修改的时间是:",time.strftime("%d %b %Y %H: %M :%S",temp))

python.exe被修改的时间是:24 Feb 2015 22:44:44
还有一些函数返回布尔类型的值,具体的解释见表8-4,这里就不一一举例了

---------------------------------------------------------------------------------------------------
pickle:腌制一缸美味的泡菜
从一个文件里读取字符串非常简单,但如果想要读取出数值,那就需要多费点儿周折.因为无论是read()方法,还是readline()方法,都是返回一个字符串,如果希望从字符串里边提取出数值的话,可以使用int()函数或float()函数把类似123或'3.14'这类字符串强制转换为具体的数值.

此前一直在讲保存文本,然而当要保存的数据像列表、字典甚至是类的实例这些更复杂的数据类型时,普通的文件操作就会变得不知所措.也许你会把这些都转换为字符串,再写入到一个文本文件中保存起来,但是很快你就会发现要把这个过程反过来,从文本文件恢复数据对象,就变得异常麻烦了.

所幸的是,Python提供了一个标准模块,使用这个模块,就可以非常容易地将列表、字典这类复杂数据类型存储为文件了.这个模块就是本节要介绍的pickle模块.
pickle就是泡菜,腌菜的意思,相信很多女读者都对韩国泡菜尤其情有独钟.至于Python的作者为何把这么一个高大上模块命名为泡菜,我想应该是跟韩剧脱不了干系.
好,说回这个泡菜.用官方文档中的话说,这是一个令人惊叹(amazing)的模块,它几乎可以把所有Python的对象都转化为二进制的形式存放,这个过程称为pickling,那么从二进制形式转换回对象的过程称为unpickling.
说了这么多,还是来点干货吧:
---------------------------------------------------------------------------------------------------
说了这么,还是来点干货吧:
#pe83.py
import pickle
my_list=[123,3.14,'小甲鱼,['another list']]
pickle_file = open('E:\\my_list.pkl','wb')
pickle.dump(my_list,pickle_file)
pickle_file.close()
分析一下:这里希望把这个列表永久保存起来(保存成文件),打开的文件一定要以二进制的形式打开,后缀名倒是可以随意,不过既然是使用pickle保存,为了今后容易记忆,建议还是使用.pkl或.pickle,使用dump方法来保存数据,完成后记得保存,跟操作普通文本文件一样.
程序执行之后E盘会出现一个my_list.pkl的文件,用记事本打开之后显示乱码(因为它保存的是二进制形式),如图8-5所示.
那么在使用的时候只需用二进制模式先把文件打开,然后用load把数据加载进来:
#p84. py
import pickle
pickle_file = open("E:\\my_list.pkl","rb")
my_list = pickle.load(pickle_file)
print(my_list)

程序执行后又取回我们的列表啦:
>>>
[123,3.14,'小甲鱼',['another list']]
>>>
利用pickle模块,不仅可以保存列表,事实上pickle可以保存任何你能想象得到的东西.



---------------------------------------------------------------------------------------------------
异常处理
你不可能总是对的:程序出现逻辑错误或者用户输入不合法都会引发异常,但这些异常并不是致命的,不会导致程序崩溃死掉.可以利用Python提供的异常处理机制,在异常出现的时候及时捕获,并从内部自我消化掉.
那么什么是异常呢?举个例子:
#p9_1.py
file-name = input('请输入要打开的文件名:')
f = open(file_name,'r')
print('文件的内容是:')
for each_line in f:
print(each_line)

这里当然假设用户的输入是正确的,但只要用户输入一个不存在的文件名,那么上面的代码就不堪一击:
>>>
请输入要打开的文件名:我为什么是一个文档.txt
Traceback(most recent call last):
File"E:\p9_1.py",line 2,in <module>
f = open(file_name,'r')
FileNotFoundError:[Errno 2]No such file or directory:'我为什么是一个文档.txt'
上面的例子就抛出了一个FileNotFoundError异常

---------------------------------------------------------------------------------------------------
1.AssertionError:断言语句(assert)失败
大家还记得断言语句吧?在关于分支和循环的章节里讲过。当assert这个关键字后边的条件为假的时候,程序将停止并抛出AssertionError异常。assert语句一般是在测试程序的时候用于在代码中置入检查点:
>>my_list =["小甲鱼"]
>> assert len(my_list)>0
>> my_list.pop()
'小甲鱼'
>> assert len(my_list) >0
Traceback(most recent call last):
File"< pyshell#3>",line 1,in <module>
assert len(my_list)>0
AssertionError
---------------------------------------------------------------------------------------------------
2.AttributeError:尝试访问未知的对象属性
当试图访问的对象属性不存在时抛出的异常:
>>> my_list =[]
>>> my_list.fishc
Traceback(most recent call last):
File"< pyshell#5>",line 1,in <module >
my list.fishc
AttributeError:'list'object has no attribute'fishc'
---------------------------------------------------------------------------------------------------
3.IndexError:索引超出序列的范围在使用序列的时候就常常会遇到IndexError异常,原因是索引超出序列范围的内容:
>>> my list=[1,2,3]
>>> my list[3]
Traceback(most recent call last):
File"<pyshell#7>",line 1,in <module>
my list[3]
IndexError:list index out of range
---------------------------------------------------------------------------------------------------
4.KeyError:字典中查找一个不存在的关键字
当试图在字典中查找一个不存在的关键字时就会引发KeyError异常,因此建议使用dict.get()方法:
>> my_dict ={"one":1,"two":2,"three":3}
>>> my_dict["one"]
1
>>ny dict["four"]
Traceback(most recent call last):
File"<pyshell#10>",line 1,in <module>
my_dict["four"]
KeyError:'four
---------------------------------------------------------------------------------------------------
5.NameError:尝试访问一个不存在的变量
当尝试访问一个不存在的变量时,Python会抛出NameError异常:
>> fishc
Traceback (most recent call last):
File "< pyshell#11>", line 1, in <module>
fishc
NameError: name 'fishc' is not defined
---------------------------------------------------------------------------------------------------
6.OSError:操作系统产生的异常
OSError顾名思义就是操作系统产生的异常,像打开一个不存在的文件会引发FileNotFoundError,而这个FileNotFoundError就是OSError的子类.例子上面已经演示过,这里就不再重复.
---------------------------------------------------------------------------------------------------
7.SyntaxError:Python的语法错误
如果遇到SyntaxError是Python的语法错误,这时Python的代码并不能继续执行,你应该先找到并改正错误:
>>> print "I love fishc.com"
SyntaxRrror:Missing parentheses in call to'print'
---------------------------------------------------------------------------------------------------
8.TypeError:不同类型间的无效操作
有些类型不同是不能相互进行计算的,否则会抛出TypeError异常:
>>>>1 + "1"
Traceback(most recent call last):
File"<pyshell#15>",line 1,in <module>
1 + "1"
TypeError:unsupported operand type(s) for +:'int' and 'str'

---------------------------------------------------------------------------------------------------
9.ZeroDivisionError:除数为零
地球人都知道除数不能为零,所以除以零就会引发ZeroDivisionError异常:
>>>5 / 0
Traceback(most recent call last):
File"<pyshell#16>",line 1,in <module>
5/0
ZeroDivisionError:division by zero
好了,知道程序抛出异常就说明这个程序有问题,但问题并不致命,所以可以通过捕获这些异常,并纠正这些错误就行.那应该如何捕获和处理异常呢?
异常捕获可以使用try语句来实现,任何出现在try语句范围内的异常都会被及时捕获到.try语句有两种实现形式:一种是try-except,另一种是try-finally.

---------------------------------------------------------------------------------------------------
try-except语句
try-except 语句格式如下:
try:
检测范围
except Exception[as reason]:
出现异常(Exception)后的处理代码

try-except语句用于检测和处理异常,举个例子来说明这一切是如何工作的:
#p92.py
f = open('我为什么是一个文档.txt')
print(f.read())
f.close()

以上代码在"我为什么是一个文档.txt"这个文档不存在的时候,Python就会报错说文件不存在:
>>>
Traceback(most recent call last):
File"E:\p92.py",line 1,in <module>
f= open('我为什么是一个文档.txt')
FileNotFoundError:[Errno 2]No such file or directory:'我为什么是一个文档.txt'
>>>
显然这样的用户体验不好,因此可以这么修改:
#p93.py
try:
f= open('我为什么是一个文档.txt')
print(f.read())
f.close()
except OSError:
print('文件打开的过程中出错啦TT')

上面的例子由于使用了大家习惯的语言来表述错误信息,用户体验当然会好很多:
>>>
文件打开的过程中出错啦TT
>>>

但是从程序员的角度来看,导致OSError异常的原因有很多(例如FileExistsError、FileNotFoundError,PermissionError等等),所以可能会更在意错误的具体内容,这里可以使用as把具体的错误信息给打印出来:
except OSError as reason:
print('文件出错啦T-TYn错误原因是:'+ str(reason))
---------------------------------------------------------------------------------------------------
针对不同异常设置多个except
一个try语句还可以和多个except语句搭配,分别对感兴趣的异常进行检测处理:
#p94.py
try:
sum =1 + '1'
f-= open('我是一个不存在的文档.txt")
print(f.read())
f.close()
except OSError as reason:
print('文件出错啦T_T\n错误原因是:'+ str(reason))
except TypeError as reason:
print('类型出错啦T_T\n错误原因是:'+ str(reason))
---------------------------------------------------------------------------------------------------
对多个异常统一处理
except后边还可以跟多个异常,然后对这些异常进行统一的处理:
#p9_5.py
try:
int('abe')
sum =1 +"11
f = open('我是一个不存在的文档.txt')
print(f.read())
f.close()
except(OSError,TypeError):
print('出错啦T_T\n错误原因是:'+ str(reason))
---------------------------------------------------------------------------------------------------
9.2.3捕获所有异常
如果你无法确定要对哪一类异常进行处理,只是希望在try语句块里一旦出现任何异常,可以给用户一个"看得懂"的提醒,那么可以这么做
...
except:
print('出错啦~')
...
不过通常不建议你这么做,因为它会隐藏所有程序员未想到并且未做好处理准备的错误,例如当用户输入Ctrl+C试图终止程序,却被解释为KeyboardInterrupt异常.另外要注意的是,try语句检测范围内一旦出现异常,剩下的语句将不会被执行。
---------------------------------------------------------------------------------------------------
try-finally语句
如果"我是一个不存在的文档"确实存在,open()函数正常返回文件对象,但异常却发生在成功打开文件后的sum=1 +'1',语句上.此时Python将直接跳到except语句,也就是说,文件打开了,但并没有执行关闭文件的命令:
#p96.py
try:
f=open('我是一个不存在的文档.txt')
print(f.read())
sum =1 + '1'
f.close()
except:
print('出错啦')
为了实现像这种"就算出现异常,但也不得不执行的收尾工作(比如在程序崩溃前保存用户文档)",引人了finally来扩展try:
#p9_7.py
try:
f=open('我是一个不存在的文档.txt')
print(f.read())
sum =1 + '1'
except:
print('出错啦')
finally:
f.close()
如果try语句块中没有出现任何运行时错误,会跳过except语句块执行finally语句块的内容.如果出现异常,则会先执行except语句块的内容再执行finally语句块的内容.总之finally语句块中的内容就是确保无论如何都将被执行的内容.
---------------------------------------------------------------------------------------------------
raise语句
有读者可能会问,我的代码能不能自己抛出一个异常呢?答案是可以的,你可以使月raise语句抛出一个异常:
>> raise ZeroDivisionError
Traceback(most recent call last):
File"<pyshell#0>",line 1,in <module>
raise ZeroDivisionError
ZeroDivisionError
抛出的异常还可以带参数,表示异常的解释:
>> raise ZeroDivisionError("除数不能为零!")
Traceback(most recent call last):
File"<pyshell#2>",line 1,in <module>
raise ZeroDivisionError("除数不能为零!")
ZeroDivisionError:除数不能为零!
---------------------------------------------------------------------------------------------------
丰富的else语句
在Python里,else语句的功能更加丰富。
在Python中,else语句不仅能跟if语句搭,构成"要么怎样,要么不怎样"的句式;
它还能跟循环语句(for语句或者while语句),构成"干完了能怎样,干不完就别想怎样"的句式;
其实else语句还能够跟刚刚讲的异常处理进行搭配,构成"没有问题?那就干吧"的句式,下边逐个给大家解释。
---------------------------------------------------------------------------------------------------
1.要么怎样,要么不怎样
典型的if-else搭配:
if条件:
条件为真执行
else:
条件为假执行
---------------------------------------------------------------------------------------------------
2.干完了能怎样,干不完就别想怎样
else可以跟for和while循环语句配合使用,但else语句块只在循环完成后执行,也就是说,如果循环中间使用break语句跳出循环,那么else里边的内容就不会被执行了.举个例子:
#p9_8.py
def showMaxFactor (num):
count =num//2
while count > 1:
if num count == 0:
print('%d最大的约数是%d' % (num, count))
break
count-=1
else:
print('%d是素数!' % num)
num = int(input('请输入一个数:'))
showlaxFactor (num)
这个小程序主要是求用户输入的数的最大约数,如果是素数的话就顺便提醒"这是一个素数".注意要使用地板除法(count=num//2)哦,否则结果会出错.使用暴力的方法一个个尝试(num%count ==0),如果符合条件则打印出最大的约数,并break,同时不会执行else语句块的内容了.但如果一直没有遇到合适的条件,则会执行else语句块内容.
for语句的用法跟while一样,这里就不重复举例了.
-------------------------------------------------------------------------------------
3.没有问题?那就干吧
else语句还能跟刚刚学的异常处理进行搭配,实现跟与循环语句搭配差不多:只要tryi句块里没有出现任何异常,那么就会执行else语句块里的内容啦。举个例子:
#p99.py
try:
int ('abc')
except ValueError as reason:
print('出错啦:' + str(reason))
else:
print('没有任何异常!')
-------------------------------------------------------------------------------------
简洁的with语句
有读者可能觉着打开文件又要关闭文件,还要关注异常处理有点烦人,所以Python提了一个with语句,利用这个语句抽象出文件操作中频繁使用的try/except/finally相关的细节。对文件操作使用with语句,将大大减少代码量,而且你再也不用担心出现文件打开了记关闭的问题了(with会自动帮你关闭文件)。举个例子:
#p9_10. рy
try:
f = open( 'data.txt', 'w')
for each_line in f:
print(each_line)
except OSError as reason:
print('出错啦:' + str(reason))
finally:
f.close()

使用with语句,可以改成这样:
#p9-11.py
try:
with open('data.txt','w') as f:
for each_line in f:
print(each_line)
except OSError as reason:
print('出错啦:' +str(reason))

有了with语句,就再也不用担心忘记关闭文件了。
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
-------------------------------------------------------------------------------------
图形用户界面入门
本章给大家介绍图形用户界面编程,也就是平时常说的GUI(Graphical User Interface.读作[gu:i])编程,那些带有按钮、文本、输入框的窗口的编程,相信大家都不会陌生。
目前有很多Python的GUI工具包可供选择, Python有一个非常简单的GUI工具包:EasyGui. EasyGui跟它的名字一样简单,一旦你的模块导入EasyGui,GUI操作就是一个简单地调用EasyGui函数的几个参数的问题了。
EasyGui官网: http://easygui. sourceforge.net.
本书配套资源: easygui-0.96.zip。
使用标准方法安装:
--解压easygui-0.96.zip
--使用命令窗口切换到easygui-docs-0.96的目录下
--在Windows下执行C:\Python34\python.exe setup.py install
--在Linux或Mac下执行sudo /usr/bin/python34 setup.py install
Windows下的安装界面如图10-1所示。
图10-1 EasyGui的安装
Microsoft uindows [版本6.3.96001
(c) 2013 Microsoft Corporation,保留所有权利.
C: \UsersV佳字cd C:\Python34\easygui-0.96
C:\Python34\easygui-0.96c \Python34\python. exe setup.py install
running install
running build
running build_py
running insta11_1ib
running install_egg_info
Removing C: \Python34\Lib\site-packages \easygui-0.96-py3.4.egg-info
lriting C: \Python34\Lib\site-packages\easygui-0.96-py3.4.egg-info
C: \Python34\easygui-0.96>
希望深入学习Python的读者,可以在本书配套资源中的easygui-0.96.zip压缩包中找至EasyGui各个函数的实现源代码(下载地址http://bs.fishc.com/thread-46069-1-1.html).
附件:easygui-0.96.zip
-------------------------------------------------------------------------------------
导入EasyGui
为了使用EasyGui这个模块,你应该先导入它.最简单的导人语句是import easygui。
如果使用这种形式导入的话,那么在使用EasyGui的函数的时候,必须在函数的前面加上前缀easygui:
>> import easygui
>> easygui.msgbox("嗨,大家好~")
回车后即弹出消息框,如图10-2所示.
另一种选择是导入整个EasyGui包:from easygui import*,这样使得我们更容易调用EasyGui的函数,可以直接这样编写代码:
>> from easygui import*
>> msgbox("嗨,小美女~")
回车后即弹出消息框,如图10-3所示.
第三种方案是使用类似下边的import语句(建议使用):import easygui as g,这样可以让你保持EasyGui的命名空间,同时减少输入字符的数量:
>> import easygui as g
>>> g.msgbox("嗨,鱼C~")
回车后即弹出消息框,如图10-4所示.
-------------------------------------------------------------------------------------
使用EasyGui
举一个简单的例子:
#p10_1. py
import easygui as g
import sys
while l:
g.msgbox("嗨,欢迎进入第一个界面小游戏^_^“)
msg ="请问你希望在鱼c工作室学习到什么知识呢?"
title ="小游戏互动"
choices = ["谈恋爱", "编程", "OOXX", "琴棋书画"]
choice = g.choicebox(msg, title, choices)
#note that we convert choice to string, in case
#the user cancelled the choice, and we got None.
g.msgbox("你的选择是:" + str(choice), "结果")
msg = "你希望重新开始小游戏吗?"
title= "请选择"
if g.ccbox(msg, title): # show a Continue/Cancel dialog
pass # user chose Continue
else:
sys.exit(0) # user chose Cancel
-------------------------------------------------------------------------------------
修改默认设置
默认情况下显示的对话框非常大,而且字体也相对难看.这里可以手动调整EsayGui的参数修改.
修改位置为C:\Python34\Lib\site-packages\easygui.py
更改对话框尺寸;找到def choicebox,下边的rootwidth=int((screen-width*0.8))和rootheight=int((screen-height*0.5))分别改为root-width=int((screen width*0.4))和rootheight =int((screen-height*0.25))。
更改字体:找到PROPORTIONAL_FONT_FAMILY =("MS","Sans","Serif")改为PROPORTIONAL_FONT _FAMILY=("微软雅黑")
EasyGui提供了非常多的组件供我们实现一个完整的界面程序,刚才给大家演示的就是msgbox、choicebox和ccbox的用法.关于更多的组件使用,大家可以参考小甲鱼翻译改编的
《EasyGui学习文档》:http://bbs.fishc.com/thread-46069-1-1.html
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
类和对象
-------------------------------------------------------------------------------------
给大家介绍对象
大家之前已经听说过封装的概念,把乱七八糟的数据扔进列表里边,这是一种封装,是数据层面的封装;把常用的代码段打包成一个函数,这也是一种封装,是语句层面的封装;本章学习的对象,也是一种封装的思想,不过这种思想显然要更先进一步:对象的来源是模拟真实世界,把数据和代码都封装在了一起.
打个比方,乌龟就是真实世界的一个对象,那么通常应该如何来描述这个对象呢?是不是把它分为两部分来说?
(1)可以从静态的特征来描述,例如,绿色的、有四条腿,10kg重,有外壳,还有个大嘴巴,这是静态一方面的描述.
(2)还可以从动态的行为来描述,例如说它会爬,你如果追它,它就会跑,然后你把它逼急了,它就会咬人,被它咬到了,据说要打雷才会松开嘴巴…它的嘴巴的重要作用不是用来咬人,是用来吃东西的,然后它还会睡觉.这些都是从行为方面进行描述的.
-------------------------------------------------------------------------------------
对象=属性+方法
Python中的对象也是如此,一个对象的特征称为"属性",一个对象的行为称为"方法".
如果把"乌龟"写成代码,将会是下边这样:
#p11_1.py
class Turtle:
#Python中的类名约定以大写字母开头
#特征的描述称为属性,在代码层面来看其实就是变量
color ='green'
weight = 10
legs =4
shell = True
mouth ='大嘴

#方法实际就是函数,通过调用这些函数来完成某些工作
def climb(self):
print("我正在很努力地向前爬.")
def run(self):
print("我正在飞快地向前跑.")
def bite(self):
print("咬死你咬死你!!")
def eat(self):
print("有得吃,真满足~-~")
def sleep( self):
print("困了,睡了,晚安, Zzzz")
以上代码定义了对象的特征(属性)和行为(方法),但还不是一个完整的对象,将定义的这些称为类(Class),需要使用类来创建一个真正的对象,这个对象就叫作这个类的一个实例(Instance),也叫实例对象(Instance Objects).
有些读者可能还不大理解,你可以这么想:这就好比工厂的流水线要生产一系列玩具,是不是要先做出这个玩具的模具,然后根据这个模具再进行批量生产,才得到真正的玩具?再举个例子:盖房子,是不是先要有个图纸,但光有个图纸你能不能住进去?显然不能,图纸只能告诉你这个房子长什么样,但图纸并不是真正的房子.要根据图纸用钢筋水泥建造出来的房子才能住人,另外根据一张图纸就能盖出很多的房子。好,说了这么多,那真正的实例对象怎么创建?创建一个对象,也叫类的实例化,其实非常简单:
>>>#先运行p11_1.py
>>> tt = Turtle()
>>>
注意,类名后边跟着的小括号,这跟调用函数是一样的,所以在Python中,类名约定用大写字母开头,函数用小写字母开头,这样更容易区分。另外赋值操作并不是必需的,但如果没有把创建好的实例对象赋值给一个变量,那这个对象就没办法使用,因为没有任何引用指向这个实例,最终会被Python的垃圾收集机制自动回收。
那如果要调用对象里的方法,使用点操作符(.)即可,其实我们已经用了何止千百遍:
>>> tt.climb()
我正在很努力地向前爬.
>>> tt.bite()
咬死你咬死你!!
>>> tt.sleep()
困了,睡了,晚安,Zzzz
-------------------------------------------------------------------------------------
面向对象编程
self是什么
细心的读者会发现对象的方法都会有一个self参数,那这个self到底是个什么东西呢?如果此前接触过其他面向对象的编程语言,例如C++,那么你应该很容易对号入座,Python的self其实就相当于C++的this指针.
这里为了照顾大部分的初学编程的读者,讲解下self到底是个什么东西.如果把类比作是图纸,那么由类实例化后的对象才是真正可以住的房子.根据一张图纸就可以设计出成千上万的房子,它们长得都差不多,但它们都有不同的主人.每个人都只能回自己的家里,陪伴自己的孩子……所以self这里就相当于每个房子的门牌号,有了self,你就可以轻松找到自己的房子.
通过一个例子稍微感受下:
>>> class Ball:
def setName(self, name) :
self.name= name
def kick(self):
print("我叫 % s,噢~谁踢我?!" % self.name)
>>> a = Ball()
>> a.setName("飞火流星")
>>>b = Ball()
>> b.setName ("团队之星")
>>c = Ball()
>>>c.setName("土豆") #乱入…
>> a.kick()
我叫飞火流星,噢~谁踢我?!
>>> b.kick()
我叫团队之星,噢~谁踢我?!
>>> c.kick()
我叫土豆,噢~谁踢我?!
-------------------------------------------------------------------------------------
你听说过Python的魔法方法吗
据说,Python的对象天生拥有一些神奇的方法,它们是面向对象的Python的一切.它们是可以给你的类增加魔力的特殊方法,如果你的对象实现了这些方法中的某一个,那么这个方法就会在特殊的情况下被Python所调用,而这一切都是自动发生的.
Python的这些具有魔力的方法,总是被双下划线所包围,今天就讲其中一个最基本的特殊方法:__init()__,关于其他Python的魔法方法,接下来会专门用一个章节来详细讲解.
通常把__init__()方法称为构造方法,__init__()方法的魔力体现在只要实例化一个对象,这个方法就会在对象被创建时自动调用(在C++里你也可以看到类似的东西,叫"构造函数")
其实,实例化对象时是可以传入参数的,这些参数会自动传入__init__()方法中,可以通过重写这个方法来自定义对象的初始化操作.举个例子:
>>class Potato:
def __init__(self, name):
self.name = name
def kick(self):
print("我叫 % s,噢~谁踢我?!" % self.name)
>> p = Potato("土豆")
>> p.kick()
我叫土豆,噢~我?!
-------------------------------------------------------------------------------------
公有和私有
一般面向对象的编程语言都会区分公有和私有的数据类型,像C++和Java们使用public和private关键字,用于声明数据是公有的还是私有的,但在Python中并没有用类似的关键字来修饰.
难道Python所有东西都是透明的?也不全是,默认上对象的属性和方法都是公开的,可以直接通过点操作符(.)进行访问:
>>> class Person:
name ="小甲鱼"
>>> p = Person()
>>> p.name
'小甲鱼'
在Python中定义私有变量只需要在变量名或函数名前加上“__”两个下划线,那么这个函数或变量就会成为私有的了:
>>> class Person:
__name = "小甲鱼
>>>p = Person()
>>> p.__name
Traceback (most recent call last):
File "<pyshell#32>", line 1, in <module>
P.name
AttributeError: 'Person' object has no attribute'__name'
这样在外部将变量名"隐藏"起来了,理论上如果要访问,就需要从内部进行:
>>>class Person:
def __init__(self, name):
self.__name = nane
def getName(self):
return self.__name
>>>p= Person("小甲鱼")
>>> p.__name
Traceback(most recent call last):
File "< pyshell #40>", line 1, in <module>
p.__name
AttributeError: 'Person' object has no attribute'__nane'
>> p.getName()
'小甲鱼'
但是你认真琢磨一下这个技术的名字name mangling(名字改编),那就不难发现其实Python只是动了一下手脚,把双下横线开头的变量进行了改名而已.实际上在外部你使用
"类名 变量名"即可访问双下横线开头的私有变量了:
>>p._erson__name
'小甲鱼'
(注:Python目前的私有机制其实是伪私有,Python的类是没有权限控制的,所有变量都是可以被外部调用的.最后的这部分有些读者(尤其是没有接触过面向对象编程的读者)可能看不懂,想不明白有什么用?没事,先放着,下节讲完继承机制你就会豁然开朗了。)
-------------------------------------------------------------------------------------
继承
现在需要扩展游戏,对鱼类进行细分,有金鱼(Goldfish)、鲤鱼(Carp)、三文鱼(Salmon),还有鲨鱼(Shark),那么我们就再思考一个问题:能不能不要每次都从头到尾去重新定义一个新的鱼类呢?因为我们知道大部分鱼的属性和方法是相似的,如果有一种机制可以让这些相似的东西得以自动传递,那就方便快捷多了.没错,你猜到了,这种机制就是今天要讲的:继承.
语法很简单:
class类名(被继承的类):
...
被继承的类称为基类、父类或超类;继承者称为子类,一个子类可以继承它的父类的任何属性和方法.举个例子:
>>> class Parent:
def hello(self):
print("正在调用父类的方法."))

>>> class Child(Parent):
pass

>>> p = Parent()
>>> p.hello()
正在调用父类的方法…
>>> c = Child()
>>> c.hello()
正在调用父类的方法…

需要注意的是,如果子类中定义与父类同名的方法或属性,则会自动覆盖父类对应的方法或属性:
>>> class Child(Parent):
def hello(self):
print("正在调用子类的方法.")

>>> c = Child()
>>> c.hello()
正在调用子类的方法…

好,那尝试来写一下刚才提到的金鱼(Goldfish)、鲤鱼(Carp)、三文鱼(Salmon),还有鲨鱼(Shark)的例子:
#p11_2.py
import random as r

class Fish:
def __init__(self):
self.x = r.randint(0, 10)
self.y = r.randint(0, 10)
def move(self):
#这里主要演示类的继承机制,就不考虑检查场景边界和移动方向的问题
#假设所有鱼都是一路向西游
self.x -= 1
print("我的位置是:", self.x, self.y)
class Goldfish(Fish):
pass
class Carp(Fish):
pass
class Salmon(Fish):
pass
#上边几个都是食物,食物不需要有个性,所以直接继承Fish类的全部属性和方法即可
#下边定义鲨鱼类,这个是吃货,除了继承Fish类的属性和方法,还要添加一个吃的方法
class Shark(Fish):
def init(self):
self.hungry = True
def eat(self):
if self.hungry:
print("吃货的梦想就是天天有得吃^~")
self.hungry = False
else:
print("太撑了,吃不下!")
>>>#先运行p11_2.py
>>> fish = Fish()
>>># 试试小鱼能不能移动
>> fish.move()
我的位置是: 5 10
> goldfish = Goldfish()
>>> goldfish.move()
我的位置是: 9 10
>> goldfish.move()
我的位置是: 8 10
>>>goldfish.move()
我的位置是:7 10
>> #可见金鱼确实在一路向西…
>>>#下边尝试生成鲨鱼
>>> shark = Shark()
>>>#试试这货能不能吃东西?
>>> shark.eat()
吃货的梦想就是天天有得吃^_^
>>> shark.eat()
太撑了,吃不下!
>>> shark.move()
Traceback (most recent call last);
File "<pyshell#16>", line 1, in <module>
shark. move()
File "E:\p11_2.py", line 13, in move
self.x-= 1
AttributeError: 'Shark' object has no attribute 'x'
奇怪!同样是继承于Fish类,为什么金鱼(goldfish)可以移动,而鲨鱼(shark)一移动就报错呢?
其实这里抛出的异常说得很清楚了:Shark对象没有x属性.原因其实是这样的:在Shark类中,重写了魔法方法__init__,但新的__init__方法里边没有初始化鲨鱼的x坐标和y坐标,因此调用move方法就会出错.那么解决这个问题的方案就很明显了,应该在鲨鱼类中重写__init__方法的时候先调用基类Fish_init_方法。
下面介绍两种可以实现的技术:
.调用未绑定的父类方法.
.使用super函数.
-----------------------------------------------------------------------------
调用未绑定的父类方法
调用未绑定的父类方法,听起来有些高深,但大家参考下面改写的代码就能心领神会了:
class Shark(Fish):
def__init__(self):
Fish.__init__(self)
self.hungry=True
再运行下发现鲨鱼也可以成功移动了:
>>>#先运行修改后的p11-2.py
>>shark=Shark()
>>>shark.move()
我的位置是:7 9
>>>shark.move()
我的位置是:6 9
这里需要注意的是这个self并不是父类Fish的实例对象,而是子类Shark的实例对象,
所以这里说的未绑定是指并不需要绑定父类的实例对象,使用子类的实例对象代替即可。
有些读者可能不大理解,没关系,这一点都不重要!因为在Python中,有一个更好的方
案可以取代它,就是使用super函数。
-----------------------------------------------------------------------------
使用super函数
super函数能够帮我自动找到基类的方法,而且还为我们传入了self参数,这样就不需要做这些事情了:
#将p11-2.py鲨鱼的代码作如下修改
class Shark(Fish):
def__init__(self):
super().__init__()
self.hungry=True
运行后得到同样的结果:
>>>#先运行修改后的pl1-2.py
>>>shark=Shark()
>>>shark.move()
我的位置是:6 1
>>>shark.move()
我的位置是:5 1
super函数的“超级”之处在于你不需要明确给出任何基类的名字,它会自动帮您找出所有基类以及对应的方法。由于你不用给出基类的名字,这就意味着如果需要改变类继承关系,只要改变class语句里的父类即可,而不必在大量代码中去修改所有被继承的方法。
-----------------------------------------------------------------------------
多重继承
除此之外Python还支持多继承,就是可以同时继承多个父类的属性和方法:
class 类名(父类1,父类2,父类3,…):

举个例子:
>>>class Base1:
def foo1(self):
print("我是fool,我在Basel中…")
>>class Base2:
def foo2(self):
print("我是foo2,我在Base2中…")
>>>class C(Basel,Base2):
pass

>>c=C()
>>>c.foo1()
我是foo1,我在Basel中…
>>c.foo2()
我是foo2,,我在Base2中…
上面就是基本的多重继承语法。但多重继承其实很容易导致代码混乱,所以当你不确定是否真的必须使用多重继承的时候,请尽量避免使用它,因为有些时候会出现不可预见的BUG。
【扩展阅读】多重继承的陷阱:钻石继承(菱形继承)问题(http://bbs.fishc.com/thread-48759-1-1.html)。
-----------------------------------------------------------------------------
组合
前边先是学习了继承的概念,然后又学习了多重继承,但听到大牛们强调说不到必要的时候不使用多重继承。哎呀,这可让大家烦恼死了,就像上回我们有了乌龟类、鱼类,现在要求定义一个类,叫水池,水池里要有乌龟和鱼。用多重继承就显得很奇怪,因为水池和乌龟、鱼是不同物种,那要怎样才能把它们组合成一个水池的类呢?
在Python里其实很简单,直接把需要的类放进去实例化就可以了,这就叫组合:
#p11_3.py
class Turtle:
def__init__(self,x):
self.num=x
class Fish:
def_init__(self,x):
self.num=x
class Pool:
def__init__(self,x,y):
self.turtle=Turtle(x)
self.fish=Fish(y)
def print_num(self):
print("水池里总共有乌龟 %d 只,小鱼%d条!" % (self.turtle.num,self.fish.num))
>>>#先运行pl1_3.py
>>pool=Pool(1,10)
>>>pool.print_num()
水池里总共有乌龟1只,小鱼10条!
Python的特性其实还支持另外一种很流行的编程模式:Mixin,有兴趣的朋友可以看看【扩展阅读】Mixin 编程机制(http://bbs.fishc.com/thread-48888-1-1.html)。
-----------------------------------------------------------------------------
类、类对象和实例对象
先来分析一段代码:
>>>class C:
count=0
>>a = C()
>>b = C()
>>c = C()
>>>print(a.count,b.count,c.count)
0 0 0
>>c.count += 10
>>>print(a.count,b.count,c.count)
0 0 10
>>>C.count += 100
>>>print(a.count,b.count,c.count)
100 100 10
从上面的例子可以看出,对实例对象c的count属性进行赋值后,就相当于覆盖了类对象C的count属性。如图11-1所示,如果没有赋值覆盖,那么引用的是类对象的count属性。
需要注意的是,类中定义的属性是静态变量,也就是相当于C语言中加上static关键字声明的变量,类的属性是与类对象进行绑定,并不会依赖任何它的实例对象。这点待会儿继续讲解。
类、类对象和实例对象
类定义 C
类对象 C
实例对象 a b c
另外,如果属性的名字跟方法名相同,属性会覆盖方法:
class C;
def x(self):
print('Xman')
>>>c = C()
>>c.x()
Xman
>>c.x=1
>>>c.x
1
>>>c.x()
Traceback(most recent call last):
File"<pyshel1#20>",1ine 1, in<module>
c.x()
TypeError:'int' object is not callable
为了避免名字上的冲突,大家应该遵守一些约定俗成的规矩:
-类的定义要“少吃多餐”,不要试图在一个类里边定义出所有能想到的特性和方法,应该利用继承和组合机制来进行扩展。
-用不同的词性命名,如属性名用名词、方法名用动词,并使用骆驼命名法 等。
-----------------------------------------------------------------------------
到底什么是绑定
Python严格要求方法需要有实例才能被调用,这种限制其实就是Python所谓的绑定概念。前面也粗略地解释了一下绑定,但有些读者可能会这么尝试,然后发现也可以调用:
>>>class BB:
def printBB():
print ("no zuo no die")
>>> BB.printBB()
no zuo no die
但这样做会有一个问题,就是根据类实例化后的对象根本无法调用里边的函数:
>>>bb=BB()
>>>bb.printBB()
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
bb.printBB()
TypeError: printBB() takes O positional arguments but 1 was given

实际上由于Python的绑定机制,这里自动把bb对象作为第一个参数传入,所以才会出现TypeError
为了让大家更好地理解,再深入挖一挖:
>>>class CC:
def setxy(self, x, y)
self.x=x
self. y =y
def printXY(self):
print(self.x, self.y)
>>>dd=CC()
可以使用__dict__查看对象所拥有的属性:
>>>dd.__dict__
{}
>> CC.__dict__
mappingproxy({'__dict__:<attribute'__dict__'of 'CC' objects>,'printXY':<function CC. printxy at 0x02D2D2B8>,'__weakref__':<attribute, '__weakref__' of 'CC' objects>,'setxY': <function CC.setXY at 0x02AC1420>,'__doc__': None,'__module__':'__main__'})
__dict__属性是由一个字典组成,字典中仅有实例对象的属性,不显示类属性和特殊属性,键表示的是属性名,值表示属性相应的数据值。
>> dd.setXY(4, 5)
>>> dd.__dict__
{'x':4,'y':5}
现在实例对象dd有了两个新属性,而且这两个属性仅属于实例对象的:
>>CC.__dict__
mappingproxy('__doc__:None,'__dict__':<attribute'__dict__' of 'CC' objects >, '__weakref__:<attribute'__weakref__' of 'CC' objects>, 'printXy': < function CC.printXY at 0x0370D2B8>,'__module__','__main__','setxy': <function CC.setxY at 0x034A1420>})
为什么会这样呢?完全是归功于self参数:当实例对象dd去调用setXY方法的时候,它传入的第一个参数就是dd,那么self.x = 4, self.y=5也就相当于dd.x=4, dd.y=5,所以你在实例对象,甚至类对象中都看不到x和y,因为这两个属性是只属于实例对象dd的.
接着再深入一下,请思考:如果我把类实例删除掉,实例对象dd还能否调用printXY方法?
>>> del CC
答案是可以的:
>>> cc.printXY()
4 5
-----------------------------------------------------------------------------
一些相关的BIF
下面介绍与类和对象相关的一些BIF(内置函数)。
1.issubclass(class,classinfo)
如果第一个参数(class)是第二个参数(classinfo)的一个子类,则返回True,否则返回False:
(1)一个类被认为是其自身的子类.
(2)classinfo可以是类对象组成的元组,只要class是其中任何一个候选类的子类,则追回True.
(3)在其他情况下,会抛出一个TypeError异常
>> class A:
pass
>> class B(A):
pass
>>> issubclass(B, A)
True
>> issubclass(B, B)
True
>>> issubclass(B,object) # object是所有类的基类
True
>>> class C:
pass
>>> issubclass(B, C)
False
-----------------------------------------------------------------------------
2. isinstance(object, classinfo)
如果第一个参数(object)是第二个参数(classinfo)的实例对象,则返回True,否则返回False:
(1)如果object是classinfo的子类的一个实例,也符合条件.
(2)如果第一个参数不是对象,则永远返回False.
(3) classinfo可以是类对象组成的元组,只要class是其中任何一个候选类的子类,则返回True.
(4)如果第二个参数不是类或者由类对象组成的元组,会抛出一个TypeError异常.
>>> issubclass(B, C)
False
>>>b1 = B()
>>>isinstance(b1, B)
True
>>>isinstance(b1, c)
False
>> isinstance(b1, A)
True
>>>isinstance(b1, (A, B, C))
True
Python提供以下几个BIF用于访问对象的属性.
-----------------------------------------------------------------------------
3. hasattr(object, name)
attr即attribute的缩写,属性的意思.接下来将要介绍的几个BIF都是跟对象的属性有关系的,例如这个hasattr()的作用就是测试一个对象里是否有指定的属性.
·第一个参数(object)是对象,第二个参数(name)是属性名(属性的字符串名字),举个
例子:
class C:
def init (self, x=0):
self.x =x
>>>c1 =C()
>>>hasattr(c1, 'x') #注意,属性名要用引号括起来
True
-----------------------------------------------------------------------------
4.getattr(object,name[,default])
返回对象指定的属性值,如果指定的属性不存在,则返回default(可选参数)的值;若没有设置 default参数,则抛出ArttributeErrorr异常。
>>>getattr(c1,'x')
0
>> getattr(c1,'y')
Traceback(most recent call last):
File"<pyshell#7>",line 1,in <module>
getattr(cl,'y')
AttributeRrror:'C'object has no attribute'y'
>>> getattr(c1,'y','您所访问的属性不存在...'))
'您所访问的属性不存在...'
-----------------------------------------------------------------------------
5.setattr(object,name,value)
与getattr()对应,setattr()可以设置对象中指定属性的值,如果指定的属性不存在,则会新建属性并赋值。
>>> setattr(c1,'y','FishC')
>>> getattr(c1,'y')
'Fishc'
-----------------------------------------------------------------------------
6.delattr(object,name)
与setattr()相反,delattr()用于删除对象中指定的属性,如果属性不存在,则抛出AttributeError异常。
>>> delattr(c1,'y')
>>> delattr(c1,'z')
Traceback(most recent call last):
File"<pyshell#9>",line 1,in <module>
delattr(c1,'z')
AttributeError:z
-----------------------------------------------------------------------------
7. property(fget = None, fset = None, fdel = None, doc= None)
俗话说:条条大路通罗马.同样是完成一件事, Python其实提供了好几个方式供你选择.property()是一个比较奇葩的BIF,它的作用是通过属性来设置属性.说起来有点绕,看一下例子:
class C:
def __init__(self, size =10):
self.size = size
def getsize(self):
return self.size
def setSize(self,value):
self.size = value
def delSize(self):
del self.size
x = property(getSize, setSize, delSize)
>>>c.x
10
>c.x=12
>>c.x
12
>>c.size
12
>> del c.x
>>c.size
Traceback (most recent call last):
File "<pyshell#20>", line 1, in < module>
c. size
AttributeError: 'C' object has no attribute ' size'
property()返回一个可以设置属性的属性,当然如何设置属性还是需要人为来写代码.第一个参数是获得属性的方法名(例子中是getSize),第二个参数是设置属性的方法名(例子中是setSize),第三个参数是删除属性的方法名(例子中是delSize).
property()有什么作用呢?举个例子,在上面的例题中,为用户提供setSize方法名来设置size属性,并提供getSize方法名来获取属性.但是有一天你心血来潮,突然想对程序进行大改,就可能需要把setSize和getSize修改为setXSize和getXSize,那就不得不修改用户调用
的接口,这样的体验非常不好
有了property(),所有问题就迎刃而解了,因为像上边一样,为用户访问size属性只提供了x属性.无论内部怎么改动,只需要相应的修改property()的参数,用户仍然只需要去操作x属性即可,没有任何影响.
很神奇是吧?想知道它是如何工作的?学完紧接着要讲的魔法方法,你就知道了.

第12章 魔法方法

12.1 构造和析构

​ 在此之前,已经接触过Python最常用的魔法方法,小甲鱼也把魔法方法说得神乎其神,似乎用了就可以化腐朽为神奇,化干戈为玉帛,化不可能为可能!
​ 说的这么厉害,那什么是魔法方法呢?

  • 魔法方法总是被双下划线包围,例如init()
  • 魔法方法是面向对象的Python的一切,如果你不知道魔法方法,说明你还没能意识到面向对象的Python的强大。
  • 魔法方法得而”魔力”体现在它们总能够在适当的时候被调用.

12.1.1 _init_ (self[,..])

​ 之前我们讨论过 __init__()方法,说它相当于其他面向对象编程语言的构造方法,也就是类在实例化成对象的时候首先会调用的一个方法.
​ 有读者可能会问:”有时候在类定义时写_init_()方法,有时候却没有,这是为什么呢?”
这是我在论坛中看到的一个问题,我想应该不仅只有一位朋友有疑惑,所以在这里解释下:在现实生活中,有一种东西迫使我们去努力拼搏,使我们获得创造力和生产力,使我们不惜背井离乡来到一个陌生的城市承受孤独和寂寞,这个东西就叫需求….,我想我已经很好地回答了这个问题.举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#p12_1.py
class Rectangle:
"""
定义一个矩形类,
需要长和宽两个参数,
拥有计算周长和面积两个方法.
需要对象在初始化的时候拥有""和""两个参数,
因此需要重写__init__()方法,因为我们说过,
__init__()方法是类在实例化成对象的时候首先会调用的一个方法,
大家可以理解吗?
"""
def __init__(self,x, y):
self.x= x
self.y = y
def getPeri(self):
return (self.x + self.y) * 2
def getArea(self);
return self.x * self.y
>>> #先运行p12_1.py
>> rect = Rectangle(3, 4)
>> rect.getPeri()
14
>> rect.getArea()
12
1
2
3
4
5
6
7
8
9
10
这里需要注意的是, __init__()方法的返回值一定是None,不能是其他:
>>>class A:
def init(self):
return "A for A-Cup"
>>>cup = A()
Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
сup = A()
TypeError: _init () should return None, not 'str'
所以一般在需要进行初始化的时候才重写__init__()方法,现在大家应该就可以理解造物者的逻辑了.但是你要知道,神之所以是神,是因为他做什么事都留有一手.其实,这个__init__()并不是实例化对象时第一个被调用的魔法方法。

12.1.2 _new_(cls[,…])

​ __new__()才是在一个对象实例化的时候所调用的第一个方法.它跟其他魔法方法不同,它的第一个参数不是self而是这个类(cls),而其他的参数会直接传递给__init__()方法的.
​ __new__()方法需要返回一个实例对象,通常是cls这个类实例化的对象,当然你也可以返回其他对象。
​ __new__()方法平时很少去重写它,一般让Python用默认的方案执行就可以了.但是有一种情况需要重写这个魔法方法,就是当继承一个不可变的类型的时候,它的特性就显得尤为重要了。

1
2
3
4
5
6
7
8
class CapStr(str):
def __new__(cls, string):
string = string.upper()
return str.__new__(cls, string)

>>> a = CapStr("I love FishC.com")
>>> a
>>> 'I LOVE FISHC.COM'

​ 这里返回str.__new__(cls,string)这种做法是值得推崇的,只需要重写我们关注的那部分内容,然后其他的琐碎东西交给Python的默认机制去完成就可以了,毕竟它们出错的几率要比我们自己写小得多。

12.1.3 __del__(self)

​ 如果说__init__()和__new__() 方法是对象的构造器的话,那么Python也提供了一个析构器,叫作__del__()方法.当对象将要被销毁的时候,这个方法就会被调用.但一定要注意的是,并非del x就相当于自动调用x.__de__(),__del__()方法是当垃圾回收机制回收这个对象的时候调用的.举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class c
def init(self):
print("我是__init__方法,我被调用了...")
def __del_(self):
print("我是__del__方法,我被调用了...")

>>>c1 = C()
>>>我是__init__方法,我被调用了…
>>>c2 = c1
>>>c3 = c2
>>>del c1
>>>del c2
>>>del c3
我是__del__方法,我被调用了…

12.2 算数运算

​ 现在来讲一个新的名词:工厂函数,不知道大家还有没有听过?其实在老早就一直在使用它,但由于那时候还没有学习类和对象,我知道那时候说了也是白说。但我知道现在来告诉大家,理解起来就不再是问题了。
​ Python2.2以后,对类和类型进行了统一,做法就是将int() 、float() 、str()、list()、 tuple()这些BIF转换为工厂函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>>type(len)
<class 'builtin function or methad'>
>>>type(int)
<class 'type'>
>>>type(dir)
<class 'builtin function or method'>
>>>type(list)
<class 'type'>
看到没有,普通的BIF应该是<class 'builtin-function-or-method'>,而工厂函数则是<class 'type'>。大家有没有觉得这个<class 'type'>很眼熟,在哪里看过?没错啦,如果定义一个类:
>>> class C:
pass
>> type(C)
<class 'type'>
它的类型也是type类型,也就是类对象,其实所谓的工厂函数,其实就是一个类对象.当你调用它们的时候,事实上就是创建一个相应的实例对象:
>>> a = int ('1231')
>>> b = int('3451')
>>> a+b
468
现在你是不是豁然发现:原来对象是可以进行计算的!其实你早该发现这个问题了,Python中无处不对象,当在求a+b等于多少的时候,事实上Python就是在将两个对象进行相加操作.Python的魔法方法还提供了自定义对象的数值处理,通过对下面这些魔法方法的重写,可以自定义任何对象间的算术运算。

12.2.1 算数运算

表12-1列举了算数运算相关的魔法方法。

魔法方法 含义
__add__ (self, other) 定义加法的行为:+
__sub__(self, other) 定义减法的行为:-
__mul__(self, other) 定义乘法的行为:*
__truediv__(self, other) 定义真除法的行为:/
__floordiv__(self, other) 定义整数除法的行为: //
__mod__(self, other) 定义取模算法的行为: %
__divmod__(self, other) 定义当被divmod()调用时的行为
__pow__(self, other, modulo]) 定义当被power()调用或**运算时的行为
__Ishift__(self, other) 定义按位左移位的行为:<<
__rshift__(self, other) 定义按位右移位的行为:>>
__and__(self, other) 定义按位与操作的行为: &
__xor__(self, other) 定义按位异或操作的行为:^
__or__(self, other) 定义按位或操作的行为:|

举个例子,下面定义一个比较特立独行的类:

1
2
3
4
5
6
7
8
9
10
11
>>>class New int(int):
def add (self, other):
return int.sub (self, other)
def sub (self, other)
return int. add (self, other)
>>>a = New int(3)
>>>b = New int(5)
>>>a + b
-2
>>>a - b
8

​ 那有些读者可能会问:我想自己写代码,不想通过调用Python默认的方案行不行?答案是肯定行,但要格外小心!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> class Try_int( int):
def __add_(self, other):
return self + other
def __sub_(self, other):
return self - other
>>a = Try_int(1)
>>>b = Try_int(3)
>>a + b
Traceback (most recent call last):
File "<pyshell#9>", line 1, in <module>
a + b
File "<pyshell#6>", line 3, in __add__
return self + other
File "<pyshell#6>", line 3, in __add__
return self + other
#此处省略很多行…
为什么会陷入无限递归呢?问题就出在这里:
def add_(self, other):
return self + other

​ 当对象涉及加法操作时,自动调用魔法方法__add__(),但看看上边的魔法方法写的是什么?写的是return self + other,也就是返回对象本身加另外一个对象,这不就又自动触发调用__add__()方法了吗?这样就形成了无限递归。所以,像下面这么写就不会触发无限递归了:

1
2
3
4
5
6
7
8
9
>>>class New int(int):
def __add__(self, other):
return int(self) + int(other)
def __sub__(self, other):
return int(self) - int(other)
>>>a = New int(1)
>>>b = New int(3)
>>>a + b
4

​ 上边介绍了很多有关算术运算的魔法方法,意思是当对象进行了相关的算术运算,自然而然就会自动触发对应的魔法方法.嘿,有悟性的读者就会说: “哇,我似乎感觉到拥有了上帝的力量.没错吧?”
​ Python正是如此,对于初学者,他们不知道魔法方法,所以默认的魔法方法会让他们以合乎逻辑的形式运行.但当你逐步深入学习,慢慢有了沉淀之后,你突然发现如果有更多的灵活性,就可以把程序写得更好…这时候, Python也可以满足你.通过对指定魔法方法的重写,
你完全可以让Python根据你的意愿去执行。

1
2
3
4
5
6
7
>>>class int(int):
def __add__(self, other):
return int.__sub__(self, other)
>>>a = int('5')
>>>b = int("3')
>>a + b
2

​ 当然,我这样做从逻辑上是说不过去的…我只是想跟大家说,随着学习的足够深入,Python允许你做的事情就更多、更灵活!

12.2.2 反运算

表12-2列举了反运算相关的魔法方法。

魔法方法 含义
__radd__ (self, other) 定义加法的行为: +(当左操作数不支持相应的操作时被调用)
__rsub__(self, other) 定义减法的行为:-(当左操作数不支持相应的操作时被调用)
__rmul__(self, other) 定义乘法的行为: * (当左操作数不支持相应的操作时被调用)
__rtruediv__(self, other) 1定义真除法的行为: /(当左操作数不支持相应的操作时被调用)
__rfloordiv__(self, other) 定义整数除法的行为: //(当左操作数不支持相应的操作时被调用)
__rmod__(self, other) 定义取模算法的行为: %(当左操作数不支持相应的操作时被调用)
__rdivmod__(self, other) 定义当被divmod()调用时的行为(当左操作数不支持相应的操作时被调用)
__rpow__(self, other) 定义当被power()调用或**运算时的行为(当左操作数不支持相应的操作时被调用)
__rlshift__(self, other) 定义按位左移位的行为:<<(当左操作数不支持相应的操作时被调用)
__rrshift__(self, other) 定义按位右移位的行为: >>(当左操作数不支持相应的操作时被调用)
__rand__(self, other) 定义按位与操作的行为: &(当左操作数不支持相应的操作时被调用)
__rxor__(self, other) 定义按位异或操作的行为: ^(当左操作数不支持相应的操作时被调用)
__ror__(self, other) 定义按位或操作的行为: |(当左操作数不支持相应的操作时被调用)

​ 不难发现,这里的反运算魔法方法跟上节介绍的算术运算符保持一一对应,不同之处就是反运算的魔法方法多了一个”r”,例如,__add__()就对应__radd__(),举个例子:

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
>>a + b
#这里加数是a,被加数是b,请问大家:这里是a主动还是b主动?
#肯定是a主动,对不对?(就像"我请你吃饭"这句话,我肯定是主动,所以应该是由我给钱,但是,如果那天我刚好没带钱,那就叫蹭饭!但饭钱是一定要给的,那应该由谁来给?肯定就只能由b来给了.)
#那反运算是同样一个道理,如果a对象的__add__()方法没有实现或者不支持相应的操作,那么python就会自动调用b的__radd__()方法.
试一下:
>>>class Nint(int):
def __radd__(self, other):
return int.__sub__(other,self)
>>>a = Nint(5)
>>>b = Nint(3)
>>>a + b
8
#由于a对象默认有__add__()方法,所以b的__redd__()没有执行
#这样就有了:
>>>1 + b
-2

关于反运算,这里还要注意一点:对于a + b,b的__radd__(self, other)的self是b对象,other是a对象.
所以不能这么写:
>>>class Nint(int):.
def __rsub__(self, other):
return int.__sub__(self, other)
>>>a = Nint(5)
>>>3 - a
2

​ 所以对于注重操作数顺序的运算符(例如减法、除法、移位),在重写反运算魔法方法的时候,就一定要注意顺序问题了。

12.2.3 增量赋值运算

​ Python也有大量的魔术方法可以来定制增量赋值语句,增量赋值其实就是一种偷懒的形式,它将操作符与赋值来结合起来.例如:

1
2
3
>>>a =a + b
#写成增量赋值的形式就是:
>>>a +=b

12.2.4一元操作符

​ 一元操作符就是只有一个操作数的意思,像a + b这样,加号左右有a、b两个操作数,叫作二元操作符。只有一个操作数的,例如把减号放在一个操作数的前边,就是取这个操作数的相反数的意思,这时候管它叫负号.
​ Python支持的一元操作符主要有__neg__()(表示正号行为),__pos__()(定义负号行为),__abs__() (定义当被abs()调用时的行为,就是取绝对值的意思),还有一个__invert__()(定义按位取反的行为)。

12.3简单定制

基本要求:

  • 定制一个计时器的类。

  • start和stop方法代表启动计时和停止计时。

  • 假设计时器对象t1,print(t1)和直接调用t1均显示结果。

  • 当计时器未启动或已经停止计时,调用stop方法会给予温馨的提示。

  • 两个计时器对象可以进行相加:t1 + t2。

  • 只能使用提供的有限资源完成。

​ 这里需要限定你只能使用哪些资源,因为Python的模块是非常多的,你要是直接上网找个写好的模块进来,那就达不到锻炼的目的了.
下边是演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>>t1 = MyTimer()
>>>t1
未开始计时!
>>>t1.stop()
提示:请先调用start()开始计时!
>> t1.start()
计时开始…
>>t1
提示:请先调用stop()开始计时!
>>>t1.stop()
计时结束!
>>>t1
总共运行了5
>>>t2 = Myrimer()
>>>t2.start()
计时开始…
>>> t2.stop()
计时结束!
>>>t2
总共运行了6
>> t1 + t2
'总共运行了11秒'

你需要下面的资源:

  • 使用time模块的localtime方法获取时间(有关time模块可参考:http://bbs.fishc.com/thread-51326-1-1,html).

  • time.localtime返回struct_time的时间格式.

  • 表现你的类:__str__()和__repr__()魔法方法.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    >>>class A:
    def __str__(self):
    return"小甲鱼是帅哥"
    >>>a =A()
    >>>print(a)
    小甲鱼是帅哥
    >>>a
    <__main__.A object at 0x03260F30 >
    >>>class B:
    def__repr__(sef):
    return "小甲鱼是帅哥"
    >>>b = B()
    >>b
    小甲鱼是帅哥

    有了这些知识,可以开始来编写代码了:

    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
    import time as t 
    class Myrimer:
    #开始计时
    def start(self):
    self.start = t.localtime()
    print("计时开始...")
    #停止计时
    def stop(slef):
    self.stop = t.localtine()
    print("计时结束!")
    """
    好,万丈高楼平地起,把地基写好后,应该考虑怎么进行计算了.localtime()返回的是一个时间元组的结构,只需要前边6个元素,然后将stop的元素依次减去start对应的元素,将差值存放在一个新的列表里:
    """
    #停止计时
    def stop(self):
    self.stop = t.localtime()
    self._calc()
    print("计时结束!")
    #内部方法,计算运行时间
    def calc(self):
    self.lasted =[]
    self.prompt="总共运行了"
    for index in range(6):
    self.lasted.append(self.stop[index] - self.start[index])
    self.prompt += str(self.lasted[index])
    print(self.prompt)
    >>>t1 = MyTimer()
    >>>t1.start()
    计时开始…
    >>>t1.stop()
    总共运行了000003
    计时结束!
    """
    已经基本实现计时功能了,接下来需要完成"print(t1)和直接调用t1均显示结果",那就要通过重写__str__()和__repr__()魔法方法来实现:
    """
    def __str__(self):
    return self.prompt
    __repr__ = __str__
    >>> t1 = MyTimer()
    >>> t1.start()
    计时开始…
    >> t1.stop()
    计时结束!
    >> t1
    总共运行了000002
    似乎做得不错了,但这里还有一些问题。假设用户不按常理出牌,问题就会很多:
    >>> t1 = MyTimer()
    >>> t1
    Traceback(most recent call last):
    File"< pyshell#11>",line 1,in <module>
    t1
    File"c:\Python34\1ib\idlelib\rpc.py",line 614,in displayhook
    text = repr(value)
    File"C:\Users\Fishco00\Desktop\test.py",line 5,in __str__
    return self.prompt AttributeError:"MyTimer'object has no attribute 'prompt'
    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
    	当直接执行t1的时候,Python会调用__str__()魔法方法,但它却说这个类没有prompt属性。prompt属性在哪里定义的?在_calc()方法里定义的,对不?但是没有执行stop()方法,_calc()方法就没有被调用到,所以也就没有prompt属性的定义了。
    要解决这个问题也很简单,大家应该还记得在类里边,用得最多的一个魔法方法是什么?
    是__init__()嘛,所有属于实例对象的变量只要在这里边先定义,就不会出现这样的问题了。
    ...
    def init (self):
    self.prompt = "未开始计时!"
    self.lasted = []
    self.start = 0
    self.stop = 0
    ...
    >>t1 = Myrimer()
    >>t1
    未开始计时!
    >> t1. start()
    Traceback (most recent call last):
    File "< pyshell#2>", line 1, in <module>
    t1. start()
    TypeError: 'int' object is not callable
    这里又出错了(当然我是故意的),大家先检查一下是什么问题?
    其实会导致这个问题,是因犯了一个微妙的错误,这样的错误通常很容易疏忽,而且很难排查。Python这里抛出了一个异常:TypeError:'int' object is not callable。
    仔细瞧,在调用start()方法的时候报错,也就是说,Python认为start是一个整型变量,而不是一个方法。为什么呢?大家看__init__()方法里,是不是也命名了一个叫作self.start的变量,如果类中的方法名和属性同名,属性会覆盖方法。
    好了,让把所有的self.start和self.end都改为self.beginself.end吧!
    现在程序没问题了,但显示时间是000003这样不大人性化,还是希望可以按照"年月日小时分钟秒"这么去显示,然后值为0的就不显示啦,这样才是人看的嘛,对不对?!所以这里添加一个列表用来存放对应的单位:
    ...
    def __init__(self):
    self.unit =['年','月','天','小时','分钟','秒']
    self.prompt ="未开始计时!"
    self.lasted =[]
    self.beginoself.end =0
    #计算运行时间
    def _calc(self):
    self.lasted =[]
    self.prompt="总共运行了"
    for index in range(6):
    self.lasted.append(self.end index]-self.begin[index])
    if self.lasted[index]:
    self.prompt +=(str(self.lasted[index]) + self.unit[index])
    ...
    >>t1 =Myrimer()
    >>t1.start()
    计时k开始...
    > t1.stop()
    计时结束!
    >>t1
    总共运行了2

    然后在适当的地方增加温馨提示:
赞赏一下吧~