惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 三生石上(FineUI控件)
云风的 BLOG
云风的 BLOG
C
Check Point Blog
Engineering at Meta
Engineering at Meta
Y
Y Combinator Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Forbes - Security
Forbes - Security
IT之家
IT之家
L
LINUX DO - 最新话题
N
News and Events Feed by Topic
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
雷峰网
雷峰网
N
News | PayPal Newsroom
The Last Watchdog
The Last Watchdog
V
Visual Studio Blog
月光博客
月光博客
Microsoft Azure Blog
Microsoft Azure Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Webroot Blog
Webroot Blog
TaoSecurity Blog
TaoSecurity Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Schneier on Security
Schneier on Security
P
Privacy International News Feed
G
Google Developers Blog
博客园 - 聂微东
博客园 - 叶小钗
M
MIT News - Artificial intelligence
Apple Machine Learning Research
Apple Machine Learning Research
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
WordPress大学
WordPress大学
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Simon Willison's Weblog
Simon Willison's Weblog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
宝玉的分享
宝玉的分享
H
Hacker News: Front Page
Martin Fowler
Martin Fowler
L
Lohrmann on Cybersecurity
G
GRAHAM CLULEY
酷 壳 – CoolShell
酷 壳 – CoolShell
罗磊的独立博客
T
The Exploit Database - CXSecurity.com
S
Security @ Cisco Blogs
博客园_首页
AWS News Blog
AWS News Blog
P
Proofpoint News Feed
人人都是产品经理
人人都是产品经理
Help Net Security
Help Net Security
Google DeepMind News
Google DeepMind News
T
Threatpost

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角 .NET Core 原生 DI 扩展之基于名称的注入实现 - 元视角
温故而知新,再话 Python 动态导入 - 元视角
飞鸿踏雪 · 2023-05-30 · via 元视角

多年前,我曾写过一篇关于 Python 动态导入的文章,当时想要解决的问题是,如何通过动态导入 Python 脚本来实现插件机制,即整个应用程序由主程序和插件两部分组成,主程序通过 importlib 模块中的 import_module 方法动态地导入一个 Python 脚本,最终通过 getattrsetattr 等方法实现反射调用。时过境迁,代码还是那些代码,江湖故人早已不知所踪。我向来都是一个喜欢怀旧的人,我怀念的是那些遗忘在寒江孤影里的江湖故人,我怀念的是那些湮灭在时光尘埃里的代码片段。或许,在屏幕前的你看来,一个每天都在经历着“更新换代”的技术人员,更应该对这一切的消逝习以为常。可正如这世界上的风、沙、星辰等流动的事物一样,无论我们愿意与否,时间总会在不经意间将那些熟悉而珍贵的东西一一带走,不放弃对过去的回忆和珍视,这便是我在世事变幻的洪流中追求的安宁与平静。正所谓“温故而知新”,今天我想要怀旧的话题是 Python 里的动态导入。

众所周知,这段时间我一直在开发基于 ChatGPT 的人工智能管家 Jarvis,在整个探索过程中,类似语音识别、语音合成这些领域,博主先后考察了微软、百度、腾讯…这些大厂的方案,这可以说是非常符合我作为 Python “调包侠” 的人设啦!以语音识别为例,最终,你可能会得到类似下面这样的代码:

class ASREngineFactory:
    @staticmethod
    def create(config, type):
        if type == ASREngineProvider.Baidu:
            return BaiduASR(config['BAIDU_APP_ID'], config['BAIDU_API_KEY'], config['BAIDU_SECRET_KEY'])
        elif type == ASREngineProvider.PaddleSpeech:
            return PaddleSpeechASR()
        elif type == ASREngineProvider.OpenAIWhisper:
            return WhisperASR()

没错,非常经典的简单工厂模式,你只需要告诉工厂类,你需要使用哪种语音识别引擎,它就可以自动帮你创建出对应的示例,如下图所示,这看起来非常合理,对吧?

config = load_config_from_env(env_file)
engine = ASREngineFactory.create(config, ASREngineProvider.PaddleSpeech)

