目录
2.1函数
2.1.1函数基础
2.1.1.1函数的定义和调用
2.1.1.2函数的作用
2.1.1.3函数的参数
2.1.1.4函数的返回值
2.1.1.5函数的作用域-LEGB
2.1.2闭包
2.1.3装饰器
2.1.4迭代器和生成器
2.1.4.1可迭代对象
2.1.4.2迭代器
2.1.4.3生成器
2.1.4.4三者之间的异同
2.1.5四种函数
2.1.5.1递归函数
2.1.5.2匿名函数
2.1.5.3普通函数
2.1.5.4高阶函数
2.1.6列表推导式
2.1.6.1三目运算符
2.1.6.2for循环
2.1.6.3for循环和if的嵌套
2.1.6.4for循环和if..else的嵌套
2.1.6.5双层for循环
2.1.6.6问题汇总
2.2类
2.2.1面向对象基础
2.2.1.1面向对象思想
2.2.1.2类和对象
2.2.1.3属性和方法
2.2.1.4类属性和对象属性
2.2.1.5检索原则
2.2.1.6创建对象经历了什么
2.2.2面向对象方法
2.2.2.1普通方法(对象方法)
2.2.2.2类方法
2.2.2.3静态方法
2.2.2.4魔术方法
2.2.2.5几种方法的区别和联系
2.2.2.6注意点
2.2.4面向对象三大特性
2.2.4.1封装(私有化)
2.2.4.2继承
2.2.4.3多继承
2.2.4.4多态
2.2.5类装饰器
2.3异常处理
2.3.1异常基础
2.3.1.1异常基本格式
2.3.1.2异常的类型
2.3.1.3异常注意点
2.3.2自定义异常
2.3.2.1异常的传递
2.3.2.2自定义异常
2.4模块
2.4.1模块基础
2.4.2循环导入问题
2.5多任务
2.5.1进程
2.5.1.1进程基础
2.5.1.2进程之间的通信
2.5.1.3进程池
2.5.1.4自定义进程
2.5.2线程
2.5.2.1线程基础
2.5.2.2自定义线程
2.5.2.3线程共享全局变量
2.5.2.4GIL锁
2.5.2.5线程之间通信以及信号量
2.5.3协程
2.5.3.1生成器实现协程
2.5.3.2greenlet实现
3.5.3.3gevent实现真正的协程
2.5.4多任务基础
2.5.1.1并行和并发
2.5.1.2阻塞和非阻塞
2.5.1.3进程线程和协程
2.5.1.4程序和进程的区别
2.5.1.5同步和异步
def 函数名([形参,...]):
函数体
函数调用:
函数名([实参])
函数参数
num=10
list1=[]
def fun(num,li):
num+=1
li.append(10)
li.append(11)
print(num)
print(li)
fun(num,list1)
print(num)
print(list1)
参数类型
# 默认值参数
def func1(a=None):
print(a)
func1()
# a是关键字参数
def func2(b,a=10):
print(a,b)
func2(b=9)
# 可变参数
def func3(*args,**kwargs):
print(args,kwargs)
func3(1,2,3,a=5,b=6)
参数的拆包和装包
一般情况下,拆包的顺序应该是:必传参数,关键字参数,可变参数,而且关键字参数一般都会在末尾,必传参数一般都会在开头
# 拆包
list1=[6,8,2]
n1,n2,n3=list1 # 都会先做拆包,然后做后面变量的赋值
print(n1,n2,n3)
# 装包
list2=[1,2,3,4,5]
x,*y,z=list2 # 一个变量名前面加一个*号表示多值参数,
print(x,y,z)
如果函数没有返回值,默认为None;如果函数有返回值,那么就要在函数体中添加return关键字
return关键字的作用
结束相关的关键字
搜索变量的规则
a=1
b=2
c=3
def func1():
a=10
b=20
c=30
def func2():
c=300 # 局部变量
print(c)
nonlocal a # 嵌套变量
print(a)
global b # 全局变量
print(b)
return func2
f=func1()
print(f())
闭包的条件
def func1():
def func2(a):
print(a)
return a+1
return func2
f=func1()
print(f(3))
获取当前函数被应用的数量
import sys
print(sys.getrefcount(func1))
闭包的优点:
简单的装饰器
这里我们就拿show和decorator举个例子.装饰器@decorator和show=decorator(show)有相同的功能,都表示将show函数通过装饰器decorator进行装饰
Python中的函数其实就是一个变量,函数名就是变量名,函数名中存放的是函数的地址.首先将需要装饰的函数传入装饰器decorator,然后通过被装饰的函数进行接收,返回出来的是内部函数的引用,在调用内部函数.
def decorator(func):
print('装饰开始')
def wrapper(s):
result=func(s) # 调用要被装饰的函数,并获取被装饰的函数返回的值
print('进行装饰',result) # 对值进行打印
print('装饰结束')
return wrapper
@decorator
def show(x): # 需要被装饰的函数
return x+1
show(2) # 这里的参数是被装饰的函数需要的参数
一个装饰器装饰多个函数
def decorator(func):
def wrapper(s):
result=func(s) # 调用要被装饰的函数,并获取被装饰的函数返回的值
print(result) # 对值进行打印
return wrapper
@decorator
def show1(x): # 需要被装饰的函数
return x
@decorator
def show2(x):
return x
show1('装饰函数show1') # 这里的参数是被装饰的函数需要的参数
show2('装饰函数show2')
一个函数被多个装饰器装饰
距离原函数越近越先被装饰
def decorator1(func):
print('-----1-----')
def wrapper(*args):
func(args)
print('-----1-----')
return wrapper
def decorator2(func):
print('-----2-----')
def wrapper(args):
func(args)
print('-----2-----')
return wrapper
@decorator1
@decorator2
def test1(x):
print(x,'需要被装饰')
test1('Marry')
-----2-----
-----1-----
('Marry',) 需要被装饰
-----2-----
-----1-----
带参数的装饰器
带参数的装饰器需要三层函数才能实现
def decorator(number):
print('装饰调用',number) # 来自装饰器的参数,传递参数,调用装饰器
def decorator1(func): # func = show
print('开始装饰')
def wrapper(*args, **kwargs): # 用来接收调用show传过来的参数
result=func(*args, **kwargs) # 调用需要装饰的函数
print(result) # 对获取到的需要装饰的函数的内容进行打印
return '装饰成功'
return wrapper
return decorator1 # 将真正的装饰器返回
@decorator(number=10)
def show(name):
print('需要装饰的函数')
return name
result=show('Marry')
print(result)
执行顺序
装饰器小结
class MyList(object):
"""自定义的一个可迭代对象"""
def __init__(self):
self.items = []
def add(self, val):
self.items.append(val)
def __iter__(self):
myiterator = MyIterator(self)
return myiterator
class MyIterator(object):
"""自定义的供上面可迭代对象使用的一个迭代器"""
def __init__(self, mylist):
self.mylist = mylist
# current用来记录当前访问到的位置
self.current = 0
def __next__(self):
if self.current < len(self.mylist.items):
item = self.mylist.items[self.current]
self.current += 1
return item
else:
raise StopIteration
def __iter__(self):
return self
一个简答的生成器
s=(x for x in range(2))
print(next(s))
print(next(s))
print(next(s,-1))
使用函数实现生成器
yield:暂停,退出返回yield后面的值
注意点:
函数
小结
实例
def func():
sum1=0
for i in range(5):
x=yield i
sum1+=(x+i)
print(x)
print('sum1',sum1)
print('i',i)
g=func() # 创建生成器对象
next(g) # 启动生成器 g.__next__()
g.send(2) # 为生成器传值
g.send(3) # 生成器中没有要接收返回的值的时候可以使用next,如果有接收的值,我们要用send()函数,包含了next()的功能
g.close() # 关闭生成器
多个生成器切换任务
def study():
for i in range(5):
print("studing......")
yield
def listening():
for i in range(5):
print("listening......")
yield
def weichar():
for i in range(5):
print("weixin......")
yield
g1=study()
g2=listening()
g3=weichar()
while True:
try:
g1.__next__()
g2.__next__()
g3.__next__()
except StopIteration:
quit()
生成器就是当前的值为下一个值计算的条件
from collections import Iterator,Iterable,Generator
# Iterbale 可迭代的
# Iterator 迭代器
# Generator 生成器
print(isinstance(r,Iterable)) # True
print(isinstance(r,Iterator)) # False
print(isinstance(r,Generator)) # False
在每次进行的结构和自己相同的情况下,可以使用递归函数,也就是自己调用自己,虽然比较简单,但是效率很低
原则:必须要有出口,不断向出口靠近
def fun1(n):
if n>1:
return n*fun1(n-1)
else:
return n
print(fun1(5))
a=lambda x,y:x+y
print(a(1,2))
就是把函数当做参数传递
sorted()函数
list3 = [('tom', 21), ('lucy', 18), ('jack', 22), ('lily', 19), ('jerry', 24)]
list4 = sorted(list3, key=lambda x: x[1], reverse=True)
print(list4)
import operator
list1 = [('tom', 21), ('lucy', 18), ('jack', 22), ('lily', 19), ('jerry', 24)]
list2=sorted(list1,key=operator.itemgetter(1),reverse=True)
print(list2)
map(func,iterable)函数
names=['guofubin','chaijinhu','cheweigang','guochengxi']
map1=map(lambda x:x.capitalize(),names)
print(map1)
print(list(map1))
filter(function,iterable)函数
a=[1,2,3,4,5,6,7,8]
filter_obj=filter(lambda i:i%2==0,a)
print(list(filter_obj))
reduce(function,iterable,initial)函数
from functools import reduce
list2 = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, list2, 0) # (运算规则,运算对象,运算初始值)
print(result)
偏函数
partial()
from functools import partial,wraps
int1 = partial(int, base=8)
print(int1('123'))
print(int1('16'))
print(int1('25'))
wraps():消除装饰器带来的一些副作用
def decorator1(func):
@wraps(func)
def wrapper(*args, **kwargs):
func(*args, **kwargs) # func = house
print('铺地板')
print('刷漆')
return wrapper
@decorator1
def house():
print('------》毛坯房')
print(house.__name__)
print(house.__doc__) # document
house()
因为加上装饰器之后文档的名字变了,文档注释也变了,为了消除装饰器的这种副作用.我们在装饰器内部函数的前面加上@wraps(func)即可
a=1 if 1>2 else 2
print(a)
a=[i*2 for i in range(10)]
print(a)
a=[i*3 for i in range(10) if i%2==0]
print(a)
a=[i+1 if i%2==0 else i for i in range(10)]
print(a)
a=["{}*{}={}".format(i,j,i*j) for i in range(10) for j in range(i,10)]
print(a)
问题一:
def fun(a = []):
a.append('a')
print(a)
print(locals())
fun()
fun()
print(globals())
# 结果
['a']
['a','a']
问题二:
print(__name__) # 表示当前模块,如果是当前模块,打印结果就是__main__
if __name__ == '__main__':
pass
问题三:
li=[1,2,3,4,5]
print(id(li))
print(id(li[0]))
列表地址是指该列表的存储地址,而列表的第一个元素的地址指的是元素存储数据的地址,这两个是不一样的.
问题四:
问题五:
Python中一切皆对象
def fun():
pass
a=0
list1=[]
print('a:',isinstance(a,object))
print('fun:',isinstance(fun,object))
print('list1:',isinstance(list1,object))
问题六
x=3 or 5
print(x) 结果是3
x=False and 5 # x=0 and 5
print(x) 结果False
x=True+5
print(x) # 结果是6
是相对于面向过程而言,就是说面向对象是将功能通过对象来实现,将功能封装进对象之中,让对象实现具体的细节这种方法将数据作为第一位,方法或者算法作为其次,是对数据的一种优化,操作起来更加方便,过程简化.面向对象的三大特征:封装\继承和多态
类属性:在类空间中,类属性既可以被类访问又可以被对象访问
class 类名:
属性=值
对象属性:在对象空间中,对象属性只能被当前对象访问
class 类名:
def __init__(self):
super(self).__init__()
属性=值
方式一:动态创建
方式二:依赖__init__()创建
class User(object):
def __init__(self):
super().__init__()
print('init')
def __new__(cls, *args, **kwargs):
print('new')
return super().__new__(cls)
def __del__(self):
print('del')
user=User()
# 结果
new
init
del
创建方式
class Dog:
def eating(self):
print('吃放')
dog=Dog()
dog.eating()
调用方式:对象名.方法名(参数)
访问方式:self.方法名(参数)
注意点:在调用的过程中会默认传递self,表示该对象自己
创建方式
class Dog:
@classmethod
def eating(cls):
print('吃放')
dog=Dog()
dog.eating()
Dog.eating()
调用方式
类中的访问方式
创建方式
import time
class Tool:
@staticmethod
def get_time():
print(time.time())
tool=Tool()
tool.get_time()
Tool.get_time()
调用方式
注意点
魔术方法简介
实例化一个对象经历了什么
常见的魔术方法
class User(object):
def __init__(self,name):
print('init')
def __new__(cls, *args, **kwargs):
print('new')
return super().__new__(cls)
def __del__(self):
print('del')
def __call__(self, *args, **kwargs):
print('call')
def __repr__(self):
return repr(self)
def __len__(self):
return None
def __str__(self):
return 'str'
user=User('Marry')
user()
print(user)
运算相关的魔术方法
对象是不能比较大小的,但是同重写这些运算相关的方法就可以
class Cat:
def __init__(self, nickname, age):
self.nickcname = nickname
self.age = age
def __gt__(self, other):
return self.age > other.age
def __lt__(self, other):
return self.age < other.age
def __eq__(self, other):
print('--->')
return self.age == other.age
def __add__(self, other):
return self.age + other.age
def __str__(self):
return str(self.age)
def __int__(self):
return self.age
c1 = Cat('花花', 2)
c2 = Cat('小猫1', 1)
c3 = Cat('小猫2', 4)
print(c1 > c2)
print(c1 < c2)
print(c1 == c2)
result = c1 + c2
print(result)
s=c1+c2+int(c3)
print(s)
属性相关的魔术方法
class Person:
def __init__(self):
self.name = '12'
def __getattr__(self, item):
if item == 'age':
return 20
elif item == 'gender':
return '男'
else:
return '不存在此属性{}'.format(item)
def __setattr__(self, key, value):
print(key, value)
# 如果这样使用就会反复调用这个方法,进入死循环,因为这个方法就是通过为属性赋值触发的,递归操作
# self.key = value
if key == 'phone' and value.startswith('139'):
object.__setattr__(self, key, value)
p = Person() # 设置对象成员值的时候触发了__setattr__函数
print(p.name)
print(p.phone) # 因为属性phone并不存在于对象,这样会触发__getattr__函数
print(p.__dict__) # 获取p对象的自身属性,并以字典的形式返回
理解封装
封装的作用
私有化
class Person:
# 限制属性作用,只有在该列表中的属性才能别其他方法和属性访问,如果不在该列表中即使是对象方法也是不能访问的,
# 这样做可以防止在函数外动态创建属性
__slots__ = ['__name', '__age', '__flag']
def __init__(self, name, age):
self.__name = name
self.__age = age
self.__flag = True
def get_name(self):
if self.__flag:
return self.__name
else:
return '没有权限查看用户名'
def get_age(self):
return self.__age
def set_name(self, name):
if len(name) >= 6:
self.__name = name
else:
print('名字必须要大于等于6位')
def set_age(self, age):
if 125 > age > 0:
self.__age = age
else:
print('年龄赋值失败,必须在0~125范围内')
p = Person('lucy', 20)
print(p.get_name())
print(p.get_age())
# p.name = 'abc'
# print(p.name)
p.set_name('steven')
print(p.get_name())
p.set_name('tom')
print(p.get_name())
p.set_age(10)
print(p.get_age())
在类的最前面加上这个__slots__ = ['__name', '__age', '__flag'],是为了防止外部随意动态的创建属性.
如果想既要用的时候像原来一样通过对象名.属性名访问对象内部的属性,也想起到限制作用,这是就要用到装饰器@property,为了让用户体验到通过对象名.属性名这种方式中的属性名是真正的属性名,我们会将get和set方法的名字也改成属性的名字,其实在底层,体验者通过上述方式点出来的是方法的名字
在原来的get方法上添加一个property装饰器,函数的名字最好增加的简要,让使用者在调用或者访问的时候更加的简洁好用(也就是让使用者认为是原来的属性名),结合私有属性一起操作.装饰set方法:
class Person:
__slots__ = ['__name']
def __init__(self):
self.__name='gfb'
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
self.__name=name
p=Person()
使用:
对象=类名(参数)
对象.属性名=值 ----set方法
print(对象.属性名)---get方法
特点:
在子类中初始化父类中的方法的时候使用,父类名.__init__(self,参数),这个放在子类__init__()中的位置不同,结果也不同,这个不像java中的一样,只能放置在构造方法的开始位置
class Animal:
type1 = '动物'
def __init__(self):
self.name = '花花'
print('----->Animal')
def run(self):
print('正在奔跑....')
def eat(self):
print('喜欢吃...')
def __str__(self):
return '当前类型:{}'.format(Animal.type1)
class Dog(Animal):
type1 = '狗'
def __init__(self, name, color):
# 调用父类的__init__方法
Animal.__init__(self)
self.name = name
self.color = color
def see_home(self):
print('看家高手....')
def eat(self):
print('喜欢啃骨头...')
d = Dog('大黄', '黄色')
d.run()
d.eat()
d.see_home()
print(d.name)
print(d)
测试题
class Father:
def __init__(self):
self.__a = 10
self.b = 8
def show(self):
r = self.__a + self.b
print('运算结果是:', r)
class Son(Father):
def __init__(self):
Father.__init__(self)
self.__a = 100
s = Son()
s.show()
结果是18,首先执行Father.__init__(self)这个,调用父类中的初始化方法__init__(),因为父类中的__a是私有化属性,因此,并没有将__a传递给子类,然后执行self.__a=100,但是在调用show()方法的时候,因为Son中没有show方法,因此他会去调用父类中的show()方法,但是因为子类中的__a是私有属性,并不能传递给父类,而在调用show()方法的时候是在父类中调用的,因此使用的__a是父类中的属性,因此得出来的结果也就是8+10=18,如果说我们将父类中的__a删除掉,它就会报错,这也这好验证了一点,__私有属性和私有方法只能在本类中使用
重写
查找顺序
可以通过类名.__mor__或者(类名.mro()函数)查看搜索顺序.
多继承的实现
class 类名(父类1,父类2,父类3):
pass
菱形继承
多态就是一个父类的名可以接受一个子类对象,比如我们创建了一个动物类,也创建了一个狗类,狗类继承了动物类,我们创建了一个动物类对象,因为狗类是动物类的子类,我们在接收狗对象的时候可以将这个狗对象存放在创建的动物类变量中.下面举个例子:
宠物商店:
class PetShop:
def __init__(self, name):
self.name = name
self.pets = set()
# 动作
def save_pet(self, pet):
# print(isinstance(pet, Pet))
if isinstance(pet, Pet):
self.pets.add(pet)
print('添加成功!')
else:
print('不是宠物不收留!')
def sale_pet(self, pet):
if isinstance(pet, Pet):
self.pets.discard(pet)
print('宠物减少')
else:
print('不是宠物不收留!')
# 查找宠物
def search_pet(self, pname):
for pet in self.pets:
if pname == pet.pname:
print('宠物在商店里')
break
else:
print('不存在此宠物!')
# 显示所有的宠物
def all_pets(self):
print('宠物商店所有的宠物信息如下:')
for pet in self.pets:
print(pet)
class Pet:
type = '宠物'
注意点
参考文章
try:
可能出现异常的代码
except Exception as ex:
如果出现异常,执行的代码
else:
没有遇到异常时执行的代码
finally:
无论有没有异常都会执行,return也阻止不了它的执行
import random
def func(list1):
sum = 0
for a in list1:
print(a)
sum += a # raise
def get_random():
try:
list1 = []
for i in range(5):
ran = random.randint(1, 10)
list1.append(ran)
list1.append('9')
func(list1)
except Exception as err:
print('+++++++++++++++++>', err)
if __name__ == '__main__':
try:
get_random()
except Exception as err:
print('-------->',err)
raise+自定义异常:当系统的异常无法满足我们的需求的时候,就需要自定义异常
# 自定义异常类
class UserNameError(Exception):
def __init__(self, *args, **kwargs):
pass
class PasswordLengthError(Exception):
def __init__(self, *args, **kwargs):
pass
def register():
username = input("请输入用户名:")
password=input("请输入密码")
if len(username) < 6 or username[0].isdigit():
raise UserNameError('用户名格式错误')
elif len(password)<6 or len(password)>10:
raise PasswordLengthError('密码长度异常.')
else:
print("注册成功")
# 调用函数
if __name__ == '__main__':
try:
register()
except Exception as err:
print(err)
定义模块就是为了让其他模块使用,因此有了模块的导入:
导入模块发生了什么
import的用法:
from..import的用法:
获取自己的模块名
from package02.demo02 import fun02
print("导入的模块",__name__)
if __name__=="__main__":
from package01.demo01 import fun01
print("本模块",__name__)
限制导入
from 模块名 import *和import *都可以将当前所有的模块导入,为了限制,我们可以使用__all__对导入进行限制,这个只对星号起作用
__all__ = ['number', 'func']
__init__.py文件
可以认为是一个魔术模块.初始化包的时候会自动加载,因此我们可以将想要提前加载的东西放入到__init__.py文件中,这会使得在包被加载的时候就会和这个文件一起加载进来了,但是即使这个也是需要导入的.
test.py
from package01 import test
test()
__init__.py
def test():
print("我是通过初始化方法加载进来的")
print("我是着执行初始化的时候执行的...")
什么是循环导入问题?
package01中的demo01.py
from package02.demo02 import fun02
def fun01():
print("我是package01中的demo01")
fun02()
package02中的demo02.py
from package01.demo01 import fun01
def fun02():
print("我是package02中的demo02")
fun01()
看起来似乎没什么错,但是在导入包的时候,首先加载包dedemo01文件执行导包动作的时候,去package02中的demo02中找fun02方法,当它进入demo02中的时候首先加载的也是导包动作:from package01.demo01 import fun01,因此又去原来的位置重新加载,一次类推,这样下去就形成了死循环,也就形成了循环导入问题.
循环导入解决方案
进程的状态
进程的相关概念
相关函数
进程执行原理
多进程案例
# 通过多任务更改全局变量
import multiprocessing
import time
def test1(count):
for i in range(count):
print("------1-------")
time.sleep(0.5)
def test2(count):
for i in range(count):
print("--------2-------")
time.sleep(0.5)
count=100
def main():
t1=multiprocessing.Process(target=test1,args=(count,))
t2=multiprocessing.Process(target=test2,args=(count,))
t1.start()
t2.start()
if __name__=='__main__':
main()
相对线程来说,进程占用的资源要比线程多很多,子进程在执行的时候将主进程的代码资源统统复制了一份去执行,因此资源独立。而线程的资源却是资源共享的。
队列:先进先出
线程之间的通信可以通过共享全局变量实现,但进程之间的通信我们就需要另外一种方式队列queue。其实通过文件也是可以完成进程之间的通信,但是相对来说是很慢的.使用队列的资源共享的缺点是只能在同一个程序中实现资源的贡献.
import multiprocessing
import time
from multiprocessing import Queue
def producer(q):
for i in range(20):
q.put(i)
time.sleep(2)
print('存储对象',i)
# 方法一
def consumer(q):
while True:
try:
val = q.get()
print('获取对象', val)
except Exception as ex:
print('没有数据了')
break
# 方法二
def consumer(q):
while True:
val = q.get()
print('获取对象', val)
if q.qsize==0:
print('没有数据了')
break
if __name__ == '__main__':
q = Queue(20)
p1=multiprocessing.Process(target=producer,args=(q,))
p2=multiprocessing.Process(target=consumer,args=(q,))
p1.start()
time.sleep(1)
p2.start()
按照队列的方式一个进程一个进程进,我们常用的是阻塞式进程池,在进程池中一个任务完成之后才会去做下一个任务.任务数是固定的话用普通进程,如果任务数是不固定的话就用进程池。multiprocessing.Pool常用函数解析:
阻塞式:
import os
import time
from multiprocessing import Pool
# 要执行的任务
def task1():
print('洗衣服:', os.getpid(), os.getppid())
time.sleep(0.5)
return '我是进程:' + str(os.getpid())
# 对来自task的数据进程处理
def callback(msg):
print('{}洗衣服任务完成!'.format(msg))
if __name__ == '__main__':
pool = Pool(4) # 创建一个进程池
# 总共有10个任务,我们将其加入到进程池中,将task1执行后的结果传递给回调函数callback,
# 让回调函数来处理数据,我们可以通过args和kwds来处理数据
for i in range(10):
pool.apply_async(task1,args=(),kwds={}, callback=callback)
pool.close() # 这是表示添加任务结束,停止向进程池中添加任务
pool.join() # 阻塞主进程,等待进程池执行任务
print('main over')
非阻塞式:
import os
import time
from multiprocessing import Pool
def task1():
for i in range(5):
print('洗衣服:',i, os.getpid(), os.getppid())
time.sleep(0.5)
if __name__ == '__main__':
pool = Pool(4)
for i in range(10):
pool.apply(task1) # 阻塞式: 进程池中一个任务完成之后才能做下一个任务
# 添加任务结束
pool.close()
# 阻塞主进程
pool.join()
print('main over')
阻塞式和非阻塞式
自定义进程步骤
from multiprocessing import Process
import time
import os
class MyProcess(Process):
def __init__(self):
Process.__init__(self)
def run(self) -> None:
start=time.time()
for i in range(10):
print(os.getpid(),os.getppid())
time.sleep(0.1)
end=time.time()
print('{}话费时间为:{}'.format(os.getpid(),end-start))
if __name__ == '__main__':
mp=MyProcess()
mp.name='进程1'
mp.start()
线程中的方法
线程实例
在python中threading模块是thread模块的升级版本,对thread做了封装
当调用start()时,才会真正的创建线程,并且开始执行,主线程会等待所有的子线程结束后才结束
from threading import Thread,current_thread,enumerate
import time
def eating(name):
for i in range(5):
print("{}正在吃饭,第{}口饭".format(name,i),current_thread().name)
time.sleep(0.2)
def singing(name):
for i in range(5):
print("{}正在唱歌,第{}首歌".format(name,i),current_thread().name)
time.sleep(0.2)
if __name__ == '__main__':
t1=Thread(target=eating,name='线程一',args=('gfb',))
t2=Thread(target=singing,name='线程二',args=('gfb',))
print('t1是否活着?',t1.is_alive())
print(len(enumerate())) # 查看当前线程数量
t1.start()
t2.start()
print('t1是否活着?',t1.is_alive()) # 判断t1线程是否活着
print(len(enumerate()))
t1.join() # 阻塞t1线程
t2.join()
print('t1是否活着?',t1.is_alive())
print(len(enumerate()))
print("main---",current_thread().name)
线程的执行顺序
注意点:
步骤:
from threading import Thread,current_thread
import time
def task(name):
for i in range(10):
print("{}正在执行任务...".format(name), current_thread().name)
time.sleep(0.2)
from threading import Thread
class MyThread(Thread):
def __init__(self,target,args):
Thread.__init__(self)
self.target=target
self.args=args
def run(self) -> None:
self.target(self.args)
if __name__ == '__main__':
mt1=MyThread(target=task,args=('gfb1'))
mt2=MyThread(target=task,args=('gfb2'))
mt1.start()
mt2.start()
mt1.join()
mt2.join()
print('main---',current_thread().name)
在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据;缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)
import threading
import time
g_num = 0
def work1(num):
global g_num
for i in range(num):
g_num += 1
print("----in work1, g_num is %d---"%g_num)
def work2(num):
global g_num
for i in range(num):
g_num += 1
print("----in work2, g_num is %d---"%g_num)
print("---线程创建之前g_num is %d---"%g_num)
t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()
t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()
while len(threading.enumerate()) != 1:
time.sleep(1)
print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)
如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确,解决线程不安全的方式:GIL锁
from threading import Thread, Lock
number = 0
def task(lock):
global number
lock.acquire() # 握住
for i in range(100000):
number += 1
lock.release() # 释放锁
if __name__ == '__main__':
lock = Lock()
t1 = Thread(target=task, args=(lock,))
t2 = Thread(target=task, args=(lock,))
t3 = Thread(target=task, args=(lock,))
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print('number:', number)
GIL锁的介绍
GIL是什么呢?仍然用篮球比赛的例子来帮助理解:把篮球场看作是CPU,一场篮球比赛看作是一个线程,
如果只有一个篮球场,多场比赛要排队进行,就是一个简单的单核多线程的程序;如果有多块篮球场,
多场比赛同时进行,就是一个简单的多核多线程的程序。然而python有着特别的规定:
每场比赛必须要在裁判的监督之下才允许进行,而裁判只有一个。这样不管你有几块篮球场,
同一时间只允许有一个场地进行比赛,其它场地都将被闲置,其它比赛都只能等待。
注意:lock.acquire()和lock.release()默认是阻塞的
如果这个锁之前是没有上锁的,那么acquire不会堵塞,如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。
每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。
线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。
GIL的优缺点
死锁:
import time
from threading import Lock, Thread, current_thread
def task1(lock1, lock2):
if lock1.acquire():
print('{}获取到lock1锁。。。。'.format(current_thread().name))
for i in range(5):
print('{}------------------>{}'.format(current_thread().name, i))
time.sleep(0.01)
if lock2.acquire():
print('{}获取了lock1,lock2'.format(current_thread().name))
lock2.release()
lock1.release()
def task2(lock1, lock2):
if lock2.acquire():
print('{}获取到lock2锁。。。。'.format(current_thread().name))
for i in range(5):
print('{}----->{}'.format(current_thread().name, i))
time.sleep(0.01)
if lock1.acquire():
print('{}获取了lock1,lock2'.format(current_thread().name))
lock1.release()
lock2.release()
if __name__ == '__main__':
lock1 = Lock()
lock2 = Lock()
t1 = Thread(target=task1, args=(lock1, lock2))
t2 = Thread(target=task2, args=(lock1, lock2))
t1.start()
t2.start()
如何避免死锁
import random
import time
from threading import Thread, current_thread
from queue import Queue
def producer(queue):
print('{}开门啦!'.format(current_thread().name))
foods = ['红烧狮子头', '香肠烤饭', '蒜蓉生蚝', '酸辣土豆丝', '肉饼']
for i in range(1, 21):
food = random.choice(foods)
print('{}正在加工中.....'.format(food))
time.sleep(1)
print('加工完成可以上菜了...')
queue.put(food)
queue.put(None)
def consumer(queue):
print('{}来吃饭啦'.format(current_thread().name))
while True:
food = queue.get()
if food:
print('正在享用美食:', food)
time.sleep(0.5)
else:
print('{}把饭店吃光啦,走人...'.format(current_thread().name))
break
if __name__ == '__main__':
queue = Queue(8)
t1 = Thread(target=producer, name='馒头', args=(queue,))
t2 = Thread(target=consumer, name='小黑', args=(queue,))
t1.start()
t2.start()
在内部有一个counter计数器,每当我们 s.acquire()一次,计数器就进行减1处理,每当 s.release()一次,计数器就进行加1处理,当计数器为0的时候其他线程的就处于等待的状态counter的值就是同一时间可以开启线程的个数(建议使用with),实现方式:
import time
from threading import Thread, Semaphore, current_thread
# from multiprocessing import Semaphore,Process
def task(s):
# s.acquire() # 减1
with s:
for i in range(5):
print('{}扫地....{}'.format(current_thread().name, i))
time.sleep(1)
# s.release() # 加1
if __name__ == '__main__':
s = Semaphore(4)
for i in range(10):
t = Thread(target=task, args=(s,))
t.start()
不光线程有信号量,进程也有信号量,如何理解信号量呢?
在进程中有个进程池,进程池中规定的存储空间只有四个的空间,刚进入进程池的时候存储空间是空的,每进入一个,进程池就创建一个相应的位置,直到四个满了,当四个中有一个任务执行完成了,出去之后外边等待的人进来之后使用的空间还是这四个中的其中一个.但是信号量的话表示我这个地方只规定四个人执行自己的任务,一旦里面的人执行完成,他会携带自己的任务走开,然后其他人进入去执行自己的任务.
进程池主要是值池中有四个位置空间来执行任务,这四个空间的地址不变,每个人进来之后,都在这四个位置其中一个上面执行任务;而信号量表示一个房间中只能供四个人来执行自己的任务,每执行完之后拿着自己完成的任务就走了,后续需要进入房间的人补上.每个人的任务相当于是自己的空间地址,因此各不相同.
协程是一种轻量级别的线程,底层通过生成器来实现,也因此在使用的时候通过yield来记录上一次的值,不会像函数调用一样从头开始执行.
def eating():
for i in range(5):
print("小黑喜欢吃肉...",i)
yield
def listen_music():
for i in range(5):
print("小黑在叫...",i)
yield
if __name__ == '__main__':
g1=eating()
g2=listen_music()
while True:
try:
next(g1)
next(g2)
except Exception:
break
from greenlet import greenlet
def eating():
for i in range(5):
print('吃饭...')
g2.switch()
def listen_music():
for i in range(5):
print('听音乐..', i)
g1.switch()
if __name__ == '__main__':
g1 = greenlet(eating)
g2 = greenlet(listen_music)
g1.switch()
解析:greenlet这个库对yield进行了封装,通过方法switch在协程之间进行自动切换。
启动的时候我们还得用switch方法进行启动。协程最大的特点就是将一起计算的延时时间充分利用做其他的事情。
通过这种方式实现的话需要手动进行协程之间的切换,而且最开始的时候还需手动启动,因为我们无法知道什么这个协程闲着,那个协程比较忙,因此,我们可以通过下面的方式实现协程之间自动的切换
import time
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
for i in range(5):
print('吃饭...',i)
time.sleep(0.1)
def listen_music():
for i in range(5):
print('听音乐..', i)
time.sleep(0.1)
if __name__ == '__main__':
g1 = gevent.spawn(eat)
g2 = gevent.spawn(listen_music)
g1.join()
g2.join()
print('----over---')
切记一点,在程序执行的开始位置,也就是导包之后我们需要加上猴子补丁monkey.patch_all(),这个就相当于一个监工,他来看谁闲着,谁忙着,如果说有人出现了耗时操作,比如time.sleep(1),那么就由他帮我们来切换协程.
先有进程,然后进程创建多个线程,线程依附在进程里面,线程里面可以包含多个协程.