编程学习网 > 编程语言 > Python > Python教程:掌握这三个方法,提升Python运行效率!
2024
08-07

Python教程:掌握这三个方法,提升Python运行效率!

在本文中,将主要介绍三个方法,提升你Python代码的运行效率。这三个方法,主要是“线程池,多线程,异步协程”。而笔者在本文中,将结合爬虫代码案例(某视频平台)进行介绍。废话不多说,直接往下看。

1. 线程池
自Python 3.2版本起,Python标准库中引入了功能强大的concurrent.futures模块,该模块旨在简化并发编程。它通过提供ThreadPoolExecutor(线程池执行器)和ProcessPoolExecutor(进程池执行器)两个核心类,极大地简化了异步执行任务的复杂度。这两个类允许开发者以几乎同步代码的方式编写并发程序,从而提高了程序执行效率和资源利用率。ThreadPoolExecutor专注于利用多线程来并行处理任务,适用于I/O密集型任务;而ProcessPoolExecutor则通过创建多个进程来并行执行任务,对于CPU密集型任务特别有效。
想象一下,你面前摆放着20张洁白无瑕的白纸,你的目标是将它们一一变成精美的千纸鹤。如果采用单线程的方式,这就好比是你独自一人,一张接一张地细心折叠,每完成一只千纸鹤,再继续下一张,直至全部完成。这种方式虽然专注且细致,但效率受限于你个人的速度。
然而,当你采用线程池的方式时,这场景就生动了起来。你不再孤军奋战,而是化身为一个高效的任务分配者。你请来了几位心灵手巧的朋友,一起加入这场千纸鹤制作大赛。你负责统筹全局,将折叠千纸鹤的任务(即执行函数的过程)一一分配给在座的每一个人。他们各自独立工作,同时进行折叠,大大加快了整体进度。这样,原本需要很长时间才能完成的20只千纸鹤,在团队的努力下迅速成型,既保持了质量,又提高了效率。这正是线程池工作的精髓所在:通过并行处理,实现任务的快速分发与执行。
# 导入模块
from concurrent.futures import ThreadPoolExecutor
# 省略代码
# 创建含20个线程的线程池
pool = ThreadPoolExecutor(20)
for i in range(len(hrefs)):
    # 把函数交给线程池执行,第一个参数为函数名,后面参数为函数参数
    pool.submit(download, hrefs[i], titles[i])
# shutdown(wait=True) 方法用于关闭线程池,并等待所有正在执行的任务完成。
pool.shutdown(wait=True)
示例代码如下:
import re
import requests
import os
from concurrent.futures import ThreadPoolExecutor
def download(url, title):
    resp = requests.get(url).content
    with open(os.path.join(file, title)+'.mp4', mode='wb') as f:
        f.write(resp)
