基础
数据类型和变量
整数:
对于很大的数,可以使用_
来分割,10_000_000_000
和10000000000
是一样的
python整数没有大小限制,浮点数也没有,超出一定范围直接直接表示为inf
字符串:
-
使用
r''
表示字符串默认不转义 -
使用
'''...'''
的格式表示多行内容
常量:
用全部大写的变量来表示常量(本质还是变量,Python没有)
空值:
python中 None
表示空值
编码
通用的字符编码工作方式(utf-8更节省空间,utf-8英文字符是1字节,unicode通常是2字节)
浏览网页的时候
字符串
编码
python字符串类型是 str
使用Unicode编码
-
ord()
获取字符的整数表示 -
chr()
将编码转为对应的字符
字符串在内存中以Unicode表示,如果要在网上传输,需要转为字节格式,bytes类型在Python中用 b''
表示
b'ABC'
和'ABC'
显示内容和前者一样,但bytes类型的每个字符只占一个字节
以Unicode表示的str用encode('编码')
来编码
1
2
3
4
5
6
7
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
>>>
解码
解码使用 decode('编码')
len()函数
如果里面的参数是字符串,计算的是字符数,如果里面的参数是字节类型的,就是计算的是字节数
1
2
3
4
>>> len('中文abc'.encode('utf-8'))
9
>>> len('中文abc')
5
编辑器编码问题
1
2
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
第一行注释是为了告诉linux系统 这个文件是Python可执行程序,windows会忽略
第二行注释告诉解释器按utf-8读取源代码,防止中文输出有问题
但还需要确保文本编辑器也在使用utf-8编码
格式化输出
%d %s 字符串 %f 浮点数 %x 16进制数
整数和浮点数可以指定是否补0和位数
主要三种 普通的 format 和 fstring
1
2
3
4
5
6
7
8
9
10
>>> print('%d %s'%(123,'哈哈'))
123 哈哈
>>> print('%d'%123)
123
>>> print('{0:03d} {1:.2f}'.format(1,3.1415))
001 3.14
>>> a=1
>>> b=3.1415
>>> print(f'{a:03d} {b:.2f}')
001 3.14
list
初始化用[]
-
a[-1]
表示倒数第一个元素 -
a.append(...)
末尾追加 -
a.insert(1,'123')
索引1位置插入123 -
a.pop(i)
删除指定索引位置的元素,如果不指定i,即末尾弹出
tuple
元组,一旦初始化不能修改 更安全
a=()
空元组
a=(1)
a会被赋值为1a=(1,)
加逗号消除歧义
dict
其他语言中的map
d['haha']
如果key不存在会报错,d.get('haha',1)
表示取key为haha的,如果不存在,使用指定的value 1,后面的1可省
dict通过计算key的哈希只来得到存放的内存位置,为了保证key的正确性,key应该是不可变对象,比如list可变,就不可以作为key
set
集合 使用{}
表示
函数
空函数
如果想定义空函数,可以用pass语句
pass语句可以用来作为占位符,也可以写在一般的语句中,比如:
if age >= 18:
pass
先让代码运行起来
类型检查
数据的类型检查 可以使用 isinstance 来实现
比如:
1
2
3
4
5
6
7
def my_abs(x):
if not isinstance(x,(int,float)):
raise TypeError('wrong type')
if x>= 0:
return x
else:
return -x
返回多值
python返回多个值是返回一个tuple,本质上也是一个单一值
参数
默认参数
默认参数不能是可变的
比如:
1
2
3
def add_end(L=[]):
L.append('END')
return L
正常调用,不会出错,但是多次默认参数调用会出错
1
2
3
4
5
6
>>> add_end()
['END']
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']
因为定义函数时,该默认值就会被定义出来,应改成
1
2
3
4
5
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
可变参数
传入的参数个数是可变的,初步的想法是,定义个参数,传入的参数是list或者tuple,但是这样需要组装,即加上()
或[]
;改进方法是,定义函数时,在参数前加上*
来修饰,即可传入任意个参数,包括0个,不用再用()
或[]
修饰。如果此时要输入的是list或者tuple,在调用函数时,变量前加上*
即可。
关键字参数
**
修饰参数名,*
本质上是调用时自动组成一个tuple,而关键字参数本质上是将任意个含参数名的参数自动组装成一个dict,关键字参数不是必选的,同可变参数类似,如果调用时输入的参数是dict,在变量名前加上**
即可
命名关键字
1
2
def person(name, age, *, city, job):
print(name, age, city, job)
即用特殊分隔符*
来隔开,后面的参数会被视为关键字参数,如果已经有可变参数了,就不需要*
分割了
1
2
def person(name, age, *args, city, job):
print(name, age, args, city, job)
后面的可变参数可以添加缺省值,从而简化调用
参数定义顺序:必选参数、默认参数、可变参数、命名关键字参数和关键字参数
高级特性
切片
切片 slice
list dict str都可以进行切片
迭代
dict的迭代:
1
>>> d = {'a': 1, 'b': 2, 'c': 3}
对key迭代 对值迭 对键值迭代
1
2
3
>>> for key in d:
... print(key)
...
for value in d.values()
,for k, v in d.items()
判断是否可迭代
1 2 >>> from collections.abc import Iterable >>> isinstance('abc', Iterable)
如果需要索引,可以使用 enumerate 函数
1
2
3
4
5
6
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
列表生成式
list comprehensions
几个简单示例:
1
2
3
4
5
6
>>> [x*x for x in range(1,11) if x%2==0]
[4, 16, 36, 64, 100]
>>> [x+y for x in 'ABC' for y in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
>>> [x if x%2==0 else -x for x in range(1,6)]
[-1, 2, -3, 4, -5]
for后面的if是过滤条件不能带else,for前面的if是表达式,必须带上else来确定x
生成器 generator
在python中,一边循环一边计算的机制,称为生成器。
与列表生成式类似,将[]
改成()
即可,通过 next()
获取下一个返回值,但一般不适用next方法,而是通过for循环,比如:
1
2
3
4
5
6
7
8
9
>>> g=(x*x for x in range(1,6))
>>> for n in g:
... print(n)
...
1
4
9
16
25
关键字 yield
以斐波那契数列为例,
1
2
3
4
5
6
7
8
9
def fib(x):
n,a,b=0,0,1
while n<x:
print(b)
a,b=b,a+b
n+=1
return 'done'
print(fib(5))
输出是 1 1 2 3 5 8 done,如果将print(b)
改成yield b
,fib函数就变成了generator函数(函数定义中包含 yield 关键字)
普通函数遇到return语句 或者 最后一行函数语句就返回,而generator函数,每次调用next()时执行,遇到yield语句返回,再次执行时,会从返回的yield语句处继续执行
for x in fib(5)
可以循环得到1 1 2 3 5 8 但无法获得return的done值,需要捕获 StopIteration
错误,返回值包含在该错误的value
中
1
2
3
4
5
6
7
8
g=fib(5)
while True:
try:
x=next(g)
print(x)
except StopIteration as e:
print(e.value)
break
即可获取返回值了
迭代器 iterator
可以直接作用于 for 循环的对象统称为可迭代对象 Iterable
如果可以被 next()
函数调用并不断返回下一个值的对象称为迭代器 Iterator
list dict str 等是 Iterable
但不是 Iterator
,但可以通过 iter()
函数获得一个Iterator
对象
python中的for循环本质上就是通过不断调用
next()
函数实现的
1
2
for x in [1, 2, 3, 4, 5]:
pass
本质为:
1
2
3
4
5
6
7
8
9
10
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
函数式编程
纯粹的函数式编程语言编写的函数没有变量,即输入是确定的,输出是确定的。(python函数允许变量,不是纯函数式编程语言)
函数式编程的特点之一就是 允许把函数本身作为参数传入另一个函数,还允许返回一个函数。
高阶函数
函数名本质就是一个变量,该变量可以指向函数,而函数的参数可以接收变量,所以函数也可以接收一个函数作为参数,这种函数称之为高阶函数,比如:
1
2
3
4
def add(a,b,f):
return f(a)+f(b)
add(-1,2,abs)
map/reduce
python 内建了 map() 和 reduce() 函数
map()的两个参数 一个是函数,一个是 Iterable 对象,map将传入的函数一次作用到序列的每个元素,并把结果作为新的Iterator对象返回。
1
2
3
4
5
def f(x):
return x*x
r=map(f,list(range(1,5)))
list(r)
输出[1,4,9,16]
map返回的结果是iterator 是 惰性序列,需要list()函数将整个序列计算出来返回
reduce()的用法,同样是两个参数,reduce的效果相当于把结果和下一个元素进行某种运算,比如:
1
2
3
4
5
6
from functools import reduce
def reduce_ff(x,y):
return x+y
reduce(reduce_ff,list(range(1,5)))
输出10
类似的 还可以写一些 类似字符转数字的功能:
1
2
3
4
5
6
7
8
9
def char2num(c):
digits={'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,}
return digits[c]
def ff(x,y):
return x*10+y
# reduce(ff,map(char2num,'12345'))
reduce(lambda x,y:x*10+y,map(char2num,'12345'))
filter
与map类似,也是接受一个函数和一个序列,根据函数的返回值是True还是False来选择保留还是丢弃
简单举个输出素数的例子:
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
# 输出素数 运用埃氏筛法 从2开始 2的倍数不是素数 所以可以从3开始构造奇数序列
# 再过滤,3的倍数不是素数...
def get_list_num():
n=3
while True:
yield(n)
n+=2
def prime():
yield 2
it = get_list_num()
while True:
n=next(it)
yield n
it=filter(lambda x:x%n>0,it)
i=1
ls=[]
for the_prime in prime():
if(i<20):
ls.append(the_prime)
i+=1
else:
break
ls
输出:[2, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37]
sorted
sorted() 也是高阶函数,前一个接收序列,第二个参数key函数进行自定义排序,还可以添加第三个参数 reverse
举个简单例子
1
2
3
4
5
6
7
8
# sorted 排序
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 66)]
# 按分数降序
L1=sorted(L,key=lambda x:x[1],reverse=True)
print(L1)
# 按名字升序
sorted(L,key=lambda x:str(x[0]).lower())
返回函数
高阶函数除了可以接收函数作为参数外,还可以将函数作为返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 返回函数测试
def createCounter():
i=0
def counter():
nonlocal i
i=i+1
return i
return counter
# 测试:
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
print('测试通过!')
else:
print('测试失败!')
-
nonlocal 声明了 counter函数中的变量i不是临时变量,而是createCounter的i
-
counterA
是一个函数,通过counterA()
进行函数调用,第一次调用时i一开始时0,调用counter 得到的是1,第二次调用时,因为返回的值是一个函数(该函数引用了外部函数createCounter的变量,相关参数和变量都会保存在返回的函数中,这种程序结构称为闭包Closure),而i已经变成了1,此时再调用,得到的结果就是1+1为2 -
counterB得到的是另一个函数,所以之前counterA的变化不会对其产生影响
装饰器
函数对象有一个__name__
属性来获取函数的名字
如果有一个函数
1
2
def now():
print(time.time())
如果需要加强该函数的功能,比如调用前打印当前调用的函数名,但now函数不进行修改,则在代码运行期间动态增加功能的方式 称为 装饰器Decorator。
装饰器本质是一个返回函数的高阶函数
1
2
3
4
5
6
7
8
9
10
11
12
13
import time
def log(func):
def wrapper(*args,**kw):
print('call %s()'% func.__name__ )
return func(*args,**kw)
return wrapper
@log
def now():
print(time.time())
now()
-
log 函数返回 wrapper函数,wrapper()函数可以接收一切参数
-
使用
@
修饰符,本质是执行了一句now = log(now)
,本来now函数还在,但now这个变量指向了wrapper这个新函数
如果修饰器本身要输入参数,会更加复杂:
1
2
3
4
5
6
7
8
9
10
11
def log(text):
def log2(func):
def wrapper(*args,**kw):
print('%s %s()'%(text, func.__name__))
return func(*args,**kw)
return wrapper
return log2
@log('execute')
def now():
print(time.time())
输出 execute now() 1664445736.5886693
三层的嵌套效果本质是 now = log('execute')(now)
但还有一个问题,此时now函数的name就变成了wrapper,解决方式,用@functools.wraps(func)
来修饰:
1
2
3
4
5
6
7
8
def log(text):
def log2(func):
@functools.wraps(func)
def wrapper(*args,**kw):
print('%s %s()'%(text, func.__name__))
return func(*args,**kw)
return wrapper
return log2
偏函数
使用functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
具体暂略
模块
模块module:避免函数名和变量名冲突,提高代码可维护性,可重用性。abc.py
abc就称之为模块,模块名可能冲突,又引入了包package,比如mycompany文件夹下有个abc.py 则模块名就成了mycompany.abc
每个包目录下应该存在一个
__init__.py
,python才会将其当作一个包,而不是普通目录,该文件可以是空文件,该文件本身就一个模块,而模块名就是 mycompany
使用模块
注意:
-
__name__ __author__
这种称为特殊变量,可以被直接引用,但都有特殊用途 -
_xxx / __xxx
下划线开头的变量或函数一般被称之为private,不应该被引用(但还是可以引用,python没有限制)
面向对象编程
类和实例
class Student(object):
所有类最终都会继承object
类,python3可以不写object,会默认继承
-
可以自由地给实例变量绑定属性
1 2 3
xxx=Student() xxx.name='123' print(xxx.name)
一般的做法是,将必须绑定的属性填写进去 定义一个
__init__
方法(初始化时必须带上相关参数)
访问限制
内部属性不被房补访问,可以再属性的名称前加上两个下划线 __
如果外界要获取或者修改,一般增加 get_name set_name 这样的方法,
如果 强制
xxx.__name
赋值,虽然可以调用,但本身并不是类中的属性,实际上private限制本质是python把类中的私有属性名自动改成了类名加私有属性名_Student__name
,因此赋值__name
本质上是给实例增加了一个属性而已
继承和多态
简单例子:定义一个父类Animal
,子类 Cat
Dog
,每个类都有一个run
函数(子类重写了父类的run) ,一个接收Animal
类型的函数,该函数执行该实例的run函数,此时不管传入cat还是dog类型的,都会正常运行,这就是多态。
开闭原则:
-
对扩展开放(比如再来个
Turtoise
子类) -
对修改封闭(不需要修改依赖Animal类型的函数)
静态语言(如java),传入的对象必须是Animal类型或者它的子类,对于动态语言(如python),传入的对象没有那么严格,只要该对象有一个
run
方法即可。
获取对象信息
判断类型 函数 或者 类 都可以使用 tpye()
函数
对于函数:
1
2
3
4
5
6
7
8
9
10
11
12
>>> 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()
,可以判断当前实例是否是某一个类或者该类的父类等
实例属性和类属性
实例属性用self.xxx来绑定,类属性直接在class中定义,所有实例都能访问到
面向对象高级编程
使用__slots__
类中使用__slots__=('name','age')
用tuple定义了允许绑定的属性名称,绑定其他属性名会报错
仅对当前类实例起作用,对继承的子类是不起作用的,但如果也定义了
__slots__
,则还会继承父类的__slots__
使用@property
对于类的方法,python内置的@property装饰器将方法变成属性调用,比如说score属性,该装饰器本身又创建了另一个@score.setter
装饰器,property相当于是getter。
属性的方法名不能和实例变量重名
多重继承
python允许多重设计
MixIn设计:不需要考虑复杂的层次关系,只需要考虑单一继承链,再添加继承额外的功能类,例如:男人类,单一继承链上他属于人类,如果还要为这个男人类添加其它功能,比如抽烟类,那就用MixIn的写法加进来,但是无需你考虑抽烟类和人类有什么关系。
class Man(Person,ChouyanMixIn)
定制类
__str__
和__repr__
print类,定制化输出,需定义 __str__
函数,但此后 如果直接敲类变量,还是和之前一样,因为不会调用__str__
而是调用__repr__
解决方法是再定义一个,通常 两者函数代码是一样的,偷懒方法:
1
2
3
4
5
6
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 ...
需定义__iter__
(该方法返回一个迭代对象)和__next__
(获取迭代对象的值)
__getitem__
取任意下标元素
__getattr__
调用不存在的属性或方法时,如果不存在会报错,当调用不存在的属性时,比如score
,Python解释器会试图调用__getattr__(self, 'score')
来尝试获得属性,这样,我们就有机会返回score
的值,也可以尝试返回一个函数:
1
2
3
4
5
6
7
8
9
10
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
if attr=='age':
return lambda: 25
getattr 默认返回时None,如果只想响应特定的几个属性,要抛出,比如:
1
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
链式调用
1
2
3
4
5
6
7
8
9
10
11
12
class Chain(object):
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__
1
2
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'
__call__
调用实例本身,通过callable()
函数来判断一个对象是否是可调用对象
使用枚举类
使用常量时,常使用大写,但是其仍然是变量
使用枚举类型 enum.Enum
1
2
3
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
使用 Month.Jan
或者 Month['Jan']
来引用常量,.value
来获取常量值,默认从0开始,如果需要更精确地控制枚举类型,可从Enum派生出自定义类
1
2
3
4
5
6
7
8
9
10
11
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
unique 确保了无重复值
!使用元类
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
比如说 定义一个 Hello 类,定义在hello.py模块中:
1
2
3
class Hello(object):
def hello(self, name='world'):
print('Hello, %s.' % name)
当python解释器加载hello.py模块时,会依次执行该模块的所有语句,结果就是动态创建了一个Hello的class对象
type()
type函数可以查看一个类型或变量的类型,
1
2
3
4
from hello import Hello
h=Hello()
print(type(Hello))
print(type(h))
输出
1
2
<class 'type'>
<class 'hello.Hello'>
class类本身类型就是 type,而h是一个实例,所以类型是class Hello
。说Hello类是运行时创建的,而创建的方法就是 type,该函数既可以返回类型,也可以创建新的类型,比如 创建Hello2类
1
2
3
4
5
6
7
8
def func(self,name="world"):
print("hello,%s"%name)
Hello2 = type('Hello2',(object,),dict(hello=func))
h=Hello2()
h.hello()
print(type(Hello2))
print(type(h))
1
2
3
hello,world
<class 'type'>
<class '__main__.Hello2'>
type三个参数:
-
class名称
-
继承的父类集合(python支持多重继承,如果只有一个父类,别忘了tuple的单元数写法)
-
方法名称与函数绑定
python解释器遇到class定义时,就是调用type函数创建class
metaclass
type动态创建类,如果要控制类的创建行为,需要使用metaclass
略费劲….暂略
错误、调试和测试
错误处理
程序出错,返回错误码十分不便(正常结果和错误码混在一起,需要大量判断),并且出错还要一级级上报
try-except-finally
所有的错误类型都继承自BaseException
继承层级
使用except时,有父类的前提下,子类的不会再被捕获
跨越多层调用,即可以在合适的层次捕获错误即可
如果不捕获错误,python解释器也会打印出错误堆栈,但程序也终止了,可以使用logging模块,将错误信息记录在日志文件中,同时让程序继续运行
错误是一种class,捕获一个错误就是捕获一个该类的实例,可以只定义错误类,选择好继承关系,用 raise
语句抛出错误实例
调试
暴力点的 print() 所有可能出错的地方,但可能有很多垃圾信息
assert
使用assert断言,用法如下:
1
2
3
4
5
6
7
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
1
2
3
4
$ python err.py
Traceback (most recent call last):
...
AssertionError: n is zero!
可以通过类似 python -O err.py
来关闭assert,相当于被替换成了pass
logging
1
2
import logging
logging.basicConfig(level=logging.INFO)
debug info warning error
IDE
略
单元测试
-
编写单元测试,需要引入python自带的
unittest
模块 -
测试类需要继承
unittest.TestCase
-
测试方法必须以
test_
作为前缀 -
常用的是断言函数
self.assertEqual(v1,v2)
self.assertRaises(ErrorName)
-
运行单元测试:
-
python -m unittest xxx_test
推荐 因为可以批量运行 -
也可以写个main函数,当作正常脚本来运行
1 2
if __name__ == '__main__': unittest.main()
-
特殊方法:
- setUp() 每次测试方法前执行
- teardown() 每次测试方法后执行
-
文档测试
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
# mydict2.py
class Dict(dict):
'''
Simple dict but also support access as x.y style.
>>> d1 = Dict()
>>> d1['x'] = 100
>>> d1.x
100
>>> d1.y = 200
>>> d1['y']
200
>>> d2 = Dict(a=1, b=2, c='3')
>>> d2.c
'3'
>>> d2['empty']
Traceback (most recent call last):
...
KeyError: 'empty'
>>> d2.empty
Traceback (most recent call last):
...
AttributeError: 'Dict' object has no attribute 'empty'
'''
def __init__(self, **kw):
super(Dict, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
if __name__=='__main__':
import doctest
doctest.testmod()
doctest
会提取注释中的代码进行测试 测试异常可以用…来省略错误
IO编程
文件读写
操作系统打开文件对象(通称为文件描述符),程序通过操作系统提供的接口进行读写
读文件
读文本文件
1
2
3
4
5
6
7
8
9
f=open('./test.txt','r')
# 读取所有文件内容
f.read()
# 读取1行内容
f.readline()
# 读取所有内容,按行化为列表
f.readlines()
# 执行完读操作 文件描述符必须关闭 因为占资源
f.close()
读写文件 可能会产生 IOError
可以使用 try...finally
但是较为繁琐,通常是使用 with
语句,会自动调用f.close()
1
2
with open('./test.txt', 'r') as f:
print(f.read())
大文件直接 read() 读取所有内容,内存可能溢出 可以使用 read(size) 比较保险,配置文件,可以使用 readlines
file-like Object 有个read()方法的对象,除了file,还可以是 字节流 网络流 自定义流等,不要求特定类的继承。
- 二进制文件 r改成rb
- 非utf-8的文本文件,可以传入 encoding参数 指定编码
- errors 参数,收到编码错误后如何处理 简单处理是忽略
errors='ignore'
写文件
必须调用close 有时候内容不会直接写入磁盘,而是在内存中缓存起来,空闲时写入,只有调用close方法,才能保证没有写入的数据全部写入磁盘
标识符:
- w
- wb 二进制
- a 追加
通过反复调用write()来进行写入
StringIO和BytesIOP
StringIO和BytesIO是在内存中操作str和bytes的方法,使得和读写文件具有一致的接口。
操作文件和目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查看当前目录的绝对路径:
>>> os.path.abspath('.')
'/Users/michael'
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
>>> os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
# 然后创建一个目录:
>>> os.mkdir('/Users/michael/testdir')
# 删掉一个目录:
>>> os.rmdir('/Users/michael/testdir')
# 拆分路径
>>> 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.rename('test.txt', 'test.py')
# 删掉文件:
>>> os.remove('test.py')
序列化
变量是存在内存中的,如果需要存储或者传输, 需要进行序列化, 在python中被称为 pickling, 反过来 读到内存中去 称之为 反序列化 unpickling
python 自带的 pickle
1
2
3
import pickle
d = dict(name='Bob', age=20, score=88)
pickle.dumps(d)
1
b'\x80\x04\x95$\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x04name\x94\x8c\x03Bob\x94\x8c\x03age\x94K\x14\x8c\x05score\x94KXu.'
dumps 将对象序列化成 bytes, dump 则是将结果写入到file-like object中
1
2
with open('./test.txt', 'wb') as f:
pickle.dump(d,f)
通过loads方法将bytes反序列化出对象, 通过 load 从file-like Object中读出对象
1
2
3
with open('./test.txt', 'rb') as f:
d= pickle.load(f)
d
缺点: 不同语言之间不兼容 甚至 不同python版本都不兼容
JSON
JSON的兼容性更好,用法与pickle类似
dict可以直接序列化JSON的{}
如果是自定义的class对象, 需要定义一些函数, 来帮助序列化和反序列化
dumps
中 default
参数定义 序列化的方法
loads
中 object_hook
定义反序列化的方法
比如定义了一个 Student
类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
def student2dict(std):
return {
'name': std.name,
'age': std.age,
'score': std.score
}
def dict2student(d):
return Student(d['name'], d['age'], d['score'])
s = Student('Bob', 20, 88)
使用方法:
1
2
3
4
5
print(json.dumps(s, default=student2dict))
# 对于绝大部分类的示例来说,都会有一个 __dict__ 属性, 用来存储实例变量 可以将任意 class 实例变为 dict
print(json.dumps(s, default=lambda obj: obj.__dict__))
print(json.loads(json_str, object_hook=dict2student))
进程和线程
多任务分成
- 多进程
- 多线程
- 多进程+多线程
多进程
Unix/Linux系统 提供了 fork()
系统调用, 该函数调用一次,返回两次,操作系统把当前进程复制一份(子进程),分别在父子进程中返回,父进程返回的是子进程的ID,子进程中返回的是0,通过python的os模块可以进行该系统调用 os.fork()
windows 平台可以使用 multiprocessing模块 该模块提供了一个 Process类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.')
如果需要大量的子进程,可以使用进程池 Pool
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.')
Pool的默认大小是系统CPU的核数
子进程
有时候子进程是外部进程,需要使用subprocess模块
进程间通信
主要使用 Queue
和 Pipes
等实现
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
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('Get %s from queue.' % value)
if __name__=='__main__':
# 父进程创建Queue,并传给各个子进程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入:
pw.start()
# 启动子进程pr,读取:
pr.start()
# 等待pw结束:
pw.join()
# pr进程里是死循环,无法等待其结束,只能强行终止:
pr.terminate()
主要使用的是Queue的put和get方法
多线程
python 提供了 _thread
和 threading
,threading 是对 _thread 的封装,通常使用 threading模块即可
1
2
3
t = threading.Thread(target=func, name='funcname')
t.start()
t.join()
任何进程都会默认启动一个线程,该线程被称之为 主线程,主线程可以启动其他新的线程
Lock
多进程同一个变量会有各自的拷贝数据,但多线程是共享数据
1
2
3
4
5
6
7
8
9
10
11
12
13
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release(
使用
try...finally
来防止死线程导致锁不被释放
如果有多个锁,可能造成死锁,全部挂起
对于每一个进程而言,解释器有一个 GIL锁 global interpreter lock,每个线程执行前,都需要先获取该锁,每执行100条字节码,解释器就会自动释放该锁,让别的线程有机会执行,所以多线程在python中智能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
所以如果想要实现多核任务,需要使用多进程来实现,因为每个进程有各自独立的GIL锁,互不影响