文章目录
一、并行编程概念
1、非并发,即按照顺序一步步执行,单核CPU,仍无法实现真正的并发,而多核可以多个任务一起执行,真正可以实现并行运行。
2、进程每打开一次应用,即有一次实例了,如双击一个QQ,即一个进程打开了,打开多个QQ,即有多个进程实例在运行了。
3、 同一个进程下的包含多个独立执行的线程,这些线程可以共享上下文 ,即共享的空间地址,包括一些数据结构,或一些资源。不同线程之间还可以进行信息共享通信,如下图:
Python全局解释器锁(GIL)
总之: Python是解释型语言,运行环境可以是虚拟机,这个虚拟机也称为解释器主循环,当运行程序时,这个解释器主循环要求每次只能有一个主要控制线程执行,因为避免多个线程同时执行时有可能导致数据不一致与状态不同步,所以即使程序中是多线程,运行时候也是一个一个执行 。所以用这个锁来切换线程,当一个线程运行完自己的字节码程序后,然后让出控制权,进入睡眠状态,让另一个线程执行。实际开发中,程序若是计算密集型消耗CPU即用多进程实现,或者底层的东西可以用c语言写,而IO密集型程序,Python会尽可能快释放锁,所以可以用Python编写。实际上只是cPython解释器有这个锁,而其他的如JavaPython解释器即没这个锁,因此这个锁,是由Python解释器运行时候加上的,与程序本身无关。
多线程之_thread模块
总概图:
模拟一个单线程耗时的案例
import time
def worker(n):
print('函数开始于:{}'.format(time.ctime()))#可以获取当前时间
time.sleep(n)#每个子线程执行所需要时间,等待几秒
print('函数结束于:{}'.format(time.ctime())) # 可以获取当前时间
def main():
print('主函数开始于:{}'.format(time.ctime())) # 可以获取当前时间
worker(4)
worker(2)
print('主函数结束于:{}'.format(time.ctime())) # 可以获取当前时间
if __name__=='__main__':
main()
测试结果:发现是一个执行完后,另一个线程再执行,即依次顺序执行的。
主函数开始于:Tue Dec 31 10:18:56 2019
函数开始于:Tue Dec 31 10:18:56 2019
函数结束于:Tue Dec 31 10:19:00 2019
函数开始于:Tue Dec 31 10:19:00 2019
函数结束于:Tue Dec 31 10:19:02 2019
主函数结束于:Tue Dec 31 10:19:02 2019
模拟多线程执行
注:不必要等一个线程执行完,你再顺序执行,而可以一起执行,先用thread模块的一些组件实现
import time
import _thread
def worker(n):
print('函数开始于:{}'.format(time.ctime()))#可以获取当前时间
time.sleep(n)#每个子线程执行所需要时间,等待几秒
print('函数结束于:{}'.format(time.ctime())) # 可以获取当前时间
def main():
print('主函数开始于:{}'.format(time.ctime())) # 可以获取当前时间
_thread.start_new_thread(worker,(4,))#开启第一个线程,参数1为执行的函数的引用,参数2为元组
_thread.start_new_thread(worker, (2,))#开启第二个线程
time.sleep(5)#让主线程等待几秒,即等待子线程执行完后再结束整个进程。
print('主函数结束于:{}'.format(time.ctime())) # 可以获取当前时间
if __name__=='__main__':
main()
测试结果:即主函数开始后,两个子线程也同时一起开始,,之后一个线程2s结束,另一个4s结束。并行各自执行各自的,并行运行了,所以总共用时4s,之后再加1s,即主线程也执行完毕了,即最后可以一起结束了。实际上main函数中 time.sleep(5)可以压缩到4s,但实际中,我们并不知道每个子任务运行时间,即休眠多长时间不知道,所以_thread模块有个锁,即在主线程运行过程中始终判断这个锁的状态,没有就等着,只有等待所有子线程执行完后,锁才释放了,主线程才结束,从而结束主进程了。所以一般不用_thread模块,麻烦
主函数开始于:Tue Dec 31 10:27:58 2019
函数开始于:Tue Dec 31 10:27:58 2019
函数开始于:Tue Dec 31 10:27:58 2019
函数结束于:Tue Dec 31 10:28:00 2019
函数结束于:Tue Dec 31 10:28:02 2019
主函数结束于:Tue Dec 31 10:28:03 2019
多线程模块之threading
第一种方式实现多线程
实例1
import time
import threading
def worker(n):
print('函数开始于:{}'.format(time.ctime()))#可以获取当前时间
time.sleep(n)#每个子线程执行所需要时间,等待几秒
print('函数结束于:{}'.format(time.ctime())) # 可以获取当前时间
def main():
print('主函数开始于:{}'.format(time.ctime())) # 可以获取当前时间
threads=[]#创建个线程列表,每建立一个线程即先不启动统一放到列表中,不想构造一个就启动一个,而是希望最后再一起启动执行。
t1=threading.Thread(target=worker,args=(4,))
threads.append(t1)
t2= threading.Thread(target=worker, args=(2,))
threads.append(t2)
for t in threads:
t.start()#依次启动每个线程,
print('主函数结束于:{}'.format(time.ctime())) # 可以获取当前时间
if __name__=='__main__':
main()
测试结果:发现确实实现了并行运行,但是发现主函数都结束了,但两个子函数最后才结束。即主线程与子线程不同步问题。
主函数开始于:Tue Dec 31 10:51:24 2019
函数开始于:Tue Dec 31 10:51:24 2019
函数开始于:Tue Dec 31 10:51:24 2019
主函数结束于:Tue Dec 31 10:51:24 2019
函数结束于:Tue Dec 31 10:51:26 2019
函数结束于:Tue Dec 31 10:51:28 2019
实例2.希望子线程结束后,再一起退出,即让主线程与子线程同步。可以用join等待
import time
import threading
def worker(n):
print('函数开始于:{}'.format(time.ctime()))#可以获取当前时间
time.sleep(n)#每个子线程执行所需要时间,等待几秒
print('函数结束于:{}'.format(time.ctime())) # 可以获取当前时间
def main():
print('主函数开始于:{}'.format(time.ctime())) # 可以获取当前时间
threads=[]#创建个线程列表,每建立一个线程即先不启动统一放到列表中,最后再一起启动执行。
t1=threading.Thread(target=worker,args=(4,))
threads.append(t1)
t2= threading.Thread(target=worker, args=(2,))
threads.append(t2)
for t in threads:
t.start()#在这依次启动每个线程
for t in threads:
t.join()#即每启动一个线程后,告诉主线程你要等我结束后你才能结束。
print('主函数结束于:{}'.format(time.ctime())) # 可以获取当前时间
if __name__=='__main__':
main()
测试结果:即主线程在子线程结束后,他才结束。也是并行运行的。
主函数开始于:Tue Dec 31 10:56:45 2019
函数开始于:Tue Dec 31 10:56:45 2019
函数开始于:Tue Dec 31 10:56:45 2019
函数结束于:Tue Dec 31 10:56:47 2019
函数结束于:Tue Dec 31 10:56:49 2019
主函数结束于:Tue Dec 31 10:56:49 2019
实例3将每个线程名字运行时也一并打印出来
threading.current_thread().name#通过这样获取当前的子线程,并把名字给我。
#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@author:lenovo
@file: lenovo.py
@time: 2019/12/31
@desc:
"""
import time
import threading
def worker(n):
print('{}函数开始于:{}'.format(threading.current_thread().name,time.ctime()))#可以获取当前时间
time.sleep(n)#每个子线程执行所需要时间,等待几秒
print('{}函数结束于:{}'.format(threading.current_thread().name,time.ctime())) # 可以获取当前时间
def main():
print('主函数开始于:{}'.format(time.ctime())) # 可以获取当前时间
threads=[]#创建个线程列表,每建立一个线程即先不启动统一放到列表中,最后再一起启动执行。
t1=threading.Thread(target=worker,args=(4,))
threads.append(t1)
t2= threading.Thread(target=worker, args=(2,))
threads.append(t2)
for t in threads:
t.start()#依次启动每个线程
for t in threads:
t.join()#即每启动一个线程后,告诉主线程你要等我结束后你才能结束。
print('主函数结束于:{}'.format(time.ctime())) # 可以获取当前时间
if __name__=='__main__':
main()
测试结果:
主函数开始于:Tue Dec 31 11:05:11 2019
Thread-1函数开始于:Tue Dec 31 11:05:11 2019
Thread-2函数开始于:Tue Dec 31 11:05:11 2019
Thread-2函数结束于:Tue Dec 31 11:05:13 2019
Thread-1函数结束于:Tue Dec 31 11:05:15 2019
主函数结束于:Tue Dec 31 11:05:15 2019
以上主要内容如下:
第二种方式实现多线程
通过自定义的一个线程派生类并继承threading.Thread来实现。虽然现在麻烦,但实际中逻辑复杂时,这样会更灵活。
#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@author:lenovo
@file: lenovo.py
@time: 2019/12/31
@desc:
"""
import time
import threading
def worker(n):
print('{}函数开始于:{}'.format(threading.current_thread().name,time.ctime()))#可以获取当前时间
time.sleep(n)#每个子线程执行所需要时间,等待几秒
print('{}函数结束于:{}'.format(threading.current_thread().name,time.ctime())) # 可以获取当前时间
#定义一个自己的线程类,继承threading.Thread类,这样实现即可以自己定义函数传参,即可以加更多的函数,实现复杂的逻辑程序了。
class Mythread(threading.Thread):
#构造函数要执行的函数为target=,以及参数args=这些名称可以自己定义
def __init__(self,func,args):
threading.Thread.__init__(self)
self.func=func
self.args=args
def run(self): #这个方法可以覆盖父类的方法,即在之后start启动一个线程时,就执行这个函数
self.func(*self.args)#因为传的是一个元组给函数参数过来,所以必须解包,所以加*
# self.func(*self.args)即我自己调用我自己的属性就进行执行了,所以run没有传参数。
def main():
print('主函数开始于:{}'.format(time.ctime())) # 可以获取当前时间
threads=[]#创建个线程列表,每建立一个线程即先不启动统一放到列表中,最后再一起启动执行。
t1=Mythread(worker,(4,))#实际在基类threading.Thread与代码之间加一个派生类Mythread,虽然复杂了,
threads.append(t1)
t2= Mythread(worker,(2,))
threads.append(t2)
for t in threads:
t.start()#依次启动每个线程
for t in threads:
t.join()#即每启动一个线程后,告诉主线程你还是要等我结束后你才能结束。
print('主函数结束于:{}'.format(time.ctime())) # 可以获取当前时间
if __name__=='__main__':
main()
测试结果:
主函数开始于:Tue Dec 31 11:25:15 2019
Thread-1函数开始于:Tue Dec 31 11:25:15 2019
Thread-2函数开始于:Tue Dec 31 11:25:15 2019
Thread-2函数结束于:Tue Dec 31 11:25:17 2019
Thread-1函数结束于:Tue Dec 31 11:25:19 2019
主函数结束于:Tue Dec 31 11:25:19 2019
以上总结:
同步锁
一件事情,由多个不同的子线程执行它,共同去执行一个任务,即可能避免混乱。即有了同步锁,即第一个子线程执行完共享数据后,释放锁,第二个子线程再去执行时,即锁住,即依次执行下去即可。引入同步原语技术,而锁只是这种技术中的一种方法。
同步原语之:锁
总括:
实例1 问题抛出,即每个子线程放鸡蛋并不是一次放1-5个鸡蛋,而是中间会被别的线程干扰。
```python
import threading
import random
import time
eggs=[]
def put_egg(n,lst):#这个函数中有两个参数
for i in range(1,n+1):#放n个鸡蛋
time.sleep(random.randint(0,2))#模拟每个子线程执行的逻辑复杂度,即执行效率不同。
lst.append(i)
def main():
threads=[]
for i in range(3):#创建3个线程都去执行放鸡蛋函数
t=threading.Thread(target=put_egg,args=(5,eggs))#执行放鸡蛋函数,参数为n与lst,所以元组中两个参数
threads.append(t)
for t in threads:
t.start()#依次启动每个线程
for t in threads:
t.join()#即每启动一个线程后,告诉主线程你还是要等我结束后你才能结束。
print(eggs)#因为启动了三个线程,所以这三个每个都会执行往eggs中放鸡蛋,而主函数最终即把所有的eggs一起打印出来了。即主线程执行 print(eggs)这个函数
if __name__=='__main__':
main()
测试结果:即每个子线程放鸡蛋并不是一次放1-5个鸡蛋,而是中间会被别的线程干扰。
[1, 1, 1, 2, 3, 2, 2, 4, 3, 4, 5, 3, 5, 4, 5]
实例2 针对即每个子线程放鸡蛋并不是一次放1-5个鸡蛋,而是中间会被别的线程干扰问题,加锁,即一个线程放完5个鸡蛋后,再解锁,第二个线程再去执行,之后依次进行。
注:解决方法:即第一个线程执行时,防止别人干扰他放鸡蛋顺序,即可以加上锁,避免其他子线程干扰,等你执行完放鸡蛋后,再释放锁,即可以了。
import threading
import random
import time
eggs=[]
lock=threading.Lock()#建立锁
def put_egg(n,lst):#这个函数中有两个参数
lock.acquire()#每个线程执行时加自己的锁,锁住
for i in range(1,n+1):#放n个鸡蛋
time.sleep(random.randint(0,2))#模拟每个子线程执行的逻辑复杂度,即执行效率不同。
lst.append(i)
lock.release()#待这个子线程执行完放鸡蛋后,再释放锁,让第二个线程去执行放鸡蛋了。
def main():
threads=[]
for i in range(3):#创建3个线程都去执行放鸡蛋函数
t=threading.Thread(target=put_egg,args=(5,eggs))#执行放鸡蛋函数,参数为n与lst,所以元组中两个参数
threads.append(t)
for t in threads:
t.start()#依次启动每个线程
for t in threads:
t.join()#即每启动一个线程后,告诉主线程你还是要等我结束后你才能结束。
print(eggs)#因为启动了三个线程,所以这三个每个都会执行往eggs中放鸡蛋,而主函数最终即把所有的eggs一起打印出来了。即主线程执行 print(eggs)这个函数
if __name__=='__main__':
main()
测试结果:即这个子线程执行完放鸡蛋后,再释放锁,让第二个线程去执行放鸡蛋了,结果就是这样对了。
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
实例3with实现锁功能,效果一样,更简便
import threading
import random
import time
eggs=[]
lock=threading.Lock()#建立锁
def put_egg(n,lst):#这个函数中有两个参数
#lock.acquire()#每个线程执行时加自己的锁,锁住
with lock:#这样with上下文操作也可以实现锁的功能
for i in range(1,n+1):#放n个鸡蛋
time.sleep(random.randint(0,2))#模拟每个子线程执行的逻辑复杂度,即执行效率不同。
lst.append(i)
# lock.release()#待这个子线程执行完放鸡蛋后,再释放锁,让第二个线程去执行放鸡蛋了。
def main():
threads=[]
for i in range(3):#创建3个线程都去执行放鸡蛋函数
t=threading.Thread(target=put_egg,args=(5,eggs))#执行放鸡蛋函数,参数为n与lst,所以元组中两个参数
threads.append(t)
for t in threads:
t.start()#依次启动每个线程
for t in threads:
t.join()#即每启动一个线程后,告诉主线程你还是要等我结束后你才能结束。
print(eggs)#因为启动了三个线程,所以这三个每个都会执行往eggs中放鸡蛋,而主函数最终即把所有的eggs一起打印出来了。即主线程执行 print(eggs)这个函数
if __name__=='__main__':
main()
测试结果:
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
队列
例如多个外卖订单来了,优先处理那个,即要有优先级,即要用队列。可以保证多线程坏境下共享资源的一个有序操作。
标准库模块queue下边有三种队列
先进先出队列即(Queue)FIFO
函数功能:由生产者用一个线程执行,每0.5秒放一个随机数到队列中,总共放5个。根据先进先出,即消费者两个线程执行时,先取先放的数据,最后通过队列的方法task_done()来说明当前任务已执行完毕
import threading
import time
import random
import queue
#定义生产者函数,往队列data_queue中放入对象,data_queue共享队列。
def produce(data_queue):
for i in range(5):
time.sleep(0.5)#每次放入一个对象时,停留0.5s
item=random.randint(1,100)
data_queue.put(item)#每次往共享队列中放入一个对象数据
print(f'{threading.current_thread().name} 在队列中放入数据项:{item}')#可以看到每次哪个线程去往里边放东西
#定义消费者者函数,往队列data_queue中取对象,data_queue共享队列。
def consumer(data_queue):
while True:
try:
item=data_queue.get(timeout=3)#指定3s若从队列中获取不到数据对象,即超时
print(f'{threading.current_thread().name} 从队列中取出数据{item}并执行了')
# 可以看到每次哪个线程去往里边取东西并执行
except queue.Empty:#捕获到队列中空了,即退出循环,不要再取数据了
break
else:
data_queue.task_done()#当上边没捕获到异常时,该处理的都已经处理完毕,即执行这个。即说明队列中数据对象已处理完毕。task_done()是一个方法
def main():
q=queue.Queue()#构造一个接受处理对象数据的队列,一会传给形参data_queue
threads = []
p= threading.Thread(target=produce, args=(q,))#即构造一个线程去负责往队列中放入数据对象
p.start()
for i in range(2): # 创建2个线程去执行从队列中取出对象数据执行
t = threading.Thread(target=consumer, args=(q,))
threads.append(t)
for t in threads:
t.start() # 依次启动每个消费者线程
for t in threads:
t.join() # 即每启动一个线程后,告诉主线程你还是要等我结束后你才能结束。
q.join()#要求主线程还要等待整个队列中的对象都执行完成后,你主线程再结束
if __name__ == '__main__':
main()
测试结果:发现生产者线程放入到队列中的数据与消费者线程处理的顺序一致。即那个数据先放入,我就先执行那个数据。如下:
Thread-1 在队列中放入数据项:17
Thread-2 从队列中取出数据17并执行了
Thread-1 在队列中放入数据项:18
Thread-3 从队列中取出数据18并执行了
Thread-1 在队列中放入数据项:40
Thread-2 从队列中取出数据40并执行了
Thread-1 在队列中放入数据项:15
Thread-3 从队列中取出数据15并执行了
Thread-1 在队列中放入数据项:85
Thread-2 从队列中取出数据85并执行了
后进先出队列(LifoQueue)LIFO,也称为栈Last in First out
处理顺序,不是按进去的先后顺序,而是由特定算法指定来决定(PriorityQueue)
多进程模块multiprocessing
由于有全局解释器锁,所以多线程更多适合IO密集型操作,因为对IO密集型操作会随时释放全局解释器锁,这样允许更多的并发。当对于计算密集型时,多线程有锁存在,还是一一顺序执行的,这时我们可以选择其他的并发操作。如多进程模块,能更合理更充分发挥计算机CPU多核的性能,适合计算密集型。
mport time
import multiprocessing
def worker(n):
print('{}执行开始于:{}'.format(multiprocessing.current_process().name,time.ctime()))#可以获取当前时间
time.sleep(n)#每个子进程执行所需要时间,等待几秒
print('{}执行结束于:{}'.format(multiprocessing.current_process().name, time.ctime())) # 可以获取当前时间
def main():
print('主函数开始于:{}'.format(time.ctime())) # 可以获取当前时间
process=[]#创建个进程列表,
t1=multiprocessing.Process(target=worker,args=(4,))
process.append(t1)
t2= multiprocessing.Process(target=worker, args=(2,))
process.append(t2)
for t in process:
t.start()#依次启动每个进程程,
for t in process:
t.join()#主进程等待子进程执行完毕
print('主函数结束于:{}'.format(time.ctime())) # 可以获取当前时间
if __name__=='__main__':
main()
测试结果:也只需4s即可执行完毕。即并行运行了。
主函数开始于:Tue Dec 31 16:59:26 2019
Process-1执行开始于:Tue Dec 31 16:59:26 2019
Process-2执行开始于:Tue Dec 31 16:59:26 2019
Process-2执行结束于:Tue Dec 31 16:59:28 2019
Process-1执行结束于:Tue Dec 31 16:59:30 2019
主函数结束于:Tue Dec 31 16:59:30 2019
更多并行编程
当确定好IO密集型就用多线程,此时我们用ThreadPoolExecutor来帮我们更简单的实现多线程。
当确定好计算密集型就用多进程,此时我们ProcessPoolExecutor来帮我们更简单的实现多进程。
实例1
import time
import concurrent.futures#导入这个模块就可以用它下边的进程与线程了
#定义一个函数计算列表中的每个数
num=list(range(1,11))
def count(n):
for i in range(10000000):
i+=i
return i*n
def worker(x):
res=count(x)
print(f'数字:{x}的计算结果是:{res}')
#顺序执行即单线程执行
def seq():
start=time.clock()#获取开始执行时的时间
for i in num:
worker(i)
print(f'顺序执行花费时间:{time.clock()-start}')
#多线程执行时间
def thread():
start=time.clock()
#采用上下文创建多个线程
with concurrent.futures.ThreadPoolExecutor(max_workers=5)as excut:#创建了5个线程了存在于实例excut中
for i in num:
excut.submit(worker,i)#即通过这样让线程池中的去为我们分配去让哪个线程去执行每个任务即worker函数
print(f'线程池执行花费时间:{time.clock() - start}')
#多进程执行时间,类似多线程
def process():
start=time.clock()
#采用上下文创建多个进程
with concurrent.futures.ProcessPoolExecutor(max_workers=5)as excut:#创建了5个进程的进程池,存在于实例excut中
for i in num:
excut.submit(worker,i)#即通过这样让进程池中的多个进程去执行worker函数
print(f'进程池执行花费时间:{time.clock() - start}')
if __name__=='__main__':
#seq()#查看顺序执行时间
#thread()#查看多线程执行时间
#process()#查看多进程执行时间
测试结果:
顺序:
数字:1的计算结果是:19999998
数字:2的计算结果是:39999996
数字:3的计算结果是:59999994
数字:4的计算结果是:79999992
数字:5的计算结果是:99999990
数字:6的计算结果是:119999988
数字:7的计算结果是:139999986
数字:8的计算结果是:159999984
数字:9的计算结果是:179999982
数字:10的计算结果是:199999980
顺序执行花费时间:5.0088314
多线程
数字:3的计算结果是:59999994
数字:1的计算结果是:19999998
数字:2的计算结果是:39999996
数字:5的计算结果是:99999990
数字:6的计算结果是:119999988
数字:4的计算结果是:79999992
数字:7的计算结果是:139999986
数字:10的计算结果是:199999980
数字:8的计算结果是:159999984
数字:9的计算结果是:179999982
线程池执行花费时间:4.618586
多进程
数字:3的计算结果是:59999994
数字:2的计算结果是:39999996
数字:1的计算结果是:19999998
数字:5的计算结果是:99999990
数字:4的计算结果是:79999992
数字:6的计算结果是:119999988
数字:8的计算结果是:159999984
数字:9的计算结果是:179999982
数字:10的计算结果是:199999980
数字:7的计算结果是:139999986
进程池执行花费时间:1.5354069
发现顺序执行与多线程(因为多线程有锁限制)所用时间差不多,说明对于计算密集型,应该采用多进程能充分调用CPU多核为我们处理。
二、函数基础
总图:
将函数赋给变量
类型是<class ‘function’>即每个函数都是属于类’function’的一个对象,因为函数本身也可以充当一个字面值,即本身也是一个对象,即可以赋值给另外一个变量了
def hello(name):
print('hello',name)
hello('tom')#hello tom
print(type(hello))#类型是<class 'function'>即每个函数都是属于类'function'的一个对象
#因为函数本身也可以充当一个字面值,即本身也是一个对象,即可以赋值给另外一个变量了
gree=hello
print(type(gree))#类型也是<class 'function'>
#即此时可以通过gree变量来运行函数了。
gree('tom')#hello tom
将函数作为参数传递
即委托另一个函数帮我们去执行。
def add(a,b):
return a+b
def sub(a,b):
return a - b
#将add与sub函数作为参数可以传给action函数了,委托action函数帮助我们去执行
def action(x,y,func):
return func(x,y)
#完成add函数
action(5,3,add)
print(action(5,3,add))
#完成sub函数
action(5,3,sub)
print(action(5,3,sub))
#也可以传进去个匿名函数进去
a=action(5,3,lambda x,y:x*y)
print(a)
print((lambda x,y:x-y)(5,3))#结果为2;注意匿名函数整体要括起来,传参数进去,之后会自动将结果返回的
函数嵌套
def gree():
def hello():
return 'hello'
return hello()#注这种返回的是将hello函数的执行结果返回了即'hello'
gree()
def gree():
def hello():
return 'hello'
return hello#注这种返回的是将hello函数本身即函数hello这个对象返回
#调用
gree()()#因为gree()只是返回hello引用,所以再加括号即可以返回'hello'字符串了
跨域访问
#1、说明默认x访问func2的本地变量域的x=20,而不是访问上一层函数func1的变量x
def func1():
x=10
def func2():
x=20
return x+10
return func2()
print(func1())#结果为30
#说明默认x访问func2的本地变量域的x=20,而不是访问上一层函数func1的变量x
#2、nonlocal x #加上这个的话,仅代表即对于嵌套函数的内层函数func2就可以访问他的上一层即外层函数func1的变量x了;注意这样仅说明他可以访问上层函数func1中的变量x,而不是全局即函数之外的变量x
def func1():
x=10
def func2():
nonlocal x#加上这个的话,仅代表即对于嵌套函数的内层函数func2就可以访问他的上一层即外层函数func1的变量x了
return x+10
return func2()
print(func1())#结果为20
#说明默认x访问func2的本地变量域的x=20,而不是访问上一层函数func1的变量x
用函数定义的装饰器
1、#特别注意:*args可以接受通配没有名字的任何类型参数,如2,3;**kwargs可以接受通配任何有名字的参数,如a=5等,即这两个参数可以通配所有函数的任意类型任意数量的参数
2、return ‘
’+func(*args,**kwargs)+’
'注意这里是字符串的拼接,不要弄成return '
func(*args,**kwargs)
'这样就是将整个看成为一个字符串了,函数的结果出不来了。#此时这个 gettex()函数返回就是个普通字符串,要想对这个函数返回结果进行修饰,可以定义一个
#装饰器函数为:
def decorate(func):#这个函数要对哪个函数进行修饰,即将哪个函数作为对象传进来
def wrapper(*args,**kwargs):#通过传进来的函数func,再通过wrapper进行 修饰。
#注意:*args可以接受通配没有名字的任何类型参数,如2,3;**kwargs可以接受通配任何有名字的参数,如a=5等,即这两个参数可以通配所有函数的任意类型任意数量的参数
return '<p>'+func(*args,**kwargs)+'</p>'#这里注意保留函数原来的参数不变,即传进来的函数func原来有什么参数就还是什么参数
return wrapper#外层函数返回wrapper函数本身这个对象即引用
@decorate#要修饰那个函数,即将定义的装饰器函数的名字就是装饰器的名字即decorate
def gettex():
return '欢迎学习python课堂'
if __name__=='__main__':
print(gettex())
结果
<p>欢迎学习python课堂</p>#即对原理函数gettex的结果修饰了。
2、效果与上面方法一样,更好理解
#此时这个 gettex()函数返回就是个普通字符串,要想对这个函数返回结果进行修饰,可以定义一个装饰器函数
def decorate(func):#这个函数要对哪个函数进行修饰,即将哪个函数作为对象传进来
def wrapper(*args,**kwargs):#通过传进来的函数func,再通过wrapper进行 修饰。
#注意:*args可以接受通配没有名字的任何类型参数,如2,3;**kwargs可以接受通配任何有名字的参数,如a=5等,即这两个参数可以通配所有函数的任意类型任意数量的参数
return '<p>'+func(*args,**kwargs)+'</p>'#这里注意保留函数原来的参数不变,即传进来的函数func原来有什么参数就还是什么参数
return wrapper#外层函数返回wrapper函数本身这个对象即引用
def gettex():
return '欢迎学习python课堂'
if __name__=='__main__':
#这个结果为wrapper函数对象,所以再对整体加上括号即可运行wrapper函数了。即
a=(decorate(gettex))()
print(a)
结果:
<p>欢迎学习python课堂</p>
3、对一个有参数的函数修饰
#不管是有参数还是无参数都可以通配,因为装饰器函数中的*args,**kwargs可以通配任意数量类型的参数
#此时这个 gettex()函数返回就是个普通字符串,要想对这个函数返回结果进行修饰,可以定义一个装饰器函数
def decorate(func):#这个函数要对哪个函数进行修饰,即将哪个函数作为对象传进来
def wrapper(*args,**kwargs):#通过传进来的函数func,再通过wrapper进行 修饰。
#注意:*args可以接受通配没有名字的任何类型参数,如2,3;**kwargs可以接受通配任何有名字的参数,如a=5等,即这两个参数可以通配所有函数的任意类型任意数量的参数
return '<p>'+func(*args,**kwargs)+'</p>'#这里注意保留函数原来的参数不变,即传进来的函数func原来有什么参数就还是什么参数
return wrapper#外层函数返回wrapper函数本身这个对象即引用
@decorate#不管是有参数还是无参数都可以通配,因为*args,**kwargs可以通配任意数量类型的参数
def getupper(text):
return text.upper()#将传进来的字符串字母大写。
if __name__=='__main__':
#这个结果为wrapper函数对象,所以再对整体加上括号即可运行wrapper函数了。即
print(getupper('www.upkt'))
<p>WWW.UPKT</p>
用类定义的装饰器
实例1 首先函数定义的装饰器不但可以用在普通的.py脚本中的普通函数上,还可以用在一个类包含的方法上,如下代码:
def decorate(func):#这个函数要对哪个函数进行修饰,即将哪个函数作为对象传进来
def wrapper(*args,**kwargs):#通过传进来的函数func,再通过wrapper进行 修饰。
#注意:*args可以接受通配没有名字的任何类型参数,如2,3;**kwargs可以接受通配任何有名字的参数,如a=5等,即这两个参数可以通配所有函数的任意类型任意数量的参数
return '<p>'+func(*args,**kwargs)+'</p>'#这里注意保留函数原来的参数不变,即传进来的函数func原来有什么参数就还是什么参数
return wrapper#外层函数返回wrapper函数本身这个对象即引用
class student:
def __init__(self,name):
self.name=name
@decorate
def getname(self):
return self.name.upper()
if __name__=='__main__':
s=student('mike')
print(s.getname())#结果为<p>MIKE</p>
实例2 ,类所定义的装饰器可以用在普通脚本中的函数中,但是用在另一个类的方法中会有问题,因为区分不了self到底是谁;实际self是给装饰器中的类用的,而不是给定义的另一个类student中的实例去用,即解决起来困难,不建议用。
class P:
def __init__(self,func):#将函数作为参数传进来
self.func=func
def __call__(self, *args, **kwargs):#这个为调用函数,相当于函数装饰器上的wrapper函数的作用
return '<p>'+self.func(*args,**kwargs)+'</p>'
@P
def getupper(text):
return text.upper() # 将传进来的字符串字母大写。
if __name__=='__main__':
print(getupper('www.upkt'))
测试结果:
<p>WWW.UPKT</p>
在函数或类装饰器上的通用模板
主要解决因为只是加了个标签
,但是还想加其他标签呢如div,又必须写一个装饰器,麻烦,能否将标签作为一个参数传进来呢?
装饰器参数
让装饰器变得更通用一些,即可以加入参数,相当于在外层在套一层函数。
实例1
def decorate(func):
def wrapper(*args,**kwargs):
return '<p>'+func(*args,**kwargs)+'</p>'
return wrapper#外层函数返回wrapper函数本身这个对象即引用
def decorate2(func):
def wrapper1(*args,**kwargs):
return '<div>{}</div>'.format( func(*args, **kwargs))
return wrapper1#外层函数返回wrapper1函数本身这个对象即引用
#加双层装饰器
@decorate2
@decorate#多层装饰器,先执行靠近函数的,再依次执行向外的
def getupper(text):
return text.upper()#将传进来的字符串字母大写。
if __name__=='__main__':
print(getupper('www.upkt'))
测试结果:
<div><p>WWW.UPKT</p></div>
实例2,再加一层标签函数
def tags(tag):#外层再加入一层传装饰的标签的参数
def decorate2(func):
def wrapper1(*args,**kwargs):
return '<{}>{}</{}>'.format(tag, func(*args, **kwargs),tag)
# return f'<tag>{func(*args, **kwargs)}</tag>'#两种格式等价
return wrapper1
return decorate2
@tags('div')
@tags('p')
def getupper(text):
return text.upper()#将传进来的字符串字母大写。
if __name__=='__main__':
print(getupper('www.upkt'))
测试结果:
<div><p>WWW.UPKT</p></div>