python learning

Basic 1

格式控制符语法、列表 list、判断、循环、字典 dictionary、函数、函数返回元组并赋给各个变量、默认参数、可变长度参数、递归:汉诺塔

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
# 廖雪峰的官方网站 python教材 1~4章
# 格式控制符语法
print('Hello, %s' % 'world')
print('hello, %s, you have %d dollars' % ('mickael', 1000))
print('hello, {0:s}, your grade {1:.1f}%'.format('xiaoming', 17.125))
s1 = 72
s2 = 85
improve = (s2-s1)/s1 * 100
print('hello, {0:s}, your grade improved by {1:.1f}%'.format("xiaoming", improve));
# 列表 list
classmate = ['xiaoming','xiaozhao','xiaohong']
print(classmate)
print(len(classmate))
print(classmate[1])
classmate.insert(1,"Jack")
print(classmate)
classmate.pop()
print(classmate)
classmate.pop(1)
print(classmate)
classmate[1] = "Sarah"
print(classmate)
L = ['apple', 123, True]
print(L)
# 判断
age = 3
if age>= 18:
print("adult")
elif age >= 6:
print("teenager")
else:
print("kids")
print("your age is", age)
# 转换成 int
birth = input('birth: ')
birth = int(birth)
if birth < 2000:
print("00qian")
else:
print("00hou")
# 循环
sum = 0
for x in list(range(5)):
sum = sum + x
print(sum)
alphobets = ['a','b','c']
for char in alphobets:
print(char)
names = ['michael', 'bob', 'Tracy']
for name in names:
print(name)
sum = 0
i = 0
while(i<101):
sum += i
i += 1
print(sum)
L = ['Bart', 'Lisa', 'Adam']
for name in L:
print("hello, %s!" % name)
# 字典 dictionary
d = {'Michael':95, 'Bob':75, 'Tracy':85}
d['Adam'] = 67
print(d['Adma'])
# 函数
def myabs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x>=0:
return x
else:
return -x
print(myabs(111))
# 函数返回元组
import math
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y + step * math.sin(angle)
return nx, ny
# 分别赋给每个变量
x, y = move(100,100,60,math.pi/6)
print(x,y)
def quadratic(a,b,c):
delta = b*b - 4*a*c
x1 = (-b + math.sqrt(delta)) / (2 * a)
x2 = (-b - math.sqrt(delta)) / (2 * a)
return x1, x2
print('quadratic(2, 3, 1) =', quadratic(2, 3, 1))
print('quadratic(1, 3, -4) =', quadratic(1, 3, -4))
if quadratic(2, 3, 1) != (-0.5, -1.0):
print('测试失败')
elif quadratic(1, 3, -4) != (1.0, -4.0):
print('测试失败')
else:
print('测试成功')
#def power(x):
# return x * x
# 默认参数
def power(x,n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
print(power(5))
print(power(5,3))
# 可变长度参数
def clac(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
print(clac(1,2))
print(clac())
# 递归:汉诺塔
# 参数n,表示初始时,3个柱子a、b、c中第1个柱子a上的盘子数量,
# 打印出把所有盘子从A借助B移动到C的方法
def move(n, a, b, c):
if n == 1:
print(a, '-->', c)
else:
move(n-1,a,c,b)
print(a,'-->',c)
move(n-1,b,a,c)
return
move(3,'A','B','C')

Basic 2

切片、迭代 Iterable Iterator、列表生成式、生成器 yield

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
L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
# 取前3个元素的笨方法
r = []
n = 3
for i in range(n):
r.append(L[i])
print(r)
# 切片
# 从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。
print(L[0:3])
print(L[:3]) #如果第一个索引是0,还可以省略:
print(L[-2:-1])
L = list(range(100))
print(L[:10])
print(L[-10:])
print(L[10:20])
print(L[0:10:2])
print(L[::5])
print('ABCDEFG'[:3])
#利用切片操作,实现一个trim()函数,去除字符串首尾的空格
def trim(s):
if s == '':
return s
i = 0
length = len(s)
j = length - 1
while(s[i]==' ' and i < length - 1):
i = i + 1
while(s[j]==' ' and j >= 1):
j = j - 1
return s[i:j+1]
# 测试:
if trim('hello ') != 'hello':
print('测试失败!')
elif trim(' hello') != 'hello':
print('测试失败!')
elif trim(' hello ') != 'hello':
print('测试失败!')
elif trim(' hello world ') != 'hello world':
print('测试失败!')
elif trim('') != '':
print('测试失败!')
elif trim(' ') != '':
print('测试失败!')
else:
print('测试成功!')
# 迭代
# 很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代
d = {'a':1 , 'b':2, 'c':3}
for key in d:
print(key, d[key])
for ch in "ABC":
print(ch)
# 如何判断一个对象是可迭代对象呢?
from collections import Iterable
print(isinstance('abc',Iterable))
print(isinstance('123',Iterable))
# 实现下标循环
# enumerate函数可以把一个list变成索引-元素对
for i, value in enumerate(['A','B','C']):
print(i,value)
# for循环里,同时引用了两个变量,在Python里是很常见的
for x,y in [(1,1),(2,4),(3,9)]:
print(x,y)
# 请使用迭代查找一个list中最小和最大值,并返回一个tuple:
def findMinAndMax(L):
if L == []:
return (None, None)
min = max = L[0]
for x in L:
if x > max:
max = x
if x < min:
min = x
return (min,max)
# 测试
if findMinAndMax([]) != (None, None):
print('测试失败!')
elif findMinAndMax([7]) != (7, 7):
print('测试失败!')
elif findMinAndMax([7, 1]) != (1, 7):
print('测试失败!')
elif findMinAndMax([7, 1, 3, 9, 5]) != (1, 9):
print('测试失败!')
else:
print('测试成功!')
# 列表生成式 List Comprehensions
# Python内置的非常简单却强大的可以用来创建list的生成式。
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list(range(1,11)))
# [1x1, 2x2, 3x3, ..., 10x10]
L = []
for x in range(1,11):
L.append(x*x)
print(L)
# 列表生成式则可以用一行语句代替循环生成上面的list
print([x*x for x in range(1,11)])
# 写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。
# for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
print([x*x for x in range(1,11) if x % 2 == 0])
# 笛卡尔积
print([m + n for m in 'ABC' for n in 'XYZ'])
# 例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:
import os
[d for d in os.listdir('.')]
# for循环其实可以同时使用两个甚至多个变量
# 列表生成式也可以使用两个变量来生成list:
d = {'x': 'A', 'y': 'B', 'z': 'C' }
print([k + '=' + v for k, v in d.items()])
# 列表中所有字符串变成小写
L = ['Hello', 'World', 'IBM', 'Apple']
print([s.lower() for s in L])
# 练习:如果list中既包含字符串,又包含整数,会报错
# 使用内建的isinstance函数可以判断一个变量是不是字符串:
# 修改列表生成式,在其中加上if语句保证可以正确执行
L1 = ['Hello', 'World', 18, 'Apple', None]
L2 = [s.lower() for s in L1 if isinstance(s, str)]
L3 = [s for s in L1 if isinstance(s,int)]
print(L3)
# 测试:
print(L2)
if L2 == ['hello', 'world', 'apple']:
print('测试通过!')
else:
print('测试失败!')
# 生成器
'''
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
'''
# 第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
L = [x * x for x in range(10)]
g = (x * x for x in range(10))
print(L)
print(g) # <generator object <genexpr> at 0x02587F60>
# 通过next()函数获得generator的下一个返回值:
# generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,
# 没有更多的元素时,抛出StopIteration的错误。
for n in g:
print(n)
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
'''
注意,赋值语句:
a, b = b, a + b
相当于:
t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]
'''
fib(6)
# 将上述函数改造成 generator
# 只需要把print(b)改为yield b就可以了:
# 变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
def fib1(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
f = fib(6)
print(f)
# 简单例子 依次返回数字 1 3 5
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
#调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:
o = odd()
print(next(o))
print(next(o))
print(next(o))
# 执行3次yield后,已经没有yield可以执行了。第4次调用next(o)就报错。
# 杨辉三角
'''
1
/ \
1 1
/ \ / \
1 2 1
/ \ / \ / \
1 3 3 1
/ \ / \ / \ / \
1 4 6 4 1
/ \ / \ / \ / \ / \
1 5 10 10 5 1
把每一行看做一个list,试写一个generator,不断输出下一行的list:
'''
def triangles():
N = [1]
while True:
yield N
N.append(0)
# print(N)
N = [N[i-1] + N[i] for i in range(len(N))]
n = 0
for t in triangles():
print(t)
n = n + 1
if n == 10:
break
# 期待输出:
# [1]
# [1, 1]
# [1, 2, 1]
# [1, 3, 3, 1]
# [1, 4, 6, 4, 1]
# [1, 5, 10, 10, 5, 1]
# [1, 6, 15, 20, 15, 6, 1]
# [1, 7, 21, 35, 35, 21, 7, 1]
# [1, 8, 28, 56, 70, 56, 28, 8, 1]
# [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
# 迭代器
'''
可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
为什么list、dict、str等数据类型不是Iterator?
Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
总结:
凡是可作用于for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
Python的for循环本质上就是通过不断调用next()函数实现的
'''

Functional Programming

函数可以是变量、简单的函数式编程、map/reduce、filter()、排序、函数作为返回值、匿名函数lambda、装饰器、偏函数

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
print(abs(-10))
# 函数可以是变量
f = abs
f(-10)
def add(x,y,f):
return f(x) + f(y)
x = -5
y = 6
f = abs
# 简单的函数式编程
print(add(x,y,f))
# 高阶函数
# map/reduce
# map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
def f(x):
return x * x
r = map(f, list(range(1,10)))
print(list(r))
# reduce
#reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,
#这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
#reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
from functools import reduce
def add(x, y):
return x + y
r = reduce(add, [1,3,5,7,9])
print(r)
def fn(x,y):
return x * 10 + y
r = reduce(fn,[1,3,5,7,9])
print(r)
# 配合map 写一个把str转换成int的函数
def char2num(s):
digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
return digits[s]
r = reduce(fn,map(char2num,'13579'))
print(r)
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
r = char2num('5')
print(r)
r = str2int('13579')
print(r)
# 练习
# 利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。
# 输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']:
def normalize(name):
return name.capitalize()
# 测试:
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)
# Python提供的sum()函数可以接受一个list并求和,
# 请编写一个prod()函数,可以接受一个list并利用reduce()求积:
def func(x,y):
return x * y
def prod(L):
return reduce(func , L)
print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
if prod([3, 5, 7, 9]) == 945:
print('测试成功!')
else:
print('测试失败!')
# 利用map和reduce编写一个str2float函数
# 把字符串'123.456789'转换成浮点数123.456789:
def str2float(s):
# 思路:map 将 123.456789 变为 123456798,即利用字符串的切片跳过小数点
def char2num(s):
return DIGITS[s]
# n = s.index('.') 取得小数点的下标 n = 3
n = s.index('.')
# reduce 函数把[123456789]变为整数之后,除以 10 ^ n1, n1 = len(s) - n - 1 = 10 - 3 - 1 = 6
n1 = len(s) - n - 1
return reduce(lambda x,y:x*10+y,map(char2num,s[:n]+s[n+1:]))/(10**n1)
print('str2float(\'123.456789\') =', str2float('123.456789'))
if abs(str2float('123.456789') - 123.456789) < 0.00001:
print('测试成功!')
else:
print('测试失败!')
# 过滤器
# Python内建的filter()函数用于过滤序列。
# 接受一个函数和一个序列,函数依次作用于序列里的每个元素,根据返回值是 True 还是 False 来决定保留还是丢弃该元素
def is_odd(n):
return n%2 == 1
L = list(filter(is_odd, [1,2,3,4,5,6,7,8,9,10]))
print(L)
# filter()函数返回的是一个Iterator
# 需要用list()函数获得所有结果并返回list。
# 删除一个序列中的空字符
# Python strip() 方法用于移除字符串头尾指定的字符(默认为空格)。返回移除字符串头尾指定的字符生成的新字符串。
# 空串("")为False,其他所有字符串皆为True
def not_empty(s):
return s and s.strip()
L = list(filter(not_empty, ['A','','B',None,'C',' ']))
print(L)
# filter 求素数 埃氏筛法
'''
首先,列出从2开始的所有自然数,构造一个序列:
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
取新序列的第一个数5,然后用5把序列的5的倍数筛掉:
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
不断筛下去,就可以得到所有的素数。
'''
# _odd_iter : 从 3 开始的奇数序列生成器 Iterator
def _odd_iter():
n = 1
while True:
n = n + 2
yield n
# 筛选函数
def _not_divisiable(n):
return lambda x : x % n > 0
# 生成器 不断返回下一个素数
def primes():
yield 2
it = _odd_iter() # Iterator
while True:
n = next(it) # Iterator 的第一项
yield n
it = filter(_not_divisiable(n),it) # 能跟第一项整除的就过滤掉
# 打印20之内的素数
for n in primes():
if n < 20:
print(n)
else:
break
# 练习:
# 回数是指从左向右读和从右向左读都是一样的数,例如12321,909。请利用filter()筛选出回数:
def _iter_():
n = 0
while True:
yield n
n = n + 1
def palindrome(n):
s = str(n)
i = 0
j = len(s) - 1
while i<j:
if s[i] != s[j]:
return False
else:
i = i + 1
j = j - 1
return True
output = filter(palindrome, range(1,200))
L = list(output)
print(L)
# sorted 排序算法
# 如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。
# Python内置的sorted()函数就可以对list进行排序:
print(sorted([36, 5, -12, 9, -21]))
# 此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序
# key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。
print(sorted([36, 5, -12, 9, -21], key=abs))
# 字符串排序
L = sorted(['bob', 'about', 'Zoo', 'Credit'])
print(L)
# 忽略大小写排序:
# 即需要key这个函数应用到所有元素上,都变成小写,再比较,所以就是:
L = sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
print(L)
# 逆序
L = sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
print(L)
# 从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。
# 练习:
# 用一组tuple表示学生名字和成绩:
# 请用sorted()对上述列表分别按名字、按成绩排序
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
# tips 选取元组中的元素方法同数组。
def by_name(t):
return t[0]
def by_grade(t):
return t[1]
L2 = sorted(L,key=by_name)
print(L2)
L2 = sorted(L,key=by_grade)
print(L2)
# 函数作为返回值
# 高阶函数可以接受函数作为参数,也可以把函数当做结果返回
# 可变参数求和
def calc_sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax
# 如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
#当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
f = lazy_sum(1,3,5,7,9)
print(f) # <function lazy_sum.<locals>.sum at 0x0227E8E8>
# 再调用 f 才是真正计算结果
print(f()) # 25
# 在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
# 返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。
# 匿名函数 & lambda 演算子
# 传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。
# Python中,对匿名函数提供了有限支持。
L = list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
print(L)
# 匿名函数lambda x: x * x实际上就是:
def f(x):
return x * x
L = list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
print(L)
# 匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
# 用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
f1 = lambda x : x * x
L = list(map(f1, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
print(L)
# 同样,也可以把匿名函数作为返回值返回,比如:
def build(x, y):
return lambda: x * x + y * y
# 练习:请用匿名函数改造下面的代码:
def is_odd(n):
return n % 2 == 1
L = list(filter(is_odd, range(1, 20)))
print(L)
L = list(filter(lambda x : x % 2 == 1, range(1,20)))
print(L)
# Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。
# 装饰器
# 函数也是一个对象
# 函数有一个 __name__ 属性可以拿到函数的名字
def now():
print("2018-05-02")
print(now.__name__)
f = now
print(f.__name__)
'''
现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,
这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
'''
# 本质上,decorator就是一个返回函数的高阶函数。
# *args: 可变参数
# **kw: 关键字参数
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
# log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。
# 借助Python的@语法,把decorator置于函数的定义处:
@log
def now1():
print('2018-05-02')
# 调用now1()函数,不仅会运行now1()函数本身,还会在运行now1()函数前打印一行日志:
# 把@log放到now1()函数的定义处,相当于执行了语句:now1 = log(now1)
now()
now1()
'''
由于log()是一个decorator,接受的参数就是原来的now,所以,原来的now1()函数仍然存在,
只是现在同名的now1变量指向了新的函数,于是调用now1()将执行新函数,即在log()函数中返回的wrapper()函数。
在wrapper()函数内,首先打印日志,再紧接着调用原始now1函数。
log()会返回一个函数,就是wrapper。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,自定义log的文本
'''
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
# 这个3层嵌套的decorator用法如下:
@log('execute')
def now2():
print('2018-05-02')
now2()
# To be completed
# 偏函数
# Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)
# 通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。
# int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:
print(int('12345'))
# 但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:
print(int('12345', base = 8))
print(int('12345', 16))
# 假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,
# 于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
def int2(x, base = 2):
return int(x, base)
print(int2('10000000'))
print(int2('10101010'))
# functools.partial就是帮助我们创建一个偏函数的,
# 不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
import functools
int2 = functools.partial(int, base = 2)
print(int2('10000000'))
print(int2('10101010'))
print(int2('10000000',base=10))
print(int2('10101010',base=10))
'''
创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数,
传入:
int2 = functools.partial(int, base=2)
实际上固定了int()函数的关键字参数base,也就是:
int2('10010')
相当于:
kw = { 'base': 2 }
int('10010', **kw)
当传入:
max2 = functools.partial(max, 10)
实际上会把10作为*args的一部分自动加到左边,也就是:
max2(5, 6, 7)
相当于:
args = (10, 5, 6, 7)
max(*args)
结果为10。
'''
# 当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

OOP1

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
class Student(object):
# 构造函数
# 第一个参数永远是 self 表示一个实例本身,但是传参的时候不需要传
# 在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
def __init__(self, name, score):
self.__name = name
self.__score = score
# 可以用 self 来表示类似 this 指针的作用
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
bart = Student('Bart', 59)
lisa = Student('Lisa', 89)
bart.print_score()
lisa.print_score()
# class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的
# 通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
# 创建实例是通过类名+()实现的
# 可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:
bart.sno = 111500206
print(bart.sno)
# 继承与多态
class Animal(object):
def run(self):
print("Animal is running.")
class Dog(Animal):
def run(self):
print("Dog is running.")
class Cat(Animal):
def run(self):
print("Cat is running")
dog = Dog()
dog.run()
cat = Cat()
cat.run()
# 判断一个变量是否是某个类型可以用isinstance()判断:
a = [1,2,3]
print(isinstance(a, list))
print(isinstance(dog, Animal))
print(isinstance(dog, Dog))
print(isinstance(dog,Animal)) # 子类实例也是基类的实例
def run_twice(animal):
animal.run()
animal.run()
run_twice(Animal())
run_twice(Dog())
run_twice(Cat())
# 如果以后新增 Animal 的子类,则不需要对 run_twice 进行任何修改
# 这就是著名的“开闭”原则:对扩展开放:允许新增Animal子类;对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
class Tortoise(Animal):
def run(self):
print('Tortoise is running slowly...')
run_twice(Tortoise())
# Tip:
'''
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
'''
# 获得对象信息
# 判断对象类型,使用type()函数:
print(type(123))
print(type('str'))
print(type(abs))
print(type(a))
print(type(dog))
print(type(123) == type(456))
print(type(123) == int)
# 判断一个对象是否是函数
import types
def fn():
pass
print(type(fn) == types.FunctionType)
print(type(abs) == types.BuiltinFunctionType)
print(type(lambda x : x) == types.LambdaType)
print(type((x for x in range(10)))==types.GeneratorType)
# 对于class的继承关系来说 ,可以使用isinstance()函数判断class的类型
class Husky(Dog):
def run(self):
print("Husky is running")
a = Animal()
d = Dog()
h = Husky()
print('isinstance test')
print(isinstance(h, Husky))
print(isinstance(d, Dog) and isinstance(d, Animal))
print(isinstance(d, Husky))
# isinstance 也可以当 type 有类似用法
print(isinstance('a', str))
# 还可以判断一个变量是否是某些类型中的一种
print(isinstance([1,2,3],(list,tuple)))
print(isinstance((1,2),(list,tuple)))
# 如果要获得一个对象的所有属性和方法,可以使用dir()函数
print(dir('ABC'))
print(dir(h))
'''
类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
其余的则是 普通属性或方法
'''
# 我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法:
class pig(Animal):
def __len__(self):
return 100
p = pig()
print(len(p))
# 实例属性和类属性
# 由于Python是动态语言,根据类创建的实例可以任意绑定属性。
class Undergraduate(object):
name = 'Student'
s = Undergraduate()
print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
print(Undergraduate.name) # 类的 name 属性
s.name = "Michael"
print(s.name)
del s.name # # 如果删除实例的name属性
print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
# 练习:
# 为了统计学生人数,可以增加一个类属性,每创建一个实例,该属性自动增加:
class Undergraduate(object):
count = 0
def __init__(self,name):
self.name = name
Undergraduate.count += 1
if Undergraduate.count != 0:
print('测试失败!')
else:
bart = Undergraduate('Bart')
if Undergraduate.count != 1:
print('测试失败!')
else:
lisa = Undergraduate('Bart')
if Undergraduate.count != 2:
print('测试失败!')
else:
print('Undergraduates:', Undergraduate.count)
print('测试通过!')

OOP2

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
class Student(object):
pass
s = Student()
s.name = 'Chang' # 给一个实例动态绑定一个属性
print(s.name)
def set_age(self, age):
self.age = age
from types import MethodType
s.set_age = MethodType(set_age, s) # 给一个实例动态绑定一个方法
s.set_age(25)
print(s.age)
s2 = Student()
'''
s2.set_age(25) # 会报错
给一个实例动态绑定的方法对另一个实例不起作用
'''
def set_score(self, score):
self.score = score
Student.set_score = set_score # 给所有实例都绑定方法,可以给class绑定方法:
# 给class绑定方法后,所有实例均可调用:
s.set_score(100)
print(s.score)
s2.set_score(99)
print(s2.score)
# 动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。
# __slots__ : 限制实例的属性
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
s = Student()
s.name = 'Michael'
s.age = 25
# s.score = 99 # 报错 AttributeError: 'Student' object has no attribute 'score'
class GraduateStudent(Student):
pass
g = GraduateStudent()
g.score = 9999 # __slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
# 在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。
##################################################
# 使用@property 来进行输入检测
s = Student()
s.age = 9999 # 这显然不合理
# 普通的方法就是在构造函数里加限制条件
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise TypeError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 and 100!')
self._score = value
s = Student()
s.set_score(60) # ok
print(s.get_score()) # 60
# s.set_score(9999) # error
# 然而上述调用方式稍显复杂,能不能用类似 属性 这样简单的方式来访问类的变量
# Python内置的@property装饰器就是负责把一个方法变成属性调用的:
class Student(object):
# getter 变成属性,加上一行 @property 就可以了
@property
def score(self):
return self._score
# @property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值
@score.setter
def score(self, value):
if not isinstance(value, int):
raise TypeError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s = Student()
s.score = 60 # score.setter
print(s.score) # score
# s.score = 9999 报 typeerror 错
# 定义只读属性:只定义 getter 不定义 setter
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2018 - self._birth
# 上面的birth是可读写属性,而age就是一个只读属性
s = Student()
s.birth = 1996
print(s.birth)
print(s.age)
# s.age = 100 # 报错:can't set attribute
# 练习:
# 请利用@property给一个Screen对象加上width和height属性,以及一个只读属性resolution:
class Screen(object):
@property
def width(self):
return self._width # 变量名和函数名要区分开,不然会死循环
@width.setter
def width(self, value):
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
self._height = value
@property
def resolution(self):
return self._width * self._height # 一个和C++很不相同的地方,在类内写属性也要加 slef. 前缀
# 测试:
s = Screen()
s.width = 1024
s.height = 768
print('resolution =', s.resolution)
if s.resolution == 786432:
print('测试通过!')
else:
print('测试失败!')
##################################################
# 多重继承
class Animal(object): # 基类
pass
class Mammal(Animal): # 哺乳动物
pass
class Bird(Animal): # 鸟类
pass
class Runnable(object): # 能跑
def run(self):
print('Running...')
class Flyable(object): # 能飞
def fly(self):
print('Flying...')
class Dog(Mammal, Runnable): # 狗
pass
class Bat(Mammal, Flyable): # 蝙蝠
pass
class Parrot(Bird, Flyable): # 鹦鹉
pass
class Ostrich(Bird, Runnable): # 鸵鸟
pass
# MixIn
'''
# 举个例子,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
# 比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPServer, ForkingMixIn):
pass
# 编写一个多线程模式的UDP服务,定义如下:
class MyUDPServer(UDPServer, ThreadingMixIn):
pass
'''
# 定制类
class Student(object):
def __init__(self, name):
self.name = name
print(Student('Micheal')) # <__main__.Student object at 0x00000140EEBFC320>
# 自定义打印实例信息
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
__repr__ = __str__ # __str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。
print(Student('Micheal'))
# __iter__
# 如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
class Fib(object):
def __init__(self):
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
self.a, self.b = self.b, self.a + self.b
if self.a > 100:
raise StopIteration() # 迭代的停止条件
return self.a
for n in Fib():
print(n)
# __getitem__
# Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行
# 不能通过 Fib()[5] 取元素
# 要想使用下标来取元素,需要实现 __getitem__() 方法
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
f = Fib()
for x in range(10):
print(f[x])
# 实现切片
class Fib(object):
def __getitem__(self, n):
if type(n) == int:
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if type(n) == slice:
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
f = Fib()
for x in range(10):
print(f[x])
print(f[0:5])
print(f[:10])
# 与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。
# 最后,还有一个__delitem__()方法,用于删除某个元素。
# __getattr__
# 正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。
# 要避免这个错误,除了可以加上这个属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr == 'score':
return 99
if attr == 'age':
return lambda:24
raise AttributeError('no such a attribute.')
# 当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值:
# 注意,只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。
s = Student()
print(s.name)
print(s.score)
print(s.age()) # 返回函数也可以,但相应的调用方式要变
# print(s.abc)
# 这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。
# __call__
# 在实例本身上调用
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s' % self.name)
s = Student("chang")
s()
# __call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象
# 怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用
# 使用 Callalbe 来判断类中有 __call__() 的类实例
print(callable(s))
# 枚举类
# Python 中实现常量定义
JAN = 1
FEB = 2
PI = 3.1415
# 缺点是 仍是变量
# 更好的方法是为这样的枚举类型定义一个class类型,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能:
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
# Month类型的枚举类,直接使用Month.Jan来引用一个常量,或者枚举它的所有成员,value属性则是自动赋给成员的int常量,默认从1开始计数
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)

Exception

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
# Exception
# 高级语言通常都内置了一套try...except...finally...的错误处理机制,Python也不例外。
# 当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,
# 执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。
try:
print('try...')
r = 10 / 0
print('result: ', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print("END")
# 如果把除数0改成2,
# 由于没有错误发生,所以except语句块不会被执行,但是finally如果有,则一定会被执行(可以没有finally语句)。
try:
print('try...')
r = 10 / 2
print('result: ', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print("END")
# 错误应该有很多种类,如果发生了不同类型的错误,应该由不同的except语句块处理。
try:
print('try...')
r = 10 / int('a')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
finally:
print('finally...')
print('END')
# Python 的异常都是 class,文档:https://docs.python.org/3/library/exceptions.html#exception-hierarchy
# 注意:捕获异常的语句会将其子类的异常也一同捕获
# 使用try...except捕获错误还有一个巨大的好处,就是可以跨越多层调用
# 比如函数main()调用foo(),foo()调用bar(),结果foo()出现了除以零错误,这时,只要main()捕获到了,就可以处理:
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
print('Error:', e)
finally:
print('finally...')
main()
# 也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写try...except...finally的麻烦。
# 调用栈
# 如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。
'''
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
bar('0')
main()
执行,结果如下:
Traceback (most recent call last): # 提示我们以下是错误跟踪信息
File "python learning Error, Debug and Test.py", line 101, in <module>
main() # 调用main出错了,位置是第 111 行,而原因是
File "python learning Error, Debug and Test.py", line 99, in main
bar('0') # 调用 bar('0') 出错,位置是第 99 行,而原因是
File "python learning Error, Debug and Test.py", line 96, in bar
return foo(s) * 2 # return foo(s) * 2这个语句出错了,但这还不是最终原因,继续往下看:
File "python learning Error, Debug and Test.py", line 93, in foo
return 10 / int(s) # return 10 / int(s)这个语句出错了,这是错误产生的源头
ZeroDivisionError: division by zero # 我们判断,int(s)本身并没有出错,但是int(s)返回0,在计算10 / 0时出错,至此,找到错误源头。
'''
# 记录错误
# 如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。
# 既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。
import logging
logging.basicConfig(level=logging.INFO)
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
logging.exception(e)
main()
print('END')
'''
ERROR:root:division by zero
Traceback (most recent call last):
File "python learning Error, Debug and Test.py", line 137, in main
bar('0')
File "python learning Error, Debug and Test.py", line 133, in bar
return foo(s) * 2
File "python learning Error, Debug and Test.py", line 130, in foo
return 10 / int(s)
ZeroDivisionError: division by zero
END # 继续打印了 END 表明程序打印完错误信息之后会继续执行并正常退出
'''
# 抛出错误
# 错误是class,捕获一个错误就是捕获到该class的一个实例。
# 错误并不是凭空产生的,而是有意创建并抛出的。
# Python的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。
# 如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例:
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n == 0:
raise FooError('invalid value: %s' % s)
return 10 / n
try:
foo('0')
except Exception as e:
logging.exception(e)
print('END')
'''
Traceback (most recent call last):
File "python learning Error, Debug and Test.py", line 176, in <module>
foo('0')
File "python learning Error, Debug and Test.py", line 173, in foo
raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0
'''
# 只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型(比如ValueError,TypeError),尽量使用Python内置的错误类型。
# 练习:
# 运行下面的代码,根据异常信息进行分析,定位出错误源头,并修复:
from functools import reduce
def str2num(s):
return int(s)
def calc(exp):
ss = exp.split('+')
ns = map(str2num, ss)
return reduce(lambda acc, x: acc + x, ns)
def main():
r = calc('100 + 200 + 345')
print('100 + 200 + 345 =', r)
r = calc('99 + 88 + 7.6')
print('99 + 88 + 7.6 =', r)
try:
main()
except Exception as e:
logging.exception(e)
print('END')
'''
控制台输出信息如下:
100 + 200 + 345 = 645
Traceback (most recent call last):
File "python learning Error, Debug and Test.py", line 212, in <module>
main()
File "python learning Error, Debug and Test.py", line 209, in main
r = calc('99 + 88 + 7.6')
File "python learning Error, Debug and Test.py", line 204, in calc
return reduce(lambda acc, x: acc + x, ns)
File "python learning Error, Debug and Test.py", line 199, in str2num
return int(s)
ValueError: invalid literal for int() with base 10: ' 7.6'
'''
# 分析得知:错误在于 str2num 这个函数无法把 7.6 转换成浮点数
from functools import reduce
def str2num(s):
if '.' in s:
return float(s)
else:
return int(s)
def calc(exp):
ss = exp.split('+')
ns = map(str2num, ss)
return reduce(lambda acc, x: acc + x, ns)
def main():
r = calc('100 + 200 + 345')
print('100 + 200 + 345 =', r)
r = calc('99 + 88 + 7.6')
print('99 + 88 + 7.6 =', r)
main()
#################################################################
# Debugging
# 断言
# 凡需要用到 print 来打印某个变量来查看是否正确的地方,都可以用 assert 来替代
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
# assert的意思是,assert 后面跟着的表达式应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。
# 如果断言失败,assert语句本身就会抛出AssertionError
# main()
'''
控制台输出如下:
Traceback (most recent call last):
File "python learning Error, Debug and Test.py", line 279, in <module>
main()
File "python learning Error, Debug and Test.py", line 272, in main
foo('0')
File "python learning Error, Debug and Test.py", line 268, in foo
assert n != 0, 'n is zero!'
AssertionError: n is zero!
'''
'''
程序中如果到处充斥着assert,和print()相比也好不到哪去。不过,启动Python解释器时可以用-O参数来关闭assert:
$ python -O err.py
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
关闭后,你可以把所有的assert语句当成pass来看。
'''
# logging
# Python内置的logging模块可以非常容易地记录错误信息:
# 还可以把print()替换为logging。
# 和assert比,logging不会抛出错误,而且可以输出到文件:
# 语句如下:
'''
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
'''
'''
INFO:root:n = 0
Traceback (most recent call last):
File "python learning Error, Debug and Test.py", line 315, in <module>
print(10 / n)
ZeroDivisionError: division by zero
logging的好处,它允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。
'''
# pdb 单步调试
import pdb
s = '0'
n = int(s)
pdb.set_trace() # 设置断点
print(10/n)
# pdb 指令:
# 启动调试器:python -m pdb err.py
# 输入命令n可以单步执行代码:
# 任何时候都可以输入命令 p 变量名来查看变量:
# 设置断点之后,直接 python err.py 就会停在断点处
# 命令 c 继续运行
'''
C:\Users\Administrator\Desktop>python err.py
> c:\users\administrator\desktop\err.py(10)<module>()
-> print(10/n)
(Pdb) p n
0
(Pdb) c
Traceback (most recent call last):
File "err.py", line 10, in <module>
print(10/n)
ZeroDivisionError: division by zero
'''
# 比较好用的 IDE:VSCode、PyCharm
# 虽然用IDE调试起来比较方便,但是最后你会发现,logging才是终极武器。

IO

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
f = open('test.txt', 'r') # 'r' 表示只读
s = f.read() # 调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示
print(s)
f.close()
# 由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally来实现:
try:
f = open('test.txt','r')
print(f.read())
finally:
if f:
f.close()
# 但是每次都这么写实在太繁琐,所以,Python引入了with语句来自动帮我们调用close()方法:
with open('test.txt','r') as f:
print(f.read())
# 这和前面的try ... finally是一样的,但是代码更佳简洁,并且不必调用f.close()方法。
# 调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,
# 所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。
# 也可以 用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。
# 如果文件很小,read()一次性读取最方便;
# 如果不能确定文件大小,反复调用read(size)比较保险;
# 如果是配置文件,调用readlines()最方便
f = open('test.txt', 'r')
for line in f.readlines():
print(line.strip())
# file-like Object
# 动态语言没有严格的继承体系限制,只要“act like a duck” 那么就是一只鸭子。
# open 函数返回的是 有 read() 方法的对象。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。
# 二进制文件
f = open('laopo.jpg', 'rb') # 用'rb'模式打开二进制文件
# print(f.read())
# 字符编码
# 要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:
# f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
# 遇到有些编码不规范的文件,你可能会遇到UnicodeDecodeError,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()函数还接收一个errors参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:
# f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')
# 写文件
# 参数改为 w 或 wb 表示写普通文件或二进制文件
# a 表示在文件末尾追加
# 具体模式定义参见文档:https://docs.python.org/3/library/functions.html#open
f = open('text.txt', 'w')
f.write('Hello, world!')
# 小结:在Python中,文件读写是通过open()函数打开的文件对象完成的。使用with语句操作文件IO是个好习惯。
############################################################
# StingIO
# 就是在内存中创建的临时 file-like Object,常常用作临时缓冲
from io import StringIO
f = StringIO()
f.write('hello')
f.write(' ')
f.write('world!')
print(f.getvalue())
# BytesIO
# 如果要操作二进制数据,就需要使用BytesIO。
from io import BytesIO
f = BytesIO()
f.write('中文'.encode('utf-8'))
print(f.getvalue())
# StringIO和BytesIO是在内存中操作str和bytes的方法,使得和读写文件具有一致的接口。
############################################################
# 操作文件和目录
# Python内置的os模块也可以直接调用操作系统提供的接口函数。
import os
print(os.name) # 如果是posix,说明系统是Linux、Unix或Mac OS X,如果是nt,就是Windows系统。
# os模块的某些函数是跟操作系统相关的。
# 环境变量
# print(os.environ)
# 要获取某个环境变量的值,可以调用os.environ.get('key')
# print(os.environ.get('PATH'))
# 操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中,这一点要注意一下。
# 查看、创建和删除目录可以这么调用:
# 查看当前绝对路径
p = os.path.abspath('.')
print(p)
# 在某个目录下面创建一个新目录
os.mkdir("E://bb")
os.rmdir("E://bb")
'''
把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符。在Linux/Unix/Mac下,os.path.join()返回这样的字符串:
part-1/part-2
而Windows下会返回这样的字符串:
part-1\part-2
'''
print(os.path.join(r'\Users\michael', 'testdir'))
# 同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:
p = os.path.split(r'C:\Users\michael\testdir\file.txt')
print(p)
# os.path.splitext()可以直接让你得到文件扩展名,很多时候非常方便:
p = os.path.splitext(r'C:\Users\michael\testdir\file.txt')
print(p)
# 这些合并、拆分路径的函数并不要求目录和文件要真实存在,它们只对字符串进行操作。
# 对文件重命名:
# os.rename('test.txt', 'test.py')
# 删掉文件:
# os.remove('test.py')
# 如何利用Python的特性来过滤文件。
# 列出当前目录下的所有目录:
L = [x for x in os.listdir('.') if os.path.isdir(x)]
print(L)
# 列出当前目录下所有的.py后缀的文件
L = [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
print(L)
# 练习
#实现dir -l
import os
def mydir(path):
if os.path.exists(path):
L = [(path+x) for x in os.listdir(path)]
print(L)
else:
print('The path not exists!')
#编写一个程序,能在当前目录以及当前目录的所有子目录下查找文件名包含指定字符串的文件,并打印出相对路径。
def findrelapath(path):
if os.path.exists(path):
L = os.listdir(path)
for x in L:
newpath = os.path.join(path,x)
if os.path.isfile(newpath):
print(x)
elif os.path.isdir(newpath):
findrelapath(newpath)
else:
print('The path not exists!')
####################################################################################
# 序列化
# 在程序运行的过程中,所有的变量都是在内存中的
# 一旦程序结束,变量所占用的内存就被操作系统全部回收。
# 把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思
# 序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
# 反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。
# Python提供了pickle模块来实现序列化。
import pickle
d = dict(name='bob', age=20, score=88)
print(d['name'])
print(d['age'])
print(pickle.dumps(d))
# pickle.dumps()方法把任意对象序列化成一个bytes,然后,就可以把这个bytes写入文件。
f = open('dump.txt', 'wb')
pickle.dump(d,f)
f.close()
# 看看写入的dump.txt文件,一堆乱七八糟的内容,这些都是Python保存的对象内部信息。
# 当我们要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化出对象,也可以直接用pickle.load()方法从一个file-like Object中直接反序列化出对象。
f = open('dump.txt', 'rb')
e = pickle.load(f)
f.close()
print(e)
# 变量的内容又回来了!不过,这个变量和原来的变量是完全不相干的对象,它们只是内容相同而已。
# Pickle的问题和所有其他编程语言特有的序列化问题一样,就是它只能用于Python,并且可能不同版本的Python彼此都不兼容
# JSON
# 如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如XML,但更好的方法是序列化为JSON,因为JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。
# JSON表示的对象就是标准的JavaScript语言的对象,JSON和Python内置的数据类型对应如下:
# Python内置的json模块提供了非常完善的Python对象到JSON格式的转换。我们先看看如何把Python对象变成一个JSON:
'''
JSON类型 Python类型
{} dict
[] list
"string" str
1234.56 int 或 float
true/false True/False
null None
'''
import json
d = dict(name='bob', age=20, score=88)
f = open('json.txt','w')
json.dump(d,f)
f.close()
# dumps()方法返回一个str,内容就是标准的JSON。
# .txt 文件中的内容:{"name": "bob", "age": 20, "score": 88}
# 要把JSON反序列化为Python对象,用loads()或者对应的load()方法,
# 前者把JSON的字符串反序列化,后者从file-like Object中读取字符串并反序列化:
f = open('json.txt','r')
s = f.read()
d = json.loads(s)
print(d)
f.close()

Regular Expression

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
# 正则表达式,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。
# 在正则表达式中,如果直接给出字符,就是精确匹配。用\d可以匹配一个数字,\w可以匹配一个字母或数字
'''
'00\d'可以匹配'007',但无法匹配'00A';
'\d\d\d'可以匹配'010';
'\w\w\d'可以匹配'py3';
'''
# .可以匹配任意字符,所以:
'''
'py.'可以匹配'pyc'、'pyo'、'py!'等等。
'''
# 用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符:
'''
来看一个复杂的例子:\d{3}\s+\d{3,8}。
我们来从左到右解读一下:
\d{3}表示匹配3个数字,例如'010';
\s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配' ',' '等;
\d{3,8}表示3-8个数字,例如'1234567'。
综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。
如果要匹配'010-12345'这样的号码呢?由于'-'是特殊字符,在正则表达式中,要用'\'转义,所以,上面的正则是\d{3}\-\d{3,8}。
'''
# 要做更精确地匹配,可以用[]表示范围,比如:
'''
[0-9a-zA-Z\_]可以匹配 一个 数字、字母或者下划线;
[0-9a-zA-Z\_]+可以匹配至少由 一个 数字、字母或者下划线组成的字符串,比如'a100','0_Z','Py3000'等等;
[a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下划线开头,后接 任意个 由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;
[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
'''
# A|B可以匹配A或B,所以(P|p)ython可以匹配'Python'或者'python'。
# ^表示行的开头,^\d表示必须以数字开头。
# $表示行的结束,\d$表示必须以数字结束。
# 例如:^py$就变成了整行匹配,就只能匹配'py'了。
# re模块:Python提供re模块,包含所有正则表达式的功能。
# 要特别注意的是,由于Python的字符串本身也用\转义,因此我们强烈建议使用Python的r前缀,就不用考虑转义的问题了
import re
re.match(r'\d{3}\-\d{3,8}$0','010-12345')
# match()方法判断是否匹配,如果匹配成功,返回一个Match对象,否则返回None。
test = '010-12345'
if re.match(r'\d{3}\-\d{3,8}$','010-12345'):
print('ok')
else:
print('failed')
# 切分
# 首先是常见的切分代码:
L = 'a b c'.split(' ')
print(L)
# 无法识别连续的空格,用正则表达式试试:
L = re.split(r'\s+', 'a b c')
print(L)
# 无论多少个空格都可以正常分割。加入,试试:
L = re.split(r'[\s\,]+', 'a,b, c d')
print(L)
# 再加入;试试:
L = re.split(r'[\s\,\;]+', 'a,b;; c d')
print(L)
# [] 表示范围,这个串至少一个的 \s 空白符 或者,逗号 或者;分号
# 分组
# 除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。
# 用()表示的就是要提取的分组(Group)。比如:
m = re.match(r'^(\d{3})-(\d{3,8})$', '010-123456')
print(m)
print(m.group(0))
print(m.group(1))
print(m.group(2))
# 如果正则表达式中定义了组,就可以在Match对象上用group()方法提取出子串来。
# group(0)永远是原始字符串,group(1)、group(2)……表示第1、2、……个子串。
t = '19:05:30'
m = re.match(r'^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$', t)
for x in range(4):
print(m.group(x))
# 贪婪匹配
# 最后需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。
x = re.match(r'^(\d+)(0*)$', '102300') # 匹配出现在数字后的 0 串
print(x.group(1))
print(x.group(2)) # 这个是空串
# 因为\d+采用贪婪匹配,直接把后面数字全部匹配了,0*只能匹配空字符串了。
# 必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:
x = re.match(r'^(\d+?)(0*)$', '102300') # 加个?就可以让\d+采用非贪婪匹配:
print(x.group(1))
print(x.group(2))
# 编译
'''
当我们在Python中使用正则表达式时,re模块内部会干两件事情:
编译正则表达式,如果正则表达式的字符串本身不合法,会报错;
用编译后的正则表达式去匹配字符串。
如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接匹配:
'''
re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
if re.match(re_telephone,'010-12345'):
print('ok')
else:
print('failed')
# 练习
# 请尝试写一个验证Email地址的正则表达式。版本一应该可以验证出类似的Email:
# someone@gmail.com
# bill.gates@microsoft.com
def is_valid_email(addr):
re_email = r'[0-9a-zA-Z\.]*@[0-9a-zA-Z]*\.com'
if re.match(re_email,addr):
print('ok')
return True
else:
print('failed')
return False
assert is_valid_email('someone@gmail.com')
assert is_valid_email('bill.gates@microsoft.com')
assert not is_valid_email('bob#example.com')
assert not is_valid_email('mr-bob@example.com')
print('测试通过')
# 练习二:
# 可以提取出 Email 的名字
# <Tom Paris> tom@voyager.org => Tom Paris
# bob@example.com => bob
# [<]{0,1} 表示 可以是 一个 < 或者空
def name_of_email(addr):
re_emailname = r'^[<]{0,1}([0-9a-zA-Z\s]*)[>|@]'
test = re.match(re_emailname, addr)
if test:
print('ok')
return test.group(1)
else:
print('failed')
return False
# 测试:
assert name_of_email('<Tom Paris> tom@voyager.org') == 'Tom Paris'
assert name_of_email('tom@voyager.org') == 'tom'
print('测试通过')

Process and Thread

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
# 多进程
# Windows下面没有fork ,请在linux下跑下面的代码
'''
import os
print('Process (%s) start...' % os.getpid())
pid = os.fork()
if pid==0:
print('I am child process (%s) and my parent is %s' % (os.getpid(), os.getppid())
else:
print('I (%s) just created a child process(%s).' % (os.getpid(), pid))
'''
# windows 下没有 fork,实现多进程要使用 multiprocessing 模块
from multiprocessing import Process
import os, time
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',)) # target 将函数作为单个进程,args 是传参数
print('Child process will start.')
p.start() # 启动
p.join() # 等待子进程结束后继续运行
print('Child process is end.')
print('..................')
time.sleep(1)
# Pool
# 要启动大量的子进程,可以用进程池的方式批量创建子进程:
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
print('Run task %s (%s)' % (name, os.getpid()))
start = time.time() # 返回当前时间的时间戳
time.sleep(random.random() * 3) # random() 方法返回随机生成的一个实数,它在[0,1)范围内。
end = time.time()
print('Task %s runs %0.2f seconds' % (name, (end-start)))
if __name__ == '__main__':
print('Parent process %s.'% os.getpid())
p = Pool(3)
for i in range(4):
p.apply_async(long_time_task, args=(i,))
print('waiting for all subprocesses done...')
p.close()
p.join()
print('Done')
print('..................')
time.sleep(1)
# 对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
# 进程间通信
# Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。
# 参考链接:https://www.cnblogs.com/kaituorensheng/p/4445418.html#_label5
from multiprocessing import Process, Queue
import os, time, random
# 写数据进程执行的代码:
def write(q):
print('Process to write: %s' % os.getpid())
for value in ['a','b','c']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
# 读
def read(q):
print('Process to read: %s' % os.getpid())
while True:
value = q.get(True)
print('Gets %s from queue.' % value)
if __name__ == '__main__':
# 父进程创建队列,传递给各个子进程
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start()
pr.start()
pw.join()
pr.terminate() # pr进程里是死循环,无法等待其结束,只能强行终止:
print('Done')
print('..................')
time.sleep(1)
##############################################################
# 多线程
'''
多任务可以由多进程完成,也可以由一个进程内的多线程完成。
我们前面提到了进程是由若干线程组成的,一个进程至少有一个线程。
由于线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程。
'''
# Python的标准库中,绝大多数情况下,我们只需要使用threading这个高级模块。
# 启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:
import time, threading
# 新进程执行的代码:
def loop():
print('thread %s is running ...' % threading.current_thread().name)
n = 0
while n < 3:
n = n + 1
print('thread %s >>> %s' % (threading.current_thread().name,n))
time.sleep(1)
print('thread %s ended.' % threading.current_thread().name)
print('thread %s is running ...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopTread') # 子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)
# 任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程
# current_thread()函数,它永远返回当前线程的实例。
# Lock
# 多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,
# 而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
# 假定这是你的银行存款:
balance = 0
def change_it(n):
# 先存后取,结果应该为0:
global balance # 告诉Python这个变量名不是局部的,而是 全局 的
###
balance = balance + n
balance = balance - n
### 这两句语句在一个线程里应该是要连续执行才对
def run_thread(n):
for i in range(5000000):
change_it(n)
t1 = threading.Thread(target=run_thread,args=(5,))
t2 = threading.Thread(target=run_thread,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance) # 由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,balance的结果就不一定是0了。
print('Done.......................')
# 为函数 change_it 添加一个锁
# 当某个线程开始执行change_it()时,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能执行。
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(5000000):
# p(信号量)
lock.acquire()
try:
change_it(n)
finally:
# v(信号量)
lock.release()
print(balance)
print('Done.......................')