首页 > 编程知识 正文

python插件开发(python开发软件界面)

时间:2023-05-06 08:19:50 阅读:87563 作者:1872

插件化APP是一个古老的话题,在我们的日常生活中更是屡见不鲜。 无论是多年臃肿的Eclipse、扩展性丰富而闻名的Chrome,还是近年来最优秀的编辑器VSCode,插件都是其重要的组成部分。 插件的意思是扩展APP的功能。 这其实和iPhone手机和AppStore的关系很像。 没有APP的手机只不过是手机。 拥有APP的手机将成为Everything。 当然,APP的安装和卸载不会影响手机的基本功能,但是APP应用离开手机也无法单独执行。 因此,“插件”实际上是按照一定规格开发的APP应用,只在特定的软件平台/APP应用中工作,不工作。 其中最重要的是,APP应用程序可以独立于插件单独运行。 这是这种“插件APP”的基本要求。

最近,我想为这个小程序添加壁纸和WallHaven这两个壁纸源。 考虑到大多数壁纸捕获的过程是相同的,博主决定用“插件”完成这个迭代。 换言之,主程序不需要进一步调整。 如果要添加新的数据源,只需编写一个. py脚本即可。 这就是我今天写这篇文章的原因。 如果使用类似于Java/C#的编译型语言执行类似的功能,则可能会考虑为插件定义IPlugin接口。 这样,每个插件实际上都是IPlugin接口的实现类,编译型语言的做法自然是通过反射来调用接口中的方法。 对于像Python这样的解释器型语言,我们也有解释器型语言的做法。

首先,从最简单的例子开始。 我们知道,Python导入语法可以用于部署Python标准库、第三方库和定制模块模块。 现在,假设您有两个模块: foo.py和bar.py。

# # foo.py

导入系统

类聊天:

def send (自发、uid、msg ) :

print ()向) uid发送消息。 (msg ) ) .格式) uid=uid,msg=msg )

联邦(自助,msg ) :

print ()群发消息: { msg } (.格式) msg=msg ) ) #bar.py

导入系统

第:类

德福赛(自助) :

print (人生短暂,我是Python ) ) )。

德芙克里(: )

print ('男人哭也不是罪' )通常,可以使用以下语句在当前模块(main.py )中使用这两个模块:

导入foo

from bar import *这是一种简单粗暴的方法,因为它导入模块中的所有内容。 一个更好的方法是按需加载。 例如,以下语句:

来这里,让我们先来看看第一个问题。 Python是怎么找模块的呢? 这与Python的导入路径有关,使用sys.path很容易找到。 典型的导入路径包括当前目录、site-package目录和python路径。 熟悉Python的人应该都知道,site-package和PYTHONPATH各自的意思是前者是用pip安装的模块的导入目录,后者是Python标准库的导入目录。 现在的商品目录从哪里开始呢? 实际上,从我们写from…import…的文章的时候开始,这个机制就已经开始工作了。 否则,Python应该找不到foo和bar这两个模块。 这里还有相对引进和绝对引进的问题,一点(.)和两点) .)的问题。 这些在这里按暂时不出现在表中的事。 因为,直接修改sys.path。

Python有动态导入模块的方式。 只需要告诉我模块名称、导入路径即可。 这是接下来要说明的导入lib标准库。 如果我们继续用foo和bar这个很棒的单词举例,现在不想用import这样的“静态”靠拢的方法导入模块,该怎么办呢? 让我们看看下面的代码。

导入foo

从导入图表

从栏导入*

导入导入lib

调用foo模块Chat类方法

foo.Chat ().send ) ' dear ',' I失误是' )

模块foo导入库.导入模块('.',' foo ' )

classchat=getattr (模块foo,' chat ' () ) ) ) ) ) ) ) ) )余下的值。) ) ) ) ) )一个侧边。) )

classChat ().send ) ' dear ',' I失误是' ) )。

调用bar模块的Echo类方法

Echo ().say ) ) ) ) )。

模块栏=导入lib .导入_模块('.','栏' )

class echo=getattr (模块栏,' echo ' ) ) ) ) )。

classEcho ().say ) ) ) ) )。

bar模块的cry (调用方法

cry () ) )

方法克里=获取(模块栏,“克里”) ) )。

方法克里() )

通过动态导入