# 注意:这里仅以某url为例
url = "https://www.ks.com/graphql"
headers = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
    "cookie": "ks.server.web_st=ChZrdWFpc2hvdS5zZXJ2ZXIud2ViLnN0EqABAnIA_qfxzqcsIUIvmlRBU_xBQ20ZCJHrSHC_O5DVQsyGOAfzQs2fMUXSLz_hAj7aG-SThYW7PkI0of1oML7X4mF0iXksoFZYG3M7ZtSrYd7_YCZzyc7oHQHKz8K4ILDuLvdATNf38pvuQAVMB7X5uHOTBy05GWF4C1HEdPhyp8JixZFUJfK2sYD6xf9DBYoEzDAwAqFZpFN13CHHkVhUVBoS6uws2LN-siMyPVYdMaXTUH7FIiB9JasFaAfLeFF5ZJpxdw9gSUXwaX2Bf3M-Xip-tDsLVigFMAE; kpf=PC_WEB; clientid=3; did=web_7ad4c176ff423b0a5c149bccba54205a; didv=1722580694294; kpn=KUAISHOU_VISION",
}
data = {"operationName":"visionSearchPhoto","variables":{"keyword":"甜妹","pcursor":"1","page":"search"},"query":"fragment photoContent on PhotoEntity {\n  __typename\n  id\n  duration\n  caption\n  originCaption\n  likeCount\n  viewCount\n  commentCount\n  realLikeCount\n  coverUrl\n  photoUrl\n  photoH265Url\n  manifest\n  manifestH265\n  videoResource\n  coverUrls {\n    url\n    __typename\n  }\n  timestamp\n  expTag\n  animatedCoverUrl\n  distance\n  videoRatio\n  liked\n  stereoType\n  profileUserTopPhoto\n  musicBlocked\n  riskTagContent\n  riskTagUrl\n  version\n}\n\nfragment recoPhotoFragment on recoPhotoEntity {\n  __typename\n  id\n  duration\n  caption\n  originCaption\n  likeCount\n  viewCount\n  commentCount\n  realLikeCount\n  coverUrl\n  photoUrl\n  photoH265Url\n  manifest\n  manifestH265\n  videoResource\n  coverUrls {\n    url\n    __typename\n  }\n  timestamp\n  expTag\n  animatedCoverUrl\n  distance\n  videoRatio\n  liked\n  stereoType\n  profileUserTopPhoto\n  musicBlocked\n  riskTagContent\n  riskTagUrl\n}\n\nfragment feedContent on Feed {\n  type\n  author {\n    id\n    name\n    headerUrl\n    following\n    headerUrls {\n      url\n      __typename\n    }\n    __typename\n  }\n  photo {\n    ...photoContent\n    ...recoPhotoFragment\n    __typename\n  }\n  canAddComment\n  llsid\n  status\n  currentPcursor\n  tags {\n    type\n    name\n    __typename\n  }\n  __typename\n}\n\nquery visionSearchPhoto($keyword: String, $pcursor: String, $searchSessionId: String, $page: String, $webPageArea: String) {\n  visionSearchPhoto(keyword: $keyword, pcursor: $pcursor, searchSessionId: $searchSessionId, page: $page, webPageArea: $webPageArea) {\n    result\n    llsid\n    webPageArea\n    feeds {\n      ...feedContent\n      __typename\n    }\n    searchSessionId\n    pcursor\n    aladdinBanner {\n      imgUrl\n      link\n      __typename\n    }\n    __typename\n  }\n}\n"}
resp = requests.post(url, headers=headers, json=data).json()
# print(resp)
hrefs = [i['photo']['manifest']['adaptationSet'][0]['representation'][0]['url'] for i in resp['data']['visionSearchPhoto']['feeds']]
titles = [i['photo']['caption'] for i in resp['data']['visionSearchPhoto']['feeds']]
titles = [re.sub(r'[\\/:*?"<>|\n]', '_', title) for title in titles]
file = "某手视频"
# 如果文件夹不存在,则创建该文件夹
if not os.path.exists(file):
    os.mkdir(file)
# 创建含20个线程的线程池
pool = ThreadPoolExecutor(20)
for i in range(len(hrefs)):
    # 把函数交给线程池执行,第一个参数为函数名,后面参数为函数参数
    pool.submit(download, hrefs[i], titles[i])
