文章目录
1、Python中的数据类型有哪些
-
数字:整数int、浮点数(float、双精度浮点型double)、布尔型(true、false)、复数(complex)、
-
字符串(不可变)
-
列表、
-
元组(不可变)
-
字典
-
集合
布尔类型:用于逻辑运算,有两个值:True(真)和False(假)。
-
可变对象中包含的对象和值是可以被修改的
-
对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样就保证了不可变对象本身永远是不可变的
1、字符串
在Python中,加了引号的字符都被认为是字符串,其声明有三种方式,分别是:单引号、双引号和三引号;
Python中的字符串有两种数据类型,分别是str类型和unicode类型,
-
str类型采用的ASCII编码,无法表示中文
-
unicode类型采用unicode编码,能够表示任意字符,包括中文和其他语言。
a = 'abc'
b = a.replace('a', 'A')
b
'Abc'
a
'abc'
-
通过str函数可以将其他类型的对象转成字符串
-
两个字符串可以相加,使其变成一个字符串
-
字符串也是一种序列, 已知字符串的名字,可以通过索引访问字符,也可以进行切片
>>>a = 5.6
>>>s = str(a)
>>>b = 'python'
>>>list(b)
['p','y','t','h','o','n']
>>>b[:3]
'pyt'
>>>c = 'ccc'
>>>d = 'ddd'
>>>c + d
'cccddd'
# 通过索引访问
>>>url = 'http://c.biancheng.net/python/'
#获取索引为 10 的字符
>>>print(url[10])
i
#获取索引从3处22(不包含22)的子串
>>>print(url[7: 22])
c.biancheng.net
1)字符串的常用方法
1) 字符与ASCII码的相互转换
-
求一个字符的ASCII码:ord()
-
ASCII转为字符:chr()
2)求字符串长度:
- len(str)
3) 分割字符串:
-
str.split (sep, maxsplit)
-
按照指定的分隔符将字符串切分成多个子串,这些子串会被保存到列表中(不包含分隔符),作为方法的返回值反馈回来。
-
sep:用于指定分隔符,可以包含多个字符。此参数默认为 None,表示所有空字符,包括空格、换行符“\n”、制表符“\t”等。
-
maxsplit:可选参数,用于指定分割的次数,最后列表中子串的个数最多为 maxsplit+1。如果不指定或者指定为 -1,则表示分割次数没有限制。
-
如果不指定 sep 参数,那么也不能指定 maxsplit 参数。
4)统计字符串出现的次数:
-
str.count(sub [, start [, end ] ] )
-
sub:表示要检索的字符串;
-
start:指定检索的起始位置(检索时包含该索引位置),也就是从什么位置开始检测。如果不指定,默认从头开始检索;
-
end:指定检索的终止位置,如果不指定,则表示一直检索到结尾。
5)合并字符串:
-
newstr = str.join( iterable )
-
newstr:表示合并后生成的新字符串;
-
str:用于指定合并时的分隔符;
-
iterable:做合并操作的源字符串数据,允许以列表、元组等形式提供。
# ASCII码和字符转换
>>>sum = ord('A')
65
>>>sum = chr(65)
A
# 字符串分割
>>>str = "C语言中文网 >>> c.biancheng.net"
>>> str
'C语言中文网 >>> c.biancheng.net'
>>>list2 = str.split('>>>') #采用多个字符进行分割
>>> list2
['C语言中文网 ', ' c.biancheng.net']
# 统计字符串出现次数
>>> str = "c.biancheng.net"
>>> str.count('.',1)
2
>>> str.count('.',2)
1
# 合并字符串
>>> list = ['c','biancheng','net']
>>> '.'.join(list)
'c.biancheng.net'
6)检测字符串中是否包含某子串:find与rfind
-
str.find(sub [, start [, end ] ] )
-
str:表示原字符串;
-
sub:表示要检索的目标字符串;
-
start:表示开始检索的起始位置。如果不指定,则默认从头开始检索;
-
end:表示结束检索的结束位置。如果不指定,则默认一直检索到结尾。
rfind():与 find() 方法最大的不同在于,rfind() 是从字符串右边开始检索。
7)检测字符串中是否包含某子串:index
-
str.index( sub [, start [ , end ] ] )
-
当指定的字符串不存在时,index() 方法会抛出异常。
-
str:表示原字符串;
-
sub:表示要检索的目标字符串;
-
start:表示开始检索的起始位置。如果不指定,则默认从头开始检索;
-
end:表示结束检索的结束位置。如果不指定,则默认一直检索到结尾。
# find()方法:检测字符串中是否包含某子串
>>> str = "c.biancheng.net"
>>> str.find('.')
1
>>> str = "c.biancheng.net"
>>> str.find('.',2,-4)
-1
# 用rfind方法检测字符串中是否包含某子串
>>> str = "c.biancheng.net"
>>> str.rfind('.')
11
# 用index检测字符串中是否包含某子串
>>> str = "c.biancheng.net"
>>> str.index('.')
1
8)大小写转换
-
str.title()
title() 方法用于将字符串中每个单词的首字母转为大写,其他字母全部转为小写,转换完成后,此方法会返回转换得到的字符串。如果字符串中没有需要被转换的字符,此方法会将字符串原封不动地返回。 -
str.lower()
lower() 方法用于将字符串中的所有大写字母转换为小写字母,转换完成后,该方法会返回新得到的字符串。如果字符串中原本就都是小写字母,则该方法会返回原字符串。 -
str.upper()
用于将字符串中的所有小写字母转换为大写字母,和以上两种方法的返回方式相同,即如果转换成功,则返回新字符串;反之,则返回原字符串。 -
3 个方法都仅限于将转换后的新字符串返回,而不会修改原字符串。
-
str 表示要进行转换的字符串。
>>> str = "c.biancheng.net"
>>> str.title()
'C.Biancheng.Net'
>>> str = "I LIKE C"
>>> str.title()
'I Like C'
>>> str = "I LIKE C"
>>> str.lower()
'i like c'
>>> str = "i like C"
>>> str.upper()
'I LIKE C'
9)去除字符串中空格(删除指定字符)
- str.strip( [ chars ] ):删除字符串前后(左右两侧)的空格或特殊字符。
-str.lstrip( [ chars ] ):删除字符串前面(左边)的空格或特殊字符。
-str.rstrip( [ chars ] ):删除字符串后面(右边)的空格或特殊字符。
-
str 表示原字符串,[chars] 用来指定要删除的字符,可以同时指定多个,如果不手动指定,则默认会删除空格以及制表符、回车符、换行符等特殊字符。
-
Python 的 str 是不可变的(不可变的意思是指,字符串一旦形成,它所包含的字符序列就不能发生任何改变),因此这三个方法只是返回字符串前面或后面空白被删除之后的副本,并不会改变字符串本身。
>>> str = " c.biancheng.net \t\n\r"
>>> str.strip()
'c.biancheng.net'
>>> str.strip(" \r")
'c.biancheng.net \t\n'
2)正则表达式
-
正则表达式是一种用来匹配字符串的。
-
它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。
-
正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。
用字符描述字符:
-
\d 可以匹配一个数字,
-
\w 可以匹配一个字母或数字
-
. 可以匹配任意字符,比如’py.’ 可以匹配’pyc’、‘pyo’、'py!'等
-
\s 可以匹配一个空格(也包括Tab等空白符)
匹配变长的字符时:
-
*表示任意个字符(包括0个),
-
+表示至少一个字符,比如 \s+ 表示至少有一个空格
-
? 表示0个或1个字符,
-
{n} 表示n个字符,
-
{n,m} 表示n-m个字符:
进阶规则:
-
[] 表示范围
-
A|B 可以匹配A或B,
比如 (P|p)ython 可以匹配 ‘Python’ 或者 ‘python’ 。 -
^表示行的开头,
-
^\d表示必须以数字开头。
-
$表示行的结束,
-
\d$表示必须以数字结束。
特殊字符,
- 在正则表达式中,比如 ‘-’ 要用 ‘’ 转义
- 用r’ , 则不用转义
比如 r’^\d{3}-\d{3,8}$’ 可以匹配010-12345
举例:
-
[a-zA-Z_][0-9a-zA-Z_]*可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量
-
[a-zA-Z_][0-9a-zA-Z_]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)
-
^py$就变成了整行匹配,只能匹配 ‘py’
-
[0-9a-zA-Z_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如’a100’,‘0_Z’,'Py3000’等等;
应用:re模块包含所有正则表达式的功能
1)切分字符串
2)分组
>>>import re
>>> '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')
['a', 'b', 'c', 'd']
>>> re.split(r'[\s\,\;]+', 'a,b;; c d')
['a', 'b', 'c', 'd']
>>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
>>> m
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> m.group(0)
'010-12345'
>>> m.group(1)
'010'
>>> m.group(2)
'12345'
- group(0)永远是原始字符串,group(1)、group(2)……表示第1、2、……个子串。
2、列表:list
可变,长度:len(li)
-
查找和插入的时间随着元素的增加而增加;
-
占用空间小,浪费内存很少。
创建列表:
-
1)for i in range(1, 10, 2): # 步长为2
-
2)S = [i for i in range(1, 10, 2)]
range函数返回一个迭代器,生成等差整数序列 -
3)创建空列表:li=[]
添加:
-
任意索引位置插入:list.insert(0, ’x’)
-
列尾添加:list.append(‘x’)
-
在列表末尾一次性追加另一个序列中的多个值:list.extend(seq)
(用新列表扩展原来的列表)
list.extend([7, 8, 9]) -
连接两个列表:
list1 + list2 -
二分法插入到有序列表中:bisect.insort(list, 6)
将6插入到有序列表list中,插入后序列仍是有序的 -
bisect.bisect(list, 6)
返回6应该插入的位置,并保持序列有序
删除:
-
任意索引位置删除:
del li[1] :移除索引1处的元素
li.pop(1):移除索引1处的元素并返回该元素 -
删除列尾li.pop() # 删除的值可用:令p=li.pop(),则可以使用p
-
删除某指定元素:li.remove(‘x’)
移除第一次出现的x
排序:
-
永久排序:li.sort() # 默认升序,无返回值
-
逆序:li.sort(reverse=True):降序
-
按照列表中每个元素的长度排序:list.sort(key=len)
-
临时排序:a = sorted(li) # 不改变原列表的顺序,有返回值
翻转列表:
-
li.reverse()
-
li[ : :-1]
-
reversed(li):不改变原列表的顺序
读:
-
读列表中的每个值:for value in li: # value即li中的元素
-
切片:前闭后开
-
Li[1:5]、Li[:5]、Li[1:]、Li[-1:]、Li[:]
判断特定值是否在列表中:
- ‘x’ in li
- ‘x’ not in li
判断列表是否为空:
- if li :
enumerate、zip函数
enumerate函数:
- 遍历一个序列的时候同时追踪其索引
zip函数:
-
将几个序列的元素配对,
-
一般用于同时遍历几个序列,和enumerate同时使用
>>>li = ['aaa', 'bbb', 'ccc']
>>>mapping = {}
>>>for i, v in enumerate(li):
>>> mapping[v] = i
>>>mapping
{'aaa',:0 , 'ccc':2, 'bbb':1}
>>>seq1 = ['aaa', 'bbb', 'ccc']
>>>seq2 = ['one', 'two', 'three']
>>>z = zip(seq1, seq2)
>>>list(z)
[('aaa', 'one'), ('bbb', 'two'), ('ccc', 'three')]
a = [1,2,3]
b = [2,1,2]
c = [x[0]+ x[1] for x in zip(a, b)]
print(c)
输出:
[3, 3, 5]
3、元组:tuple
长度固定的、不可变的列表,使用()来标识
创建元组:
-
建立空元组:tuple_1 = ()
-
直接用逗号分隔序列值即可生成tuple
-
元组中只包含一个元素时,需要在元素后面添加逗号 , ,否则括号会被当作运算符使用:
>>>tup = 1,2,3
>>>tup
(1, 2, 3)
>>>tup2 = (3,4),(5,6)
>>>tup2
((3, 4), (5, 6))
>>>tup1 = (50)
>>>type(tup1) # 不加逗号,类型为整型
<class 'int'>
>>>tup1 = (50, )
>>>type(tup1) # 加上逗号,类型为元组
<class 'tuple'>
列表的insert、append、pop、remove在元组中不可用,
虽然不能修改元组的元素,但是可以给存储元组的变量赋值。(即直接设置一个新的元组给这个变量)
-
直接tuple[1] = 3 会报错
-
如果元组中的一个元素是可变的,那可以对这个元素进行修改
-
tuple乘以一个整数,生成含有多份拷贝的元组:
对象本身没有复制,只是只指向它们的引用进行了复制 -
可以用+号连接元组:
(1,2)+(3,4)返回(1,2,3,4) -
元组拆包的应用:
1)两个数进行交换
2)遍历元组或者列表组成的序列
3)从函数返回多个值 -
count函数用来计算某个数值在元组中出现的次数(列表中也可以用)
>>>tup = ('foo',[1,2])
>>>tup[1].append(3)
>>>tup
('foo',[1,2,3])
>>>tup * 2
('foo',[1,2],'foo',[1,2],'foo',[1,2])
>>>tup1 = [1,2,3]
>>>a, b, c = tup1
>>>b
2
>>>a, b = 1, 2
>>>b, a = a, b
>>>b
1
>>>seq = [(1, 2), (3, 4)]
>>>for a, b in seq:
.......print('a = {0}, b = {1}'.format(a, b))
a=1, b=2
a=3, b=4
>>>value = 1, 2, 3, 4, 5
>>>a, b, *rest = value
>>>a, b
(1, 2)
>>>rest
[3, 4, 5]
>>>a = (1,2,2,2,3)
>>>a.count(2)
3
访问速度:
4、字典:dict
创建空字典:dic = {}
添加键-值对:dic[‘键名’] = value
访问值:dic[‘键名’]
修改值:
-
dic[‘键名’] = 新value
-
一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉
-
合并两个字典:dic.update({‘b’:1, ‘c’: 2})
如果传入的字典中的键和原字典的相同,则原字典的值会被覆盖
删除:
- 删除键-值对:del dic[‘键名’]
- 删除键:dic.pop(键名),返回该键对应的值
遍历
-
遍历键-值对:
for key, value in dic.items(): # items()返回一个键值对 -
遍历值:
1)for value in dic.values()
2)for value in set( dic.values() ) # 先去重,再遍历 -
遍历键:
1)for key in dic.keys()
2)for key in sorted( dic.keys() )
对键排序后在遍历,不改变原字典中的排序
判断键是否存在:
-
dic.get(‘键名’) :如果不存在,则返回None
-
dic.get(‘键名’,-1):如果不存在,返回-1,(-1是自己设置的,可以变成别的)
-
‘键名’ in dic:不存在的话,返回False
注意:
-
dict的key必须是不可变对象,列表不可以用作key:
通过hash函数判断一个对象是否可以哈希化(即是否可用作字典的键) -
当元组作为字典的键时,元组内的元素也必须是不可变的
-
当列表要作字典的键时,可以将其转换为数组(通过tuple函数)
-
字典的值只能有一个
-
dict内部存放的顺序和key放入的顺序是没有关系的。
-
dict的特点:
1)查找和插入的速度极快,不会随着key的增加而变慢;
2)需要占用大量的内存,内存浪费多。
5、集合:set
-
无序且唯一。自动去重,set中没有重复的元素
-
set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”
-
set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:
创建集合:
- 用set函数:s = set([1, 2, 3])
- 用{}:{2,3,4}
添加:
- s.add(4)
- 可以重复添加同一个值,但不会有效果,因为会去重
删除:
- s.remove(4)
fronzenset:不可变的集合,方法和set的相同。
6、类型转换
-
str、bool、int、float函数可以将对象转换为自身的类型
-
tuple函数可以将任意序列或者迭代器转换成元组
-
list函数可以将其他类型序列转换为列表
>>>s = '5.445'
>>>fval = float(s)
>>>type(fval)
float
>>>int(fval)
3
>>>bool(fval)
True
>>>bool(o)
False
>>>tup = tuple('string')
>>>tup
('s','t','r','i','n','g')
>>>tup[0]
's'
7、推导式
1)三元表达式:
- value = x1 if condition else x2
2)列表推导式:
-
[ expr for val in collection ]
-
[ expr for val in collection if condition ]
-
列表推导式中带有列表推导式:
[ [ expr for val_1 in collection_1 ] for collection_1 in collection ]
3)嵌套列表推导式:
- [ expr for val_1 in collection_1 for val_2 in collection_2]
- for表达式的顺序和实际写嵌套循环生成列表的顺序一致,即最前面的for表达式为最外层循环
4)字典推导式:
- { key-expr: value-expr for val in collection if condition }
5)集合推导式:
- { expr for val in collection if condition }
2、判断数据类型
1、isinstance()
-
isinstance()函数来判断一个对象是否是一个已知的类型,类似 type()。
-
如果要判断两个类型是否相同推荐使用 isinstance()。
isinstance(object, classinfo)
-
object – 实例对象。
-
classinfo – 可以是直接或间接类名、基本类型或者由它们组成的元组。
-
返回值:
如果对象的类型与参数二的类型(classinfo)相同则返回 True,否则返回 False。
>>>a = 2
>>> isinstance (a,int)
True
>>> isinstance (a,str)
False
>>> isinstance (a,(str,int,list)) # 是元组中的一个返回 True
True
>>> isinstance([1, 2, 3], (list, tuple))
True
2、type()
# 判断基本数据类型
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type('abc')==str
True
>>> type('abc')==type(123)
False
>>>A = 123
>>>>B = [123,'A']
>>>>C = {'A':123, 'B':{'D':1, 'C': 1}}
>>>>type(A) == type(1); type(B) == type([]); type(C) == type({'a': 1});
True
True
True
# 判断是否是函数:
>>> 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
3、type和isinstance的区别
-
type() 不会认为子类是一种父类类型,不考虑继承关系。
-
isinstance() 会认为子类是一种父类类型,考虑继承关系。isinstance可以用来判断继承。
class A:
pass
class B(A):
pass
b = B()
isinstance(b, B) # 返回True,因为b是B的实例
isinstance(b, B) # 返回True,因为B是A的子类
isinstance(b, (A,B,C)) # 返回True
3、复制
1、直接赋值、浅拷贝和深度拷贝
直接赋值:其实就是对象的引用(别名)。
浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。
深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。
2、复制列表
-
用切片的方式进行复制
-
用list函数:
list()是列表构造函数。它会在传入的数列基础上新建一个列表。数列不一定是列表,它可以是任何类型的数列。 -
用copy函数
-
用copy.deepcopy函数
当列表中含有列表时,只有copy.deepcopy方法得到的新列表才是包含子列表在内的完全复制。
'''
此时并没有复制a所指引的列表。
只是创建了一个新的标签b,然后将其指向a所指向的列表。
'''
>>> a = [1, 2, 3]
>>> b = a
>>> a.append(4)
>>> print a
[1, 2, 3, 4]
>>> print b
[1, 2, 3, 4]
1. 用切片的方式进行复制
内建函数id()可以返回对象的唯一id。该id是对象的内存地址。
>>> c = a[1:3]
[2, 3]
>>> id(a)
3086056
>>> id(c)
3063400 # 内存地址不一样,代表的就是c和a相互独立,对a进行的操作不会影响到c
2.用list函数
>>> d = list(a)
>>> id(a)
3086056
>>> id(d)
3086256
使用 a[:], list(a), a*1,copy.copy(a)四种方式复制列表结果都可以得到一个新的列表,但是如果列表中含有列表,所有b, c, d, e四个新列表的子列表都是指引到同一个对象上。
只有使用copy.deepcopy(a)方法得到的新列表f才是包括子列表在内的完全复制。
>>> import copy
>>> a = [[10], 20]
>>> b = a[:]
>>> c = list(a)
>>> d = a * 1
>>> e = copy.copy(a)
>>> f = copy.deepcopy(a)
>>> a.append(21)
>>> a[0].append(11)
>>> print id(a), a
30553152 [[10, 11], 20, 21]
>>> print id(b), b
44969816 [[10, 11], 20]
>>> print id(c), c
44855664 [[10, 11], 20]
>>> print id(d), d
44971832 [[10, 11], 20]
>>> print id(e), e
44833088 [[10, 11], 20]
>>> print id(f), f
44834648 [[10], 20]
3、复制文件
用 Python 复制文件的 9 种方法具体是:
-
shutil copyfile() 方法
-
shutil copy() 方法
-
shutil copyfileobj() 方法
-
shutil copy2() 方法
-
os popen 方法
-
os system() 方法
-
threading Thread() 方法
-
subprocess call() 方法
-
subprocess check_output() 方法
4、IO编程
IO在计算机中指Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。
1、打开文件
- with 的作用就是自动调用close()方法
'''方式一'''
f = open( '/Users/michael/test.txt', 'r' )
f.close() # 文件使用完毕后必须关闭
'''
方式二:
with 的作用就是自动调用close()方法
'''
with open( '/path/to/file', 'r' ) as f: #
print( f.read() )
'''
打开非utf-8编码的文件,比如打开GBK编码的文件
'''
with open('/Users/michael/gbk.txt', 'r', encoding='gbk') as f:
f.read()
open()是否需要缓冲区:
通常情况下、建议大家在使用 open() 函数时打开缓冲区,即不需要修改 buffing 参数的值。
如果 buffing 参数的值为 0(或者 False),则表示在打开指定文件时不使用缓冲区;如果 buffing 参数值为大于 1 的整数,该整数用于指定缓冲区的大小(单位是字节);如果 buffing 参数的值为负数,则代表使用默认的缓冲区大小。
为什么呢?原因很简单,目前为止计算机内存的 I/O 速度仍远远高于计算机外设(例如键盘、鼠标、硬盘等)的 I/O 速度,如果不使用缓冲区,则程序在执行 I/O 操作时,内存和外设就必须进行同步读写操作,也就是说,内存必须等待外设输入(输出)一个字节之后,才能再次输出(输入)一个字节。这意味着,内存中的程序大部分时间都处于等待状态。
而如果使用缓冲区,则程序在执行输出操作时,会先将所有数据都输出到缓冲区中,然后继续执行其它操作,缓冲区中的数据会有外设自行读取处理;同样,当程序执行输入操作时,会先等外设将数据读入缓冲区中,无需同外设做同步读写操作。
2、文件读写
在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。
读:
f.read(): # 读取全部文件内容
f.read(size): # 每次读取size个字节内容
f.readline(): # 每次读取一行的内容
f.readlines(): # 读取全部内容,但结果是个list,每行内容是一个元素
'''
调用readlines()一次读取所有内容并按行返回list
读取文件之后,文字末尾会出现'\n'
strip() 函数中可以把目标内容line里面所有的空格、空行等都删除掉,只剩余文字内容
'''
for line in f.readlines():
print( line.strip() )
写:
f.write( ) # 将数据写入文件
f.writelines() # 将多行数据写入文件中,数据以列表的方式提供
比如:
f.writelines(['11\n','22\n',...'66\n'])
和光标有关的操作:
f.tell( ) # 可以将文件指针的当前指向的位置读出(即光标位置)
f.seek() #将光标位置移至所需位置。
'''
f.seek(offset,whence=0)
offset:开始偏移量,也就是代表需要移动偏移的字节数。
whence:从哪个位置开始偏移;
whence常量:
os.SEEK_SET: 0 相对文件起始位,默认
os.SEEK_CUR: 1 相对文件的当前位置
os.SEEK_END: 2 相对文件的结束位置
'''
# 比如:
import os #导入OS
f.seek(-3,os.SEEK_CUR)
f.truncate( [size]) # size可有可无
#当不指定size的时候,表示从光标位置删除后面内容;
#当指定size之后,表示从文件头开始,保留size个字节的字符(同上,中文按两个字节计算)
f.flush( ) # 将内存内容立即写入硬盘
open()标识符:
模式 | 描述 | 指针 | 文件是否存在的影响 |
---|---|---|---|
r | 以只读方式打开文件。 | 文件的指针将会放在文件的开头。这是默认模式。 | 如果文件不存在,则出错 |
rb | 以二进制格式打开一个文件用于只读。 | 文件指针将会放在文件的开头。这是默认模式。 | 如果文件不存在,则出错 |
r+ | 打开一个文件用于读写。 | 文件指针将会放在文件的开头。 | 如果文件不存在,则出错。读写都可以移动光标。写入时,如果光标不在文件末尾,则会覆盖源文件 |
rb+ | 以二进制格式打开一个文件用于读写。 | 同r+ | 同r+ |
w | 以只写模式打开文件 | 光标在文件开头 | 如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。 | 同w | 同w |
w+ | 打开一个文件用于读写。 | 光同w | 同w |
wb+ | 以二进制格式打开一个文件用于读写。 | 同w | 同w |
a | 打开一个文件用于追加。 | 文件指针将会放在文件的结尾。追加模式 | 如果该文件已存在,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab | 以二进制格式打开一个文件用于追加。 | 同a | 同a |
a+ | 打开一个文件用于读写。 | 同a | 同a |
ab+ | 以二进制格式打开一个文件用于追加。 | 同a | 同a |
3、os文件/目录方法
5、异常
-
当一个程序发生异常时,代表该程序在执行时出现了非正常的情况,无法再执行下去。默认情况下,程序是要终止的。如果要避免程序退出,可以使用捕获异常的方式获取这个异常的名称,再通过其他的逻辑代码让程序继续运行,这种根据异常做出的逻辑处理叫作异常处理。
-
对异常处理的简单理解就是:在程序运行出现错误时,让Python 解释器执行事先准备好的除错程序,进而尝试恢复程序的执行。
1、捕获异常 :try except else finally
try:
可能产生异常的代码块
except Error1:
处理异常的代码块1
except Error2 as e:
处理异常的代码块2
except (Error3, Error4, ... ):
处理异常的代码块3
except:
处理其它异常
else:
没有异常的话执行此段代码块
finally:
无论是否发生异常都要执行的代码块
try 块有且仅有一个,但 except 代码块可以有多个,且每个 except 块都可以同时处理多种异常。
try except 语句的执行流程如下:
首先执行 try 中的代码块,
1)如果执行过程中出现异常,系统会自动生成一个异常类型,并将该异常提交给 Python 解释器,此过程称为捕获异常。
-
当 Python 解释器收到异常对象后,会寻找能处理该异常对象的 except 块
-
先跳转到第一个except语句,如果第一个except中定义的异常与引发的异常匹配,则执行该except中的语句,处理异常
-
如果引发的异常不匹配第一个except,则会搜索第二个except,如果所有的except都不匹配,则程序终止,Python 解释器也将退出。
2)如果执行过程中没有异常的话,如果有else的话,就执行else下的代码。
3)不论有没有异常,如果有finally的话,最后都要执行finally下的代码块
2、主动触发异常:raise
def not_zero(num):
try:
if num == 0:
raise ValueError('参数错误')
return num
except Error1 as e:
print(e)
not_zero(0)
3、追溯异常:traceback、exc_info()
捕获异常时,有 2 种方式可获得更多的异常信息,分别是:
-
使用 sys 模块中的 exc_info 方法;
-
使用 traceback 模块中的相关函数。
import traceback
try:
1/0
except Exception as e:
traceback.print_exc()
如果捕获到异常的话,traceback会把出错的程序所在的文件位置或者函数位置等具体信息显示出来,比如以下这种。
Traceback (most recent call last):
File "E:/PycharmProjects/ProxyPool-master/proxypool/test.py", line 4, in <module>
1/0
ZeroDivisionError: division by zero
exc_info() 方法会将当前的异常信息以元组的形式返回,该元组中包含 3 个元素,分别为 type、value 和 traceback,它们的含义分别是:
- type:异常类型的名称,它是 BaseException 的子类
- value:捕获到的异常实例。
- traceback:是一个 traceback 对象。
#使用 sys 模块之前,需使用 import 引入
import sys
try:
x = int(input("请输入一个被除数:"))
print("30除以",x,"等于",30/x)
except:
print(sys.exc_info())
print("其他异常...")
当输入 0 时,程序运行结果为:
请输入一个被除数:0
(<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero',), <traceback object at 0x000001FCF638DD48>)
其他异常...
6、断言(assert)、break、continue
-
assert 语句,又称断言语句,可以看做是功能缩小版的 if 语句,它用于判断某个表达式的值,如果值为真,则程序可以继续往下执行;反之,Python 解释器会报 AssertionError 错误。
-
assert 语句通常用于检查用户的输入是否符合规定,还经常用作程序初期测试和调试过程中的辅助工具。
语法结构为: assert 表达式
assert语句对的执行流程可以用 if 判断语句表示:
if 表达式==True:
程序继续执行
else:
程序报 AssertionError 错误
mathmark = int(input())
#断言数学考试分数是否位于正常范围内
assert 0 <= mathmark <= 100
#只有当 mathmark 位于 [0,100]范围内,程序才会继续执行
print("数学考试分数为:",mathmark)
输入:90
输出:数学考试分数为: 90
输入:159
输出:
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\file.py", line 3, in <module>
assert 0 <= mathmark <= 100
AssertionError
Python 提供了 2 种强制离开当前循环体的办法:
-
使用 continue 语句,可以跳过执行本次循环体中剩余的代码,转而执行下一次的循环。
-
只用 break 语句,可以完全终止当前循环。
7、垃圾回收(Garbage collection )机制
在Python中,主要通过引用计数进行垃圾回收;
通过 “标记-清除” 解决容器对象可能产生的循环引用问题;
通过 “分代回收” 以空间换时间的方法提高垃圾回收效率。
1、引用计数法(Reference Counting)
-
引用计数法的原理是每个对象维护一个ob_ref,用来记录当前对象被引用的次数,也就是来追踪到底有多少引用指向了这个对象,
-
当指向该对象的内存的引用计数器为0的时候,该内存将会被Python虚拟机销毁
-
Python语言默认采用的垃圾收集机制
当发生以下四种情况的时候,该对象的引用计数器+1:
-
对象被创建 a=14
-
对象被引用 b=a
-
对象被作为参数,传到函数中 func(a)
-
对象作为一个元素,存储在容器中 List={a,”a”,”b”,2}
与上述情况相对应,当发生以下四种情况时,该对象的引用计数器-1:
-
当该对象的别名被显式销毁时 del a
-
当该对象的引别名被赋予新的对象, a=26
-
一个对象离开它的作用域,例如 func函数执行完毕时,函数里面的局部变量的引用计数器就会减一(但是全局变量不会)
-
将该元素从容器中删除时,或者容器被销毁时。
引用计数法的优点:
-
高效
-
实时性。运行期没有停顿,一旦没有引用,内存就直接释放了,处理回收内存的时间分摊到了平时。
-
对象有确定的生命周期
-
易于实现
引用计数法的缺点:
-
维护引用计数消耗资源,维护引用计数的次数和引用赋值成正比,而不像mark and sweep等基本与回收的内存数量有关。
-
无法解决循环引用的问题。A和B相互引用而再没有外部引用A与B中的任何一个,它们的引用计数都为1,但显然应该被回收。
'''循环引用的例子'''
a = { } # 对象A的引用计数为 1
b = { } # 对象B的引用计数为 1
a['b'] = b # B的引用计数增1
b['a'] = a # A的引用计数增1
del a # A的引用减 1,最后A对象的引用为 1
del b # B的引用减 1, 最后B对象的引用为 1
2、标记清除(Mark—Sweep)
标记清除算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:
-
标记阶段:遍历所有的对象,如果是可达的(reachable),也就是还有对象引用它,那么就标记该对象为可达(活动对象)
-
清除阶段,再次遍历对象,如果发现某个对象没有标记为可达(非活动对象),则就将其回收。
判断活动对象:
-
对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。
-
根对象就是全局变量、调用栈、寄存器。
在上图中,我们把小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。
使用:
-
“标记-清除”法是为了解决循环引用问题。
-
标记清除算法作为Python的辅助垃圾收集技术主要处理的是一些容器对象,比如list、dict、tuple,set等,因为对于字符串、数值对象是不可能造成循环引用问题。
-
python使用零代链表来持续追踪活跃的对象,每次当你创建一个对象或其他什么值的时候,Python会将其加入零代链表
-
当被分配对象的计数值与被释放对象的计数值之间的差异累计超过某个阈值,则Python的收集机制就启动了,并且触发零代算法(循环引用检测),释放“浮动的垃圾”,并且将剩下的活跃对象移动到一代列表。
-
随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。
-
垃圾回收时,会暂停整个应用程序,等待标记清除结束后才会恢复应用程序的运行。
缺点:
- 清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。
3、分代回收(Generational Collection)
在循环引用对象的回收中,整个应用程序会被暂停,为了减少应用程序暂停的时间,Python 通过“分代回收”以空间换时间的方法提高垃圾回收效率。
分代回收是基于这样的一个统计事实:
- 对于程序,存在一定比例的内存块的生存周期比较短;而剩下的内存块,生存周期会比较长,甚至会从程序开始一直持续到程序结束。生存期较短对象的比例通常在 80%~90% 之间,
- 这种思想简单点说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集。这样在执行标记-清除算法时可以有效减小遍历的对象数,从而提高垃圾回收的速度。
Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代。Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。
新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
同时,分代回收是建立在标记清除技术基础之上。
分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象。
回收逻辑:
8、缓存重用机制
-
Python 缓冲机制是为提高程序执行的效率服务的,
-
实际上就是在 Python解释器启动时从内存空间中开辟出一小部分,用来存储高频使用的数据,这样可以大大减少高频使用的数据创建时申请内存和销毁时撤销内存的开销。
#范围在 [-5, 256] 之间的小整数
int1 = -5
int2 = -5
print("[-5, 256] 情况下的两个变量:", id(int1), id(int2))
输出:
[-5, 256] 情况下的两个变量: 1792722416 1792722416
#bool类型
bool1 = True
bool2 = True
print("bool类型情况下的两个变量:",id(bool1),id(bool2))
输出:
bool类型情况下的两个变量: 1792241888 1792241888
#对于字符串
s1 = "3344"
s2 = "3344"
print("字符串情况下的两个交量", id(s1), id(s2))
输出:
字符串情况下的两个交量 2912801330712 2912801330712
#大于 256 的整数
int3 = 257
int4 = 257
print("大于 256 的整数情况下的两个变量", id(int3), id(int4))
输出:
大于 256 的整数情况下的两个变量 2912801267920 2912801267920
#大于 0 的浮点数
f1 = 256.4
f2 = 256.4
print("大于 0 的浮点数情况下的两个变量", id(f1), id(f2))
输出:
大于 0 的浮点数情况下的两个变量 2912762210728 2912762210728
#小于 0 的浮点数
f3 = -2.45
f4 = -2.45
print("小于 0 的浮点数情况下的两个变量", id(f3), id(f4))
输出:
小于 0 的浮点数情况下的两个变量 2912762211016 2912762211040
#小于 -5 的整数
n1 = -6
n2 = -6
print("小于 -5 的整数情况下的两个变量", id(n1), id(n2))
输出:
小于 -5 的整数情况下的两个变量 2912801267952 2912801267984
def fun():
#[-5,256]
int1 = -5
print("fun中 -5 的存储状态",id(int1), id(int2))
#bool类型
bool3 = True
print("fun中 bool 类型的存储状态",id(bool3),id(bool2))
#字符串类型
s1 = "3344"
print("fun 中 3344 字符串的存储状态", id(s1), id(s2))
#大于 256
int3 = 257
print("fun中 257 的存储状态", id(int3), id(int4))
#浮点类型
f1 = 256.4
print("fun 中 256.4 的存储状态",id(f1), id(f2))
#小于 -5
n1 = -6
print("fun 中 -6 的存储状态", id(n1), id(n2))
fun()
输出:
fun中 -5 的存储状态 1792722416 1792722416
fun中 bool 类型的存储状态 1792241888 1792241888
fun 中 3344 字符串的存储状态 1976405206496 1976405206496
fun中 257 的存储状态 1976405225648 1976405225680
fun 中 256.4 的存储状态 1976394459752 1976394459872
fun 中 -6 的存储状态 1976404744880 1976405225744
9、进程、线程、协程
多任务的实现有3种方式:
-
多进程模式;
-
多线程模式;
-
多进程+多线程模式。
1、多进程
1)Unix/Linux 操作系统中使用 os 模块中的 fork() 函数在 python 程序中创建子进程。
-
普通的函数调用,调用一次,返回一次,但是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.
2)在Windows系统中使用multiprocessing模块的Process类来代表一个进程对象
-
from multiprocessing import Process
-
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动
-
join() 方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
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.
进程池可以用来批量创建子进程
-
from multiprocessing import Pool
-
Pool(n) :设置同时跑 n 个进程,默认是cpu的核数
-
对Pool对象调用 join() 方法会等待所有子进程执行完毕,调用 join()之前必须先调用 close(),调用close()之后就不能继续添加新的Process了。
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.
- task 0,1,2,3是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。
进程间的通信
- from multiprocessing import Queue
'''
在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
'''
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()
运行结果:
Process to write: 50563
Put A to queue...
Process to read: 50564
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.
2、多线程
Python 多线程
每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。任何进程默认就会启动一个线程。
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
-
线程可以被抢占(中断)。
-
在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) – 这就是线程的退让。
python中的模块:
-
Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。
-
绝大多数情况下,我们只需要使用threading这个高级模块。
-
启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:
t = threading.Thread( target=xx, name=‘yy’ )
threading 模块提供的其他方法:
-
threading.currentThread():
返回当前的线程变量。 -
threading.enumerate():
返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 -
threading.activeCount():
返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
-
run():
用以表示线程活动的方法。 -
start():
启动线程活动。 -
join([time]):
等待至线程中止。这阻塞调用线程直至线程的join()
方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。 -
isAlive():返回线程是否活动的。
-
getName(): 返回线程名。
-
setName():设置线程名。
import time, threading
# 新线程执行的代码:
def loop():
print('thread %s is running...' % threading.current_thread().name)
n = 0
while n < 5:
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='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)
运行结果:
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.
-
任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程
-
主线程实例的名字叫MainThread,
-
子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……
-
多个线程操作共享变量时,需要加锁
多线程的优点
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
-
使用线程可以把占据长时间的程序中的任务放到后台去处理。
-
用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
-
程序的运行速度可能加快
-
在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
3、多进程和多线程比较
要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker。
-
如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker。
-
如果用多线程实现Master-Worker,主线程就是Master,其他线程就是Worker。
多进程模式
-
最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名的Apache最早就是采用多进程模式。
-
缺点是创建进程的代价大,在Unix/Linux系统下,用fork调用还行,在Windows下创建进程开销巨大。
-
另外,操作系统能同时运行的进程数也是有限的,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统连调度都会成问题。
多线程模式
-
在Windows下,多线程的效率比多进程要高
-
任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。
在Windows上,如果一个线程执行的代码出了问题,你经常可以看到这样的提示:“该程序执行了非法操作,即将关闭”,其实往往是某个线程出了问题,但是操作系统会强制结束整个进程。
4、分布式进程
分布式进程指的是将Process进程分布的多台机器上,充分利用多台机器的性能完成复杂的任务
分布式进程在python 中依然要用到multiprocessing 模块。
multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。可以写一个服务进程作为调度者,将任务分布到其他多个进程中,依靠网络通信进行管理。
例子:
在做爬虫程序时,抓取某个网站的所有图片,如果使用多进程的话,一般是一个进程负责抓取图片的链接地址,将链接地址放到queue中,另外的进程负责从queue中取链接地址进行下载和存储到本地。
现在把这个过程做成分布式,一台机器上的进程负责抓取链接地址,其他机器上的进程负责系在存储。
那么遇到的主要问题是将queue 暴露到网络中,让其他机器进程都可以访问,分布式进程就是将这个过程进行了封装,我们可以将这个过程称为本地队列的网络化
5、协程
协程,又称微线程,是用户级的轻量级线程。
协程拥有自己的寄存器上下文和栈,调度切换时,将寄存器上下文保存在其他地方,切回来恢复。因此,协程能保留上一次调用的状态
python通过yield提供了对协程的基本支持,但不完全,而使用第三方gevent库是更好的选择。gevent是基于协程的python网络函数库