我们在运行时期间引入一个模块(.py),这恰恰是我们需要的功能。为了让大家对比这两种方式上的差异,我给出了静态引入和动态引入的等价代码。其中,getattr()其实可以理解为Python中的反射,我们总是可以按照模块->类->方法的顺序来逐层查找,即:通过dir()方法,然后该怎么调用就怎么调用。所以,到这里整个“插件化”的思路就非常清晰了,即:首先,通过配置来为Python增加一个导入路径,这个导入路径本质上就是插件目录。其次,插件目录内的每一个脚本文件(.py)就是一个模块,每个模块都有一个相同的方法签名。最终,通过配置来决定要导入哪一个模块,然后调用模块中类的实例方法即可。顺着这个思路,为 WallPaper 项目引入了插件机制,核心代码如下:

if(pluginFile == '' or pluginName == ''): spider = UnsplashSpider() imageFile = spider.getImage(downloadFolder) setWallPaper(imageFile) else: if(not check(pluginFile,addonPath)): print('插件%s不存在或配置不正确' % pluginName) return module = importlib.import_module('.',pluginFile.replace('.py','')) instance = getattr(module,pluginName) imageFile = instance().getImage(downloadFolder) setWallPaper(imageFile)

接下来,我们可以很容易地扩展出 必应壁纸 和 WallHaven 两个“插件”。按照约定,这两个插件都必须实现getImage()方法,它接受一个下载目录作为参数,所以,显而易见,我们在这个插件里实现壁纸的下载,然后返回壁纸的路径即可,因为主程序会完成剩余设置壁纸的功能。

# 必应每日壁纸插件 class BingSpider: def getImage(self, downloadFolder): searchURL = 'https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN' response = requests.get(searchURL) data = json.loads(response.text) resultId = data['images'][0]['hsh'] resultURL = 'https://cn.bing.com' + data['images'][0]['url'] print(u'正在为您下载图片:%s...' % resultId) if(not path.exists(downloadFolder)): os.makedirs(downloadFolder) jpgFile = resultId + '.jpg' jpgFile = os.path.join(downloadFolder, jpgFile) response = requests.get(resultURL) with open(jpgFile,'wb') as file: file.write(response.content) return jpgFile # WallHaven壁纸插件 class WallHavenSpider: def getImage(self,downloadFolder): url = 'https://alpha.wallhaven.cc/wallpaper/' response = requests.get(url) print(response.text) soup = BeautifulSoup(response.text,'html.parser') imgs = soup.find_all('img') length = len(imgs) if length > 0: match = random.choice(imgs) rawUrl = match.get('src') rawId = rawUrl.split('/')[-1] rawUrl = 'https://w.wallhaven.cc/full/' + rawId[0:2] + '/wallhaven-' + rawId raw = requests.get(rawUrl) imgFile = os.path.join(downloadFolder, rawId) with open(imgFile,'wb') as f: f.write(raw.content) return imgFile

好了,现在功能是实现了,我们来继续深入“插件化”这个话题。考虑到Python是一门解释型的语言,我们在编写插件的时候,更希望做到“热插拔”,比如修改了某个插件后,希望它可以立刻生效,这个时候我们就需要重新加载模块,此时importlib的reload就能满足我们的要求,这正是博主一开始就要使用importlib,而不是import语法对应内建方法__import__()的原因。以C#的开发经历而言,虽然可以直接更换DLL实现更新,可更新的过程中IIS会被停掉,所以,这种并不能被称之为“热更新”。基于以上两点考虑,博主最终决定使用watchdog配合importlib来实现“热插拔”,下面是关键代码:

class LoggingEventHandler(FileSystemEventHandler): # 当配置文件修改时重新加载模块 # 为节省篇幅已对代码进行精简 def on_modified(self, event): super(LoggingEventHandler, self).on_modified(event) what = 'directory' if event.is_directory else 'file' confPath = os.path.join(sys.path[0],'config.ini') if(what =='file' and event.src_path == confPath): importlib.reload(module) logging.info("Modified %s: %s", what, event.src_path)

好了,现在我们就完成了这次“插件化”的迭代,截止到目前为止,博主共完成了 Unsplash 、 Bing壁纸 、 WallHaven 和 国家地理 四个“源”的接入,这些插件在实现上基本大同小异,本质上来讲它们是一个又一个的爬虫,只要实现了getImage()这个方法都可以接入进来,这就是我们通常说的“约定大于配置”,关于更多的代码细节,大家可以通过Github来了解。

简单回顾下这篇博客,核心其实是importlib模块的使用,它可以让我们在运行时期间动态导入一个模块,这是实现插件化的重要前提。以此为基础,我们设计了基于Python脚本的单文件插件,即从指定的目录加载脚本文件,每个脚本就是一个插件。而作为插件化的一个延伸,我们介绍了watchdog模块的简单应用,配合importlib模块的reload()方法,就可以实现所谓的“热更新”

关注大话编程,一起提升技能。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。