- pycharm输入一个方法时,不知道要给定哪些参数的话,可以按
commond + P
得到提示
1. Python基础
(1)直接运行.py文件:
- 在MacOS或Linux系统中,可以直接运行.py文件,方法是在.py文件的第一行加上以下注释:
#!/opt/miniconda3/bin/python
print("Hello, World!")
- 然后修改.py文件的权限:
chmod a+x hello.py
- 这时就可以直接执行hello.py文件了:
./hello.py
(2)数据类型
- 整数
- 计算机由于使用二进制,所以,有时候用十六进制表示整数比较方便,十六进制用
0x
前缀和0-9,a-f表示,例如:0xff00
,0xa5b4c3d2
,等等。 - 对于很大的数,例如
10000000000
,很难数清楚0的个数。Python允许在数字中间以_
分隔,因此,写成10_000_000_000
和10000000000
是完全一样的。十六进制数也可以写成0xa1b2_c3d4
- 计算机由于使用二进制,所以,有时候用十六进制表示整数比较方便,十六进制用
- 字符串
- r’ '表示‘’内部的字符串默认不转意
print(r'\\\t\\') # 输出: \\\t\\
- 如果字符串内部有很多换行,用
\n
写在一行里不好阅读,为了简化,Python允许用'''...'''
的格式表示多行内容
- r’ '表示‘’内部的字符串默认不转意
print('''line1
line2
line3''')
# 输出:
line1
line2
line3
-
取字符的整数表示 ord()
ord('A') # 65
-
把编码转换为对应的字符 chr()
chr(66) # 'B'
-
把str转换为以字节为单位bytes b
x = b'ABC'
-
.encode()方法可以把字符串编码为指定的bytes :
'ABC'.encode('ascii')
'中文'.encode('utf-8')
-
.decode()方法把字节流bytes转换为字符串:
b'ABC'.decode('ascii')
- 如果bytes中存在无法解码的字节,decode()方法会报错;
- 可以指定errors='ignore’忽略不能解码的字节:
b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
-
当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
(3)输出格式
-
% :
'Hello, %s' % 'world'
Hi, %s, you have $%d.' % ('Michael', 1000000)
- %d 整数
- %f 浮点数
- %s 字符串
- %x 十六进制整数
- %% 转移字符,表示百分号
-
.format()
'Hello, {0}, 成绩提升了{1:.1f}%'.format('小明', 17.125) # Hello,小明,成绩提升了17.1%
-
f-string 使用以
f
开头的字符串,称之为f-string
,它和普通字符串不同之处在于,字符串如果包含{xxx}
,就会以对应的变量替换
r = 2.5
s = 3.14 * r ** 2
print(f'The area of a circle with radius {r} is {s:.2f}')
The area of a circle with radius 2.5 is 19.62
(4)字典
dct = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
- 获取键为’Michael‘的值:
dct['Michael']
dct.get('Michael') # 如果键不存在,则返回None
dct.get('Michael', -1) # 如果键不存在,则返回-1
- dict 的key必须是不可变对象:
- 这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。
- 这个通过key计算位置的算法称为哈希算法(Hash)。
- 要保证hash的正确性,作为key的对象就不能变。
- 在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key
(5)set
- 创建:
s = set([1, 1, 2, 2, 3, 3]) # {1,2,3}
- 添加:
s.add(4)
- 删除:
s.remove(4)
- 求交:
s1 & s2
- 求并:
s1 | s2
- set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。list不可以放入set。
2. 函数
(1)默认参数
def add_end(L='default')
- 定义默认参数要牢记一点:默认参数必须指向不变对象!
- 如果指向可变对象,第二次调用时,默认指向的对象在第一次调用后发生了变化,第二次会直接使用第一次调用后变化的对象,对结果产生不可预知的影响
(2)可变参数
- 定义函数时传可变参数:使用
*numbers
表示传入的参数数量可变,numbers为tuple,保存所有传入的参数
# 定义时:
def calc(*numbers):
sum = 0
for n in numbers:
sum += n*n
return sum
# 调用时:
calc(1,2,3)
calc(1,2,3,4)
- 调用时传入可变参数:
*nums
表示将nums中的所有元素传入函数调用中
nums = [1,2,3]
calc(*nums) # 等价于 calc(nums[0], nums[1], nums[2])
(3)关键字参数
- 可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
person('Michael', 30) # name: Michael age: 30 other: {}
person('Bob', 35, city='Beijing') # name: Bob age: 45 other: {'city': 'Beijing'}
- 调用时,用
**
修饰字典变量,注意此处传入的**extra
是extra
的一份拷贝,函数调用中对传入的参数的改变,不会影响到原始变量extra
extra = {'city': 'Beijing', 'job': Engineer}
person('Jack', 24, **extra) # 等价于 person('Jack', 24, city=extra['city'], job=extra['job'])
(4)命名关键字参数
- 如果要限制关键字参数的名字,就可以用命名关键字参数
- 和关键字参数
**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数
def person(name, age, *, city, job):
print(name, age, city, job)
- 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符
*
了:
def person(name, age, *args, city, job):
print(name, age, args, city, job)
- 命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() missing 2 required keyword-only arguments: 'city' and 'job'
- 由于调用时缺少参数名
city
和job
,Python解释器把前两个参数视为位置参数,后两个参数传给*args
,但缺少命名关键字参数导致报错。
(5)组合参数
- 在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
- 对于任意函数,都可以通过类似
func(*args, **kw)
的形式调用它,无论它的参数是如何定义的 - 虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差。
(6)递归函数
- 尾递归优化:
- 解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
- 尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。
- 这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
# 优化前的递归函数
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
# 尾递归优化
def fact(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
3. 高级特性
(1)迭代
- 判断一个对象是不是可迭代对象:
collections.abc
的Iterable
类型
from collections.abc import Iterable
print(isinstance('abc', Iterable)) # str是可迭代对象 True
print(isinstance(123, Iterable)) # 整数不是可迭代对象 False
(2)列表生成式
nums = list(range(1, 11)) # [1,2,3,4,5,6,7,8,9,10]
[x*x for x in range(1, 11)] # [1,4,9,16,25,36,49,64,81,100]
[x*x for x in range(1, 11) if x%2==0] # [4,16,36,64,100]
[m+n for m in 'ABC' for n in 'XYZ'] # ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
d = {'x': 'A', 'y': 'B', 'z': 'C'}
[k + '=' + v for k,v in d.items()] # ['y=B', 'x=A', 'z=C']
(3)生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator
- 列表:
l = [x*x for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
- 生成器:
g = (x*x for x in range(10))
# <generator object <genexpr> at 0x1022ef630>
- 访问生成器的元素:
next(g)
- 遍历生成器的元素:
for n in g:
- 定义生成器的另一种方法:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b # yield
a, b = b, a+b
n = n+1
return 'done'
- generator的函数,在每次调用
next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行 - 生成杨辉三角
def triangles():
L = [1]
while True:
yield L
L1 = L + [0]
L2 = [0] + L
L = [L1[i] + L2[i] for i in range(len(L1))]
(4)迭代器
- 将可迭代对象(Iterable)变为迭代器 Iterator对象:
iter(.....)
4. 函数式编程
(1)高阶函数
- 函数名也是变量:
f = abs # f指向函数abs(), f是指向函数的变量,其实abs也是指向函数的变量 打印f,是: <built-in function abs> print(f(-10)) # 10
- 传入函数:
def add(x, y, f):
return f(x) + f(y)
# 调用:
add(-5, 6, abs)
(2)map/reduce
- map()
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。map(fun, list)
- 例如:
map(str, [1,2,3,4,5])
将list中的所有int转换为str,转换后的结果是一个Iterator
对象,Iterator
是惰性序列,因此可以用list()
转换 list(map(str, [1,2,3,4,5]))
- reduce()
from functools import reduce
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
- reduce接收的函数可以用lambda函数直接定义
reduce(lambda x, y: x*10+y, map(int, s))
- 首先用
map()
将字符串s
中的每一个字符通过int()
函数转换为整型变量; - 再通过
reduce()
将所有int值拼接成一个整型数值
- 首先用
(3)filter
- 和
map()
类似,filter()
也接收一个函数和一个序列。 - 和
map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。 - 例如,在一个list中,删掉偶数,只保留奇数:
def is_odd(n): return n % 2 == 1 list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])) # 结果: [1, 5, 9, 15]
- 可见用
filter()
这个高阶函数,关键在于正确实现一个“筛选”函数。 - 注意到
filter()
函数返回的是一个Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()
函数获得所有结果并返回list。
(4)sorted
- Python内置的
sorted()
函数就可以对list进行排序:sorted([36, 5, -12, 9, -21])
sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序,例如按绝对值大小排序:
sorted([36, 5, -12, 9, -21], key=abs)
key
需传入一个函数,该函数会作用在list的每个元素上,然后将函数的每一个对应输出作为排序的依据- 要进行反向排序,不必改动key函数,可以传入第三个参数
reverse=True
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
(5)返回函数
- 函数作为返回值:高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
def calc_sum(*args): ax = 0 for n in args: ax += n return ax # 可以不返回求和的结果,而是返回求和的函数 def lazy_sum(*args): def sum(): ax = 0 for n in args: ax += n return ax return sum
- 当我们调用
lazy_sum()
时,返回的并不是求和结果,而是求和函数:<function lazy_sum.<locals>.sum at 0x101c6ed90>
- 调用函数
f()
时,才真正计算求和的结果:25
- 在这个例子中,我们在函数
lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。 - 调用
calc_sum()
时,返回的函数并没有立刻执行,而是直到调用了f()
才执行。
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
f1() # 9
f2() # 9
f3() # 9
# 返回的函数引用了变量`i`,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量`i`已经变成了`3`,因此最终结果为`9`。
- 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
- 如果在内层函数
fn()
中,对外层变量x
赋值,由于Python解释器会把x
当作函数fn()
的局部变量,它会报错,原因是x
作为局部变量并没有初始化,直接计算x+1
是不行的。但我们其实是想引用inc()
函数内部的x
,所以需要在fn()
函数内部加一个nonlocal x
的声明。加上这个声明后,解释器把fn()
的x
看作外层函数的局部变量,它已经被初始化了,可以正确计算x+1
。
def inc():
x = 0
def fn():
nonlocal x # 将x声明为nonlocal,代指外层的x
x = x + 1
return x
return fn
f = inc()
print(f()) # 1
print(f()) # 2
- 使用闭包时,对外层变量赋值前,需要先使用nonlocal声明该变量不是当前函数的局部变量。
(6)匿名函数
- 关键字
lambda
表示匿名函数,冒号前面的x
表示函数参数。 - 匿名函数有个限制,就是只能有一个表达式,不用写
return
,返回值就是该表达式的结果。 - 用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
- 同样,也可以把匿名函数作为返回值返回,比如:
def build(x, y):
return lambda: x * x + y * y
(7)装饰器
- 定义一个
now()
函数
def now():
print('2015-3-25')
- 现在,假设我们要增强
now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator) - 本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator
- 不带参数的装饰器
log
:
import functools
def log(func):
@functools.wraps(func) # 返回的那个`wrapper()`函数名字就是`'wrapper'`,所以,需要把原始函数的`__name__`等属性复制到`wrapper()`函数中,否则,有些依赖函数签名的代码执行就会出错。不需要编写`wrapper.__name__ = func.__name__`这样的代码,Python内置的`functools.wraps`就是干这个事的
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
- 使用:
@log
def now():
print('2015-3-25')
- 带参数的装饰器:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
- 使用:
@log('execute')
def now():
print('2015-3-25')
(8)偏函数
functools.partial
的作用是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
import functools
int2 = functools.partial(int, base=2)
int2('1000000') # 64
# 上述代码定义的int2等价于:
def int2(x, base=2):
return int(x, base) # 以2进制的形式传入x,转为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
。
5. 模块
(1)包:
- 引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。
mycompany
├─ __init__.py
├─ abc.py
└─ xyz.py
- 请注意,每一个包目录下面都会有一个
__init__.py
的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py
可以是空文件,也可以有Python代码,因为__init__.py
本身就是一个模块,而它的模块名就是mycompany
- 自己创建模块时要注意命名,不能和Python自带的模块名称冲突。例如,系统自带了sys模块,自己的模块就不可命名为sys.py,否则将无法导入系统自带的sys模块。
(2)使用模块:
#!/opt/miniconda3/envs/dl/bin/python
# -*- coding: utf-8 -*-
' a test module '
__author__ = 'Michael Liao'
import sys
def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')
if __name__=='__main__':
test()
- 第1行和第2行是标准注释,第1行注释可以让这个
hello.py
文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码; - 第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
- 第6行使用
__author__
变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名; - 以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。
- 后面开始就是真正的代码部分。
- 作用域:
- 在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过
_
前缀来实现的。 - 正常的函数和变量名是公开的(public),可以被直接引用,比如:
abc
,x123
,PI
等; - 类似
__xxx__
这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__
,__name__
就是特殊变量,hello
模块定义的文档注释也可以用特殊变量__doc__
访问,我们自己的变量一般不要用这种变量名; - 类似
_xxx
和__xxx
这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc
,__abc
等; - 之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。
- 在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过
6. 面向对象编程
(1)类和实例
class
后面紧接着是类名,即Student
,类名通常是大写开头的单词,紧接着是(object)
,表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object
类,这是所有类最终都会继承的类。继承object
类时,object
可以省略- 和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同
class Student(object):
pass
bart = Student()
bart.name = 'Bart Simpson'
- 由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的
__init__
方法,在创建实例的时候,就把name
,score
等属性绑上去。注意:特殊方法“init”前后分别有两个下划线!!!
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
- 注意到
__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身 - 有了
__init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self
不需要传,Python解释器自己会把实例变量传进去
(2)访问限制
- 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线
__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
-
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
-
但是如果外部代码要获取name和score怎么办?可以给Student类增加
get_name
和get_score
这样的方法:
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
- 如果又要允许外部代码修改score怎么办?可以再给Student类增加
set_score
方法:
class Student(object):
...
def set_score(self, score):
self.__score = score
-
需要注意的是,在Python中,变量名类似
__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__
、__score__
这样的变量名。 -
有些时候,你会看到以一个下划线开头的实例变量名,比如
_name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。 -
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问
__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量
(3)继承和多态
- 在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
class Dog(Animal)
,表示Dog类继承自Animal父类,Dog类就自动拥有了Animal类的所有功能 - 多态的好处就是,当我们需要传入
Dog
、Cat
、Tortoise
……时,我们只需要接收Animal
类型就可以了,因为Dog
、Cat
、Tortoise
……都是Animal
类型,然后,按照Animal
类型进行操作即可。由于Animal
类型有run()
方法,因此,传入的任意类型,只要是Animal
类或者子类,就会自动调用实际类型的run()
方法,这就是多态的意思 - 判断一个变量是否是某个类型可以用
isinstance()
判断:isinstance(b, Animal)
- 静态语言 vs 动态语言
- 对于静态语言(例如Java)来说,如果需要传入
Animal
类型,则传入的对象必须是Animal
类型或者它的子类,否则,将无法调用run()
方法。 - 对于Python这样的动态语言来说,则不一定需要传入
Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了:
- 对于静态语言(例如Java)来说,如果需要传入
class Animal(object):
def run(self):
print('Animal is running')
class Dog(Animal):
def run(self):
print('Dog is running')
class Timer(object):
def run(self):
print('Start...')
def run_twice(animal):
animal.run()
animal.run()
- 这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
- Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个`read()`方法,返回其内容。但是,许多对象,只要有`read()`方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了`read()`方法的对象。
class Timer(object):
def run(self):
print('Start...')
# Timer()类有run()方法,就是file-like object方法,它虽然不是继承自Animal类,但是也可以被run_twice()方法调用:
run_twice(Animal())
run_twice(Dog())
run_twice(Timer()) #
(4)获取对象属性
- 使用
type()
判断对象类型
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
- 判断基本数据类型可以直接写
int
,str
等,但如果要判断一个对象是否是函数怎么办?可以使用types
模块中定义的常量:
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
- 要判断class的类型,可以使用
isinstance()
函数。isinstance(dog, Animal)
- 能用
type()
判断的基本类型也可以用isinstance()
判断isinstance(123, int)
- 并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple)) True >>> isinstance((1, 2, 3), (list, tuple)) True
- 能用
- 如果要获得一个对象的所有属性和方法,可以使用
dir()
函数,它返回一个包含字符串的list- 我们自己写的类,如果也想用
len(myObj)
的话,就自己写一个__len__()
方法
>>> class MyDog(object): ... def __len__(self): ... return 100 ... >>> dog = MyDog() >>> len(dog) 100
hasattr(obj, 'x') # 返回bool,查看对象obj是否有'x'属性
getattr(obj, 'lower') # 返回bool,查看对象obj是否有'lower()方法'
getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
setattr(obj, 'y', 0) # 将对象obj中的y属性设置为9
- 我们自己写的类,如果也想用
(5)实例属性和类属性
- 在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
7. 面向对象高级编程
(1)使用__slots__
- 给单个实例动态绑定方法:
class Student(object):
pass
def set_age(self, age):
self.age = age
from types import MethodType
s = Student()
s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
s.set_age(25) # 调用实例方法
- 给所有实例都绑定方法,可以给class直接绑定该方法:
Student.set_score = set_score
- 使用__slots__, 限制实例的属性,只允许对Student实例添加
name
和age
属性
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
s = Student()
s.name = 'Michael'
s.age = 25
s.score = 99 # 绑定score属性失败,其不在__slots__中
- 使用
__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的 - 除非在子类中也定义
__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
(2)使用@property
- 在类中,可以通过
get_score()
和set_score()
方法来对score属性的读写做限制(例如score必须是int,在0-100之间) - 但是,
get_score()
和set_score()
的调用方法又略显复杂,没有直接用属性这么直接简单。 - Python内置的
@property
装饰器就是负责把一个方法变成属性调用的:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer')
if value < 0 or value > 100:
raise ValueError('score must between 0 - 100')
self._score = value
- 把一个getter方法变成属性,只需要加上
@property
就可以了,此时,@property
本身又创建了另一个装饰器@score.setter
,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作。 - 注意到这个神奇的
@property
,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。 - 要特别注意:属性的方法名不要和实例变量重名。例如,以下的代码是错误的:
class Student(object):
# 方法名称和实例变量均为birth:
@property
def birth(self):
return self.birth
- 这是因为调用
s.birth
时,首先转换为方法调用,在执行return self.birth
时,又视为访问self
的属性,于是又转换为方法调用,造成无限递归,最终导致栈溢出报错RecursionError
。
(3)多继承
- 一个类可以继承自多个父类,这个类就可以拥有它的所有父类的功能
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
- 在设计类的继承关系时,通常,主线都是单一继承下来的,例如,
Ostrich
继承自Bird
。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich
除了继承自Bird
外,再同时继承Runnable
。这种设计通常称之为MixIn。 - 为了更好地看出继承关系,我们把
Runnable
和Flyable
改为RunnableMixIn
和FlyableMixIn
。类似的,你还可以定义出肉食动物CarnivorousMixIn
和植食动物HerbivoresMixIn
,让某个动物同时拥有好几个MixIn
(4)定制类
-
__slot__()
,规定只能给类定义特定的属性 -
__len__()
,能让class作用于len()
函数 -
__str__()
,类似于Java的toString()方法,用于打印一个实例:class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name: %s)' % self.name print(Student('Michael')) # Student object (name: Michael)
- 但是细心的朋友会发现直接敲变量不用
print
,打印出来的实例还是不好看:
>>> s = Student('Michael') >>> s <__main__.Student object at 0x109afb310>
- 这是因为直接显示变量调用的不是
__str__()
,而是__repr__()
,两者的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务的。 - 解决办法是再定义一个
__repr__()
。但是通常__str__()
和__repr__()
代码都是一样的,所以,有个偷懒的写法:
class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name=%s)' % self.name __repr__ = __str__
- 但是细心的朋友会发现直接敲变量不用
-
__iter__()
,如果一个类想被用于for ... in
循环,类似list或tuple那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()
方法拿到循环的下一个值,直到遇到StopIteration
错误时退出循环。class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化两个计数器a,b def __iter__(self): return self # 实例本身就是迭代对象,故返回自己 def __next__(self): self.a, self.b = self.b, self.a + self.b # 计算下一个值 if self.a > 100000: # 退出循环的条件 raise StopIteration() return self.a # 返回下一个值 for n in Fib(): print(n) # Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素Fib()[5]会报错
-
__getitem__()
,要表现得像list那样按照下标取出元素,需要实现__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() f[0] # 1 f[1] # 1
- 此时,要使用切片
Fib()[0:5]
依然不行,因为__getitem__(self, n)
中的n既可以是int,也可以是切片对象slice,因此要做判断:
class Fib(object): def __getitem__(self, n): if isinstance(n, int): # n是索引 a, b = 1, 1 for x in range(n): a, b = b, a + b return a if isinstance(n, slice): # n是切片 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() f[0:5]
__getattr__()
,用于处理调用类中不存在的方法或者属性:
class Student(object): def __init__(self): self.name = 'Michael' def __getattr__(self, attr): if attr=='score': # 调用类中不存在的'score'属性时 return 99 if attr=='age': # 调用类中不存在的'age()'方法时 return lambda: 25 raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
__call__()
,任何类,只需要定义一个__call__()
方法,就可以直接对实例进行调用。
class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name) # 调用方式如下: >>> s = Student('Michael') >>> s() # self参数不要传入 My name is Michael. - 通过`callable()`函数,我们就可以判断一个对象是否是“可调用”对象。 >>> callable(Student()) True >>> callable(max) True >>> callable([1, 2, 3]) False >>> callable(None) False >>> callable('str') False
- 此时,要使用切片
(5)使用枚举类
- 为枚举类型定义一个class类型,每个常量都是class的一个唯一实例。Python提供了
Enum
类来实现这个功能
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
- 使用:
Month.Jan # <Month.Jan: 1> 默认从1开始
- 枚举所有成员:
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
- 如果需要更精确地控制枚举类型,可以从
Enum
派生出自定义类
from enum import Enum, unique
@unique # `@unique`装饰器可以帮助我们检查保证没有重复值。
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
# 访问:
Weekday.Mon
Weekday['Mon']
Weekday.Mon.value # 1
Weekday(1) # Weekday.Mon
(6)使用元类
type()
函数可以查看一个类型或变量的类型,Hello
是一个class,它的类型就是type
,而h
是一个实例,它的类型就是classHello
。
>>> from hello import Hello
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class 'hello.Hello'>
- class的定义是运行时动态创建的,而创建class的方法就是使用
type()
函数。 type()
函数既可以返回一个对象的类型,又可以创建出新的类型:
>>> def fn(self, name='world'): # 先定义函数
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>
-
要创建一个class对象,
type()
函数依次传入3个参数:
1. class的名称;
2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;如果只继承一个父类,记得要加’,
,例如(object,)
3. class的方法名称与函数绑定,这里我们把函数fn
绑定到方法名hello
上。 -
metaclass: 要控制类的创建行为,还可以使用metaclass。
-
步骤:先定义metaclass,就可以创建类,最后创建实例。
-
metaclass可以向类中动态添加函数
class ListMetaclass(type): # metaclass是累的模板,所以必须从type类型派生, 类名通常习惯以Metaclass结尾
def __new__(cls, name, bases, attrs): # 当前准备创建的类的对象,类的名字,类继承的父类集合,累的方法集合
attrs['add'] = lambda self, value: self.append(value) # 向类中添加self.add()方法
return type.__new__(cls, name, bases, attrs)
# 定义类时,徐传入关键字参数metaclass
class MyList(list, metaclass=ListMetaclass):
pass
L = MyList()
L.add(1) # [1] 原list 是没有 add()方法的,自定义的MyList类中添加了add()方法
- 使用metaclass可以实现对象-关系映射(ORM)就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句
8. 错误、调试、测试
(1)错误处理:
-
try-except-finally
:try: do_something except ZeroDivisionError as e: print('except:', e) except ValueError as e: print('except:', e) except Exception as e: print('Error:', e) else: print('no error!') # 配合except使用,如果没捕获异常,就执行else。可以没有else finally: print('finally...') # 必定执行,可以没有finally
-
记录错误:logging
- Python内置的
logging
模块可以非常容易地记录错误信息
# err_logging.py import logging 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')
- 同样是出错,但程序打印完错误信息后会继续执行,并正常退出:
$ python3 err_logging.py ERROR:root:division by zero Traceback (most recent call last): File "err_logging.py", line 13, in main bar('0') File "err_logging.py", line 9, in bar return foo(s) * 2 File "err_logging.py", line 6, in foo return 10 / int(s) ZeroDivisionError: division by zero END
- Python内置的
-
抛出错误:
- 自定义错误:
class FooError(ValueError): pass
- 抛出:
raise FooError('invalid value')
- 捕获后,不处理,继续向上抛出,直接用 raise,抛出当前错误
# err_reraise.py def foo(s): n = int(s) if n==0: raise ValueError('invalid value: %s' % s) return 10 / n def bar(): try: foo('0') except ValueError as e: print('ValueError!') raise bar()
(2)调试
- 断言:
assert 表达式, 字符串信息 # 如果表达式是True,正常执行, 如果为False,抛出AssertionError,并打印字符串信息
- 启动Python解释器时可以用
-O
参数来关闭assert
(断言的开关“-O”是英文大写字母O,不是数字0。) 关闭后,你可以把所有的assert
语句当成pass
来看。
- logging:
- 和
assert
比,logging
不会抛出错误,而且可以输出到文件
import logging logging.basicConfig(level=logging.INFO) # 设置输出级别 s = '0' n = int(s) logging.info('n = %d' % n) print(10 / n) # 输出为: $ python err.py INFO:root:n = 0 Traceback (most recent call last): File "err.py", line 8, in <module> print(10 / n) ZeroDivisionError: division by zero
logging
允许你指定记录信息的级别,有debug
,info
,warning
,error
等几个级别,当我们指定level=INFO
时,logging.debug
就不起作用了。同理,指定level=WARNING
后,debug
和info
就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
- 和
- pdb:启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态
$ python -m pdb err.py
(3)单元测试
-
编写单元测试时,我们需要编写一个测试类,从
unittest.TestCase
继承import unittest from mydict import Dict class TestDict(unittest.TestCase): def test_init(self): d = Dict(a=1, b='test') self.assertEqual(d.a, 1) self.assertEqual(d.b, 'test') self.assertTrue(isinstance(d, dict)) def test_key(self): d = Dict() d['key'] = 'value' self.assertEqual(d.key, 'value') def test_attr(self): d = Dict() d.key = 'value' self.assertTrue('key' in d) self.assertEqual(d['key'], 'value') def test_keyerror(self): d = Dict() with self.assertRaises(KeyError): value = d['empty'] def test_attrerror(self): d = Dict() with self.assertRaises(AttributeError): # 期待抛出指定类型的Error,比如通过`d['empty']`访问不存在的key时,断言会抛出`KeyError` value = d.empty
- 以
test
开头的方法就是测试方法,不以test
开头的方法不被认为是测试方法,测试的时候不会被执行 self.assertEqual()
self.assertTrue()
with self.assertRaises(AttributeError):
检查代码执行错误时,是否抛出了指定的异常- 运行单元测试:
- 方法一:
if __name__ == '__main__': unittest.main()
- 方法二:
$ python -m unittest mydict_test
- 以
-
setUp()
与tearDown()
- 相当于Java的JUnit中的
before()
,after()
,可以在单元测试中编写这两个特殊的setUp()
和tearDown()
方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。
- 相当于Java的JUnit中的
(4)文档测试
- 用于测试写在注释中的例子
- 对于如下的文档注释,要求Example:之下的例子严格按照Python交互式命令行的格式书写
def abs(n): ''' Function to get absolute value of number. Example: >>> abs(1) 1 >>> abs(-1) 1 >>> abs(0) 0 ''' return n if n >= 0 else (-n)
- doctest,如果所有例子完全通过测试,则没有任何输出,如果不通过,输出测试的错误信息
if __name__=='__main__': import doctest doctest.testmod()
9. IO编程
(1)文件读写
-
读文件:
f = open('test.txt', 'r')
文件名,标识符
f.close()
- 如果文件名不存在,会抛出
IOError
的错误,因此要用try-finally块进行包裹:
try: f = open('test.txt', 'r') f.read() finally: if f: f.close()
f.read()
,一次把文件中的所有内容读入到内存;如果内容较多,超出内存大小,则会报错f.read(size)
,一次读取size大小的内容,如果不能确定文件大小,反复调用read(size)
比较保险f.readline()
,一次读取文件中的一行f.readlines()
,一次读取所有内容并按行返回list
with open('test.txt', 'r') as f: print(f.read())
Python引入了with
语句来自动帮我们调用close()
方法
-
读取二进制文件:
f.open('test.jpg', 'rb') # 十六进制表示的字节
要读取二进制文件,比如图片、视频等等,用'rb'
模式打开文件即可
-
指定字符编码格式:
encoding
f = open('test.txt', 'r', encoding='gbk')
- 如遇到非法编码的字符,会抛出异常,
open()
可接收参数errors
指定处理方式:f = open('test.txt', 'r', encoding='gbk', errors='ignore')
-
写文件:
w
或wb
(二进制)f = open('test.txt', 'w') f.write('Hello, world!') f.close()
- 或用
with open('test.txt', 'w'):
自动关闭文件,无需自己写f.close()
- 以
w
或wb
模式写入文件时,如果文件已经存在,会覆盖掉原有内容 - 以
a
模式写入文件,可实现追加效果
- 或用
(2)StringIO、BytesIO
-
StringIO是在内存中读写str
- 写入:
from io import StringIO f = StringIO() f.write('hello') f.write(' ') f.write('world!') print(f.getvalue()) # 获得写入后的str
- 读入:
from io import StringIO f = StringIO('Hello!\nHi!\nGoodbye!') # 用一个str初始化StringIO while True: s = f.readline() # 像读文件一样操作 if s == '': break print(s.strip())
-
BytesIO:StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO
- 写入:
from io import BytesIO f = BytesIO() f.write('中文'.encode('utf-8')) print(f.getvalue())
- 读入:
from io import BytesIO f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87') f.read() # 像读文件一样操作
(3)操作文件和目录
- Python内置的
os
模块可以直接调用操作系统提供的接口函数
import os
os.name # 操作系统类型
os.uname() # 获取详细的系统信息
os.environ # 操作系统中定义的环境变量,全部保存在os.environ中
os.environ.get('PATH') # 获取环境变量PATH的值
os.envirion.get('x', 'default') # 获取环境变量'x'的值,如果不存在,返回默认值'default'
- 操作文件和目录的函数一部分放在
os
模块中,一部分放在os.path
模块中
os.path.abspath('.') # 查看当前目录的绝对路径
os.path.join('/Users/michael', 'testdir') # '/Users/michael/testdir,把两个路径合成一个时,不要直接拼字符串,而要通过`os.path.join()`函数,这样可以正确处理不同操作系统的路径分隔符
# 拆分路径时,也不要直接拆分字符串
os.path.split('/Users/michael/testdir/file.txt') # ('/Users/michael/testdir', 'file.txt')
#直接获得文件的扩展名
os.path.splitext('/path/to/file.txt') # ('/path/to/file', '.txt')
os.mkdir('/Users/michael/testdir') # 创建目录
os.rmdir('Users/michael/testdir') # 删除目录
os.rename('test.txt', 'test.py') # 重名名文件
os.remove('test.py') # 删除文件
# os不提供文件复制的函数,shutil模块提供了copyfile()函数可用于文件复制
# 列出当前目录下的所有目录,即过滤掉文件
[x for x in os.listdir('.') if os.path.isdir(x)]
#列出所有.py文件
[x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
(4)序列化
- 序列化: 把变量从内存中编程可存储或传输的过程,在Python中叫
pickling
,序列化后就可把内容写入磁盘或者通过网络传输到别的机器 - 反序列化: 把变量内容从序列化的对象重新读入到内存,
unpickling
import pickle
d = dict(name='Bob', age=20, score=88)
pickle.dumps(d) # 把对象序列化为bytes,注意是dumps,有s
# b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'
f = open('dump.txt', 'wb')
pickle.dump(d, f) # 把对象序列化后直接写入f
f.close()
f = open('dump.txt', 'rb')
d = pickle.load(f) # 反序列化出对象
f.close()
- JSON:如果在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,序列化为JSON是比较好的选择
import json
d = dict(name='Bob', age=20, score=88)
json.dumps(d) # 序列化为JSON对象,返回str:'{"age":20, "score":88, "name":"Bob"}'
# 反序列化
json_str = '{"age":20, "score":88, "name":"Bob"}'
json.loads(json_str) # 注意是loads , 有s
- JSON序列化自定义的class时,需指定default参数,即如何将class转换为dict:
class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
# 指定序列化的方式
def student2dict(student):
return dict(name=student.name, age=student.age, score=student.score)
# 指定反序列化的方式
def dict2student(dct):
return Student(dct['name'],dct['age'],dct['score'])
# 序列化
s = Student('Bob',22,88)
json_str = json.dumps(s, default=student2dict)
# 更加简化的序列化:
json.dumps(s, default=lambda obj: obj.__dict__)
# 反序列化
json.loads(json_str, object_hook=dict2student)
10. 进程、线程
(1)多进程
- 获取父进程的ID:
os.getppid()
- 获取当前进程的ID:
os.getpid()
- 普通的函数调用,调用一次,返回一次,但是
fork()
调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。 - 子进程永远返回
0
,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()
就可以拿到父进程的ID。import os print('Process (%s) start...' % os.getpid()) # Only works on Unix/Linux/Mac: 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))
运行结果如下:
Process (876) start... I (876) just created a child process (877). I am child process (877) and my parent is 876.
-
multiprocessing
multiprocessing
模块提供了一个Process
类来代表一个进程对象from multiprocessing import Process import os # 子进程要执行的代码 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',)) print('Child process will start.') p.start() p.join() print('Child process end.')
执行结果如下:
Parent process 928. Child process will start. Run child process test (929)... Process end.
- 创建子进程时,只需要传入一个执行函数和函数的参数,创建一个
Process
实例,用start()
方法启动,这样创建进程比fork()
还要简单。 join()
方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
-
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)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')
执行结果如下:
Parent process 669. Waiting for all subprocesses done... Run task 0 (671)... Run task 1 (672)... Run task 2 (673)... Run task 3 (674)... Task 2 runs 0.14 seconds. Run task 4 (673)... Task 1 runs 0.27 seconds. Task 3 runs 0.86 seconds. Task 0 runs 1.41 seconds. Task 4 runs 1.91 seconds. All subprocesses done.
- 对Pool
对象调用join()
方法会等待所有子进程执行完毕,调用join()
之前必须先调用close()
,调用close()
之后就不能继续添加新的Process
了。
- 请注意输出的结果,task 0
,1
,2
,3
是立刻执行的,而task 4
要等待前面某个task完成后才执行,这是因为Pool
的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool
有意设计的限制,并不是操作系统的限制。如果改成:
p = Pool(5)
就可以同时跑5个进程。
(看到多进程的子进程)
(2)多线程
(3)ThreadLocal
11. 正则表达式
import re
re.match(正则表达式, 待匹配的字符串) # 如果匹配成功,则返回一个Match对象,否则返回None
(1)基础:
\d
匹配一个数字\w
匹配一个字母或数字\s
匹配一个空格.
匹配任意一个字符*
匹配任意个字符**(包括0个)**+
匹配至少1个字符?
匹配0个或1个字符{n}
匹配n个字符{n,m}
匹配n-m个字符
(2)进阶:
[ ]
表示范围:[0-9a-zA-Z\_]
匹配一个数字、字母或者下划线[0-9a-zA-Z\_]+
匹配至少一个数字、字母或者下划线
(A|B)
: A或者B:(P|p)ython
匹配Python
或者python
^
表示行的开头^\d
必须以数字开头
$
表示行的结束\d$
必须以数字结尾
py
可以匹配到python
,如果要精确匹配到py
,则要用^py$
- 强烈建议使用Python的
r
前缀,就不用考虑转义的问题了:s = r'ABC\-001' #对应正则表达式 'ABC\-001'
(3)切分字符串
'a b c'.split(' ') # ['a', 'b', ' ', ' ', 'c']
没办法去除空格re.split(r'\s+', 'a b c') # ['a', 'b', 'c']
去除所有空格re.split(r'[\s\,\;]+', 'a,b;;c d') # 以空格 逗号 分号分割
(4)分组
()
用括号提取分组:m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
m.group(0) # 原始字符串 010-12345
m.group(1) # 提取第一个括号中的内容——区号 010
m.group(2) # 提取第二个括号中的内容—— 12345
m.groups() # 返回所有组组成的tuple—— ('010', '12345'))
(5)贪婪匹配
- 默认为贪婪匹配
- 末尾加
?
,改为非贪婪匹配
(6)预编译
- 如果一个正则表达式要重复使用几千次,可以预编译该表达式,提高效率:
import re re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$') re_telephone.match('010-12345').groups() re_telephone.match('010-8086').groups()