这里,其实有一段小插曲,博主最近开始尝试使用 virtualenv 来管理不同的 Python 版本,这样做的好处是,我只需要在不同的工作场所拉取代码、激活环境,就可以享受到完全一样的开发环境。当然,这一切都只是理论上的,实际使用下来的感受是,它并不能完全抹平环境上的差异。譬如,当我试图在个人电脑上安装 PaddleSpeechRasa 这两个库时,依然免不了遇到各种错误,即使是在同一个 Python 环境下。

此时,你会发现一个非常尴尬的问题,即使我不使用 PaddleSpeech 来作为 Jarvis 的语音识别引擎,它依然无法正常工作,原因是我环境中没有安装 PaddleSpeech,我不得不注释掉项目中所有和 PaddleSpeech 有关的代码,而这一切的根源其实是,我们在代码中使用了静态导入的方式,如下图所示:

# baidu-aip
from aip import AipSpeech
# paddlespeech
from paddlespeech.cli.asr.infer import ASRExecutor
# openai-whisper
import whisper

我相信,这个代码在通常情况下是没有任何问题的,可凡事都有例外,有没有一种可能,我们可以像使用 C# 里的 #if DEBUG#if NET40 等预处理指令一样,让它按照不同的条件去导入不同的模块呢?比如,当我使用 Whisper 时,我希望它只导入 whisper 模块,而当我使用 PaddleSpeech 时,我希望它只导入 paddlespeech.cli.asr.infer 模块下的 ASRExecutor 类。换言之,我希望实现两个目的,其一是按需导入,只导入需要的模块。其二是延迟导入,使用的时候再导入

延时摄影技术纪录下的星空 延时摄影技术纪录下的星空

好了,既然一切问题的根源是静态导入,那么,我们的思路就是将其调整为动态导入,此时,我们需要祭出大杀器 importlib,这里以 baidu-aip 这个包为例:

class BaiduASR:
    def __init__(self, APP_ID, API_KEY, SECRET_KEY):
        aip = None
        try:
            aip = importlib.import_module('aip')
        except ImportError as e:
            print("baidu-aip is required, run 'pip install baidu-aip' first")
        self.client = aip.AipSpeech(APP_ID, API_KEY, SECRET_KEY)
        self.recoginzer = sr.Recognizer()
        # ......

可以注意到,主要的改动在第 5 行,因为 AipSpeechaip 这个模块中的一个类,所以,我们可以在动态导入 aip 模块后直接使用该类型。当然,这个方案会损失一点点编程体验,因为 IDE 的智能提示可能会失效。考虑到使用者不一定安装了这个库,我们可以在异常处理中提醒对方安装这个库,这是我从开源社区学会的一个小技巧😀。当然,你还需要删除此前静态导入部分的代码片段:

from aip import AipSpeech

现在,当我需要使用某一个语音识别引擎时,我只需要给 ASREngineFactory 传入一个类型,它将会在创建实例的时候动态导入对应的模块。这样,即使我没有安装 PaddleSpeech,它丝毫不影响我使用 baidu-aip 或者 openai-whisper 这两个库,这样听起来更合理一点,不是吗?

import sys

plugins = ['plugin1', 'plugin2', 'plugin3']
for plugin in plugins:
    __import__(plugin)
    sys.modules[plugin].run()

除了这种方式以外,对于此前博主讨论过的插件化问题,我们还可以使用 __import__方式,它同样可以实现类似的效果。如图所示,假设我们有三个插件 plugin1plugin2plugin3,它们各自拥有一个叫做 run() 的方法,此时,我们可以通过 __import__ 这个内置的函数来动态地导入插件。按照 Python 模块的缓存机制,每个模块只会被导入一次。首先,它会去检查 sys.modules 中是否存在该模块,只有当该模块不存在的时候,它才会去检索和导入该模块。因此,我们可以从 sys.modules 调用该模块的 run() 方法。当然,这个方案最大的问题是,需要手动处理模块名称字符串,特别是当你为插件引入模块或者是包的概念的时候。