# 等待所有线程执行结束后关闭线程池
pool.shutdown(wait=True)
2. 多线程
多线程与线程池类似,threading一次创建一个线程。
在Python中,利用threading模块可以方便地创建和管理线程。首先,通过import threading语句导入线程模块。随后,使用threading.Thread类来实例化一个线程对象,这个类的构造函数至少需要一个参数:target,它指定了当线程启动时应当调用的函数或方法。
在Thread类的实例化过程中,target参数接收一个可调用对象(通常是函数名),作为线程的主要任务。此外,args和kwargs是两个可选参数,分别用于传递位置参数和关键字参数给target指定的函数。其中,args应是一个元组(tuple),即使只有一个参数,也需要加上逗号以表明其元组身份;而kwargs则是一个字典(dict),用于传递命名参数。
创建了线程对象后,通过调用其start()方法来启动线程的执行。这个方法会安排线程对象所代表的线程执行target参数指定的函数。
为了等待线程执行完成,可以使用join()方法。join()方法是一种同步机制,它会阻塞调用它的线程(通常是主线程),直到被调用的线程(即执行join()方法的线程对象所代表的线程)执行完毕。join()方法还可以接受一个可选的超时时间(以秒为单位),如果设置了超时时间,则主线程会等待指定的时间,如果在这段时间内被调用的线程没有结束,则主线程会继续执行,而被调用的线程则可能仍在后台运行。
综上所述,threading.Thread类的实例化与操作提供了一种灵活而强大的方式来并行执行任务,通过合理利用线程池(尽管这里直接讨论的是单个线程的创建与管理),可以显著提高程序的执行效率和响应速度。
示例代码如下:
import threading
# 初始化一个空列表,用于存储所有子线程的引用
sub_threads = []
# 假设 hrefs 和 titles 是两个列表,分别存储了网页的链接和标题
# 循环遍历 hrefs 列表的长度
for i in range(len(hrefs)):
    # 创建一个子线程,用于下载操作
    # threading.Thread 是创建线程的函数,target 参数指定线程执行的函数
    # args 参数是一个元组,包含了传递给 download 函数的参数
    sub_thread = threading.Thread(target=download, args=(hrefs[i], titles[i]))
    # 将创建的子线程添加到 sub_threads 列表中,以便之后可以管理它们
    sub_threads.append(sub_thread)
    # 设置子线程为守护线程,当主线程结束时,所有守护线程都将自动结束
    sub_thread.daemon = True
    # 启动子线程,使其开始执行
    sub_thread.start()
# 循环遍历 sub_threads 列表,等待所有子线程执行完毕
# i.join() 表示等待线程 i 执行完成
for i in sub_threads:
    i.join()
3. 异步协程
异步协程的概念,可以生动地类比于日常生活中的多任务并行处理场景。想象一下,在烹饪的情境中,当你使用电饭煲开始煮饭时,这段时间并非只是单调的等待。聪明的厨师会利用这段“等待时间”并行地进行其他烹饪任务,比如切菜、炒菜或是准备调料,从而最大化地利用时间资源,提高整体烹饪效率。
同样地,在编程领域,尤其是面对网络请求等可能产生较长时间延迟的操作时,传统的单线程程序往往会陷入“阻塞”状态,即程序在执行到这类操作时,会暂停当前任务,直到操作完成(如等待服务器响应)才能继续。这种“傻等”不仅浪费了时间,也降低了程序的响应能力和整体性能。
而异步协程则提供了一种更为高效的处理方式。它允许在同一线程内,以非阻塞的方式并发执行多个任务。当程序发起一个网络请求时,异步协程会立即将该请求提交给系统处理,并立即返回执行权,使得程序可以继续执行后续的代码,如处理其他请求、进行数据处理等。一旦网络请求完成,异步协程会以一种高效的方式通知程序,并允许程序在合适的时候处理响应结果。
通过这种方式,异步协程实现了类似多线程或多进程的并发效果,但相比之下,它在内存消耗、上下文切换成本等方面具有显著优势。因此,异步协程成为现代编程中处理I/O密集型任务(如网络请求、文件读写等)的一种重要且高效的手段。
示例代码如下:
import re
import requests
import os
import asyncio
import aiohttp
import aiofiles
# 定义异步下载函数
async def download(session, url, title):
    # 使用 session.get 获取 URL 内容
    async with session.get(url) as response:
        resp = await response.read()
        # 将获取的内容写入文件,文件名为标题,格式为.mp4
        async with aiofiles.open(os.path.join(file, title)+'.mp4', mode='wb') as f:
            await f.write(resp)
# 定义主函数
async def main(hrefs, titles):
    # 创建任务列表
    tasks = []
    # 使用 aiohttp 创建一个异步会话
    async with aiohttp.ClientSession() as session:
        # 遍历 hrefs 和 titles 列表,为每个 URL 创建下载任务
        for i in range(len(hrefs)):
            task = asyncio.create_task(download(session, hrefs[i], titles[i]))
            tasks.append(task)
        # 等待所有任务完成
        await asyncio.gather(*tasks)
        # for task in tasks:
        #     await task
# 注意:这里仅以某url为例
url = "https://www.ks.com/graphql"
headers = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
    "cookie": "ks.server.web_st=ChZrdWFpc2hvdS5zZXJ2ZXIud2ViLnN0EqABAnIA_qfxzqcsIUIvmlRBU_xBQ20ZCJHrSHC_O5DVQsyGOAfzQs2fMUXSLz_hAj7aG-SThYW7PkI0of1oML7X4mF0iXksoFZYG3M7ZtSrYd7_YCZzyc7oHQHKz8K4ILDuLvdATNf38pvuQAVMB7X5uHOTBy05GWF4C1HEdPhyp8JixZFUJfK2sYD6xf9DBYoEzDAwAqFZpFN13CHHkVhUVBoS6uws2LN-siMyPVYdMaXTUH7FIiB9JasFaAfLeFF5ZJpxdw9gSUXwaX2Bf3M-Xip-tDsLVigFMAE; kpf=PC_WEB; clientid=3; did=web_7ad4c176ff423b0a5c149bccba54205a; didv=1722580694294; kpn=KUAISHOU_VISION",
}
data = {"operationName":"visionSearchPhoto","variables":{"keyword":"甜妹","pcursor":"10","page":"search"},"query":"fragment photoContent on PhotoEntity {\n  __typename\n  id\n  duration\n  caption\n  originCaption\n  likeCount\n  viewCount\n  commentCount\n  realLikeCount\n  coverUrl\n  photoUrl\n  photoH265Url\n  manifest\n  manifestH265\n  videoResource\n  coverUrls {\n    url\n    __typename\n  }\n  timestamp\n  expTag\n  animatedCoverUrl\n  distance\n  videoRatio\n  liked\n  stereoType\n  profileUserTopPhoto\n  musicBlocked\n  riskTagContent\n  riskTagUrl\n  version\n}\n\nfragment recoPhotoFragment on recoPhotoEntity {\n  __typename\n  id\n  duration\n  caption\n  originCaption\n  likeCount\n  viewCount\n  commentCount\n  realLikeCount\n  coverUrl\n  photoUrl\n  photoH265Url\n  manifest\n  manifestH265\n  videoResource\n  coverUrls {\n    url\n    __typename\n  }\n  timestamp\n  expTag\n  animatedCoverUrl\n  distance\n  videoRatio\n  liked\n  stereoType\n  profileUserTopPhoto\n  musicBlocked\n  riskTagContent\n  riskTagUrl\n}\n\nfragment feedContent on Feed {\n  type\n  author {\n    id\n    name\n    headerUrl\n    following\n    headerUrls {\n      url\n      __typename\n    }\n    __typename\n  }\n  photo {\n    ...photoContent\n    ...recoPhotoFragment\n    __typename\n  }\n  canAddComment\n  llsid\n  status\n  currentPcursor\n  tags {\n    type\n    name\n    __typename\n  }\n  __typename\n}\n\nquery visionSearchPhoto($keyword: String, $pcursor: String, $searchSessionId: String, $page: String, $webPageArea: String) {\n  visionSearchPhoto(keyword: $keyword, pcursor: $pcursor, searchSessionId: $searchSessionId, page: $page, webPageArea: $webPageArea) {\n    result\n    llsid\n    webPageArea\n    feeds {\n      ...feedContent\n      __typename\n    }\n    searchSessionId\n    pcursor\n    aladdinBanner {\n      imgUrl\n      link\n      __typename\n    }\n    __typename\n  }\n}\n"}
resp = requests.post(url, headers=headers, json=data).json()
# print(resp)
hrefs = [i['photo']['manifest']['adaptationSet'][0]['representation'][0]['url'] for i in resp['data']['visionSearchPhoto']['feeds']]
titles = [i['photo']['caption'] for i in resp['data']['visionSearchPhoto']['feeds']]
titles = [re.sub(r'[\\/:*?"<>|\n]', '_', title) for title in titles]
file = "某手视频"
# 如果文件夹不存在,则创建该文件夹
if not os.path.exists(file):
    os.mkdir(file)
# 设置异步事件循环策略
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
# 运行主函数

asyncio.run(main(hrefs, titles))

以上就是Python教程:掌握这三个方法,提升Python运行效率!的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取