Python实现Plugin
1. Plugin与Python
插件的历史最早可追溯至1970年代,它是一种程序组件,通过和应用程序的互动,为应用程序增加一些所需要的特定的功能[维基]。插件允许第三方开发者对已有的程序功能进行扩展和完善,具体的例子包括音频播放软件的解码器、浏览器的视频播放插件等。插件需要按照一定的接口规范与应用程序互动,这个规范是调用它的应用程序定义的。
为了在实践上应用,我看了两篇搜索比较靠前的博客文章,它们的代码比较精简,都是通过python package来实现的。
本文组织如下。第二部分介绍python package;第三部分介绍上面两篇博客中实现插件的方法。
2. Python packages
Package是python用名字空间组织module的一种方法,通过"."将各级路径连接起来,以"A.B.C"的形式组成层次结构。每一个python文件是一个module,包含module的目录可以组成一个package,包含package的目录又可以进一步组成更高级的package。
2.1 package的基本结构
不是所有的目录都会被解释为package,其充要条件是目录内包含一个名为__init__.py的文件,这个文件可以是空文件,也可以包含一些定义和初始化代码,包含该文件的目录可以被import。如下目录可以被作为package使用,本例中除了__init__.py文件外,还包含三个module。
myprint/
|---__init__.py
|---print_int.py
|---print_float.py
|---print_complex.py
因为myprint目录下有__init__.py文件,myprint是一个package,可以在python代码或命令行中import它及它包含的module。
import myprint # import了一个package
或
import myprint.print_int # import了package下的一个module
或
from myprint import print_int # import了package下的一个module,引用时可以不带package name
2.2 subpackage
Packages可以形成多层嵌套的结构,tensorflow代码中经常看到import多层的情况。下例中tensorflow、python、framework都是上一层namespace中的package,只有ops是module。
import tensorflow.python.framework.ops
如果有import A.B.C.D
,只有D可以是module,A、B、C都必须是package。
2.3 __init__.py文件
文件__init__.py让python将其所属目录视为package,该文件可以是空文件,也可以包含python代码,文件内的代码在package第一次被进程import时执行一次。文件__init__.py中定义的函数、变量,可以通过package name被外部引用。如果上面例子中myprint/__init__.py包含如下代码,则default_int和default_float两个名字可以通过myprint.default_int
和myprint.default_float
访问。
__all__ = ['print_int', 'print_float']
default_int = 0
default_float = 0.0
import myprint
myprint.print_int
myprint.print_float
如果import的是一个subpackage,则路径上所有package的__init__.py文件会被依次执行。例如,当import A.B.C.D
时,A package、B package、C package会被先后初始化,如果D也是package,则D package也会被初始化。
__init__.py文件中还可以定义__all__变量,如上面代码中有__all__ = ['print_int', 'print_float']
,当通过代码from myprint import *
导入package时,只有__all__中定义的所有name会被导入,而不是把myprint/目录下的所有东西都导入。__all__中定义的name是package下的module或subpackage。
下面介绍的两种实现插件的方法,都用到了__init__.py文件的__all__变量。
3. Python实现的Plugin
下面两篇博客都各自实现了插件结构,都是通过python的package实现,插件被实现为一个module,并列放置在package内,插件的作者需要修改package的__init__.py文件中的__all__变量。
不同之处在于。第一篇博客只是将所有plugin import进来,使用时需要提供plugin的module name,然后通过eval方式引用。第二篇博客有一个反向注册的机制,在插件module被import时,将plugin及其名字注册到一个公共变量中。
我在实际工作中,需要实现一个清理字符串的package,需要的功能可能随着遇到的CASE逐渐完善。因此考虑将所有字符串清理函数串成一个序列结构,依次执行其中的每个功能。我仿照第二篇博客实现,稍微做了一些改动,因为我既需要插件按顺序全部执行,也需要能指定只执行某插件。
3.1 字符串清理Plugin
目录结构如下,package名为text_processor,这是使用方需要import的那个package。具体的处理逻辑在plugins目录下,因为plugins目录下有一个__init__.py文件,所以它也是一个package。
text_processor
|---__init__.py
|---text_processor.py
|---plugins
|---__init__.py
|---clean_dupspace.py
|---clean_htmltag.py
|---clean_punctuation.py
|---clean_q2b.py
从上到下依次看每个文件的内容,首先是text_processor/__init__.py文件。这个文件只有一行语句,从当前目录下的plugins package导入所有模块。所以,当我们通过import text_processor
导入text_processor时,就会执行下面这条语句,plugins下的模块也会被导入。
from .plugins import *
接下来是text_processor/text_processor.py文件。这个module是用户需要使用的唯一接口,它有一个普通方法,一个类方法。类方法返回一个闭包,该闭包可以作为类的decorator,来修饰每个plugin module内定义的类。普通方法是用户调用的,用来处理字符串。
class TextProcessor(object):
PLUGIN_NAMES = []
PLUGINS = {}
def process(self, text, plugins=()):
if plugins is ():
for plugin_name in self.PLUGIN_NAMES:
text = self.PLUGINS[plugin_name]().process(text)
else:
for plugin_name in plugins:
text = self.PLUGINS[plugin_name]().process(text)
return text
@classmethod
def plugin_register(cls, plugin_name):
def wrapper(plugin):
cls.PLUGINS.update({plugin_name:plugin})
cls.PLUGIN_NAMES.append(plugin_name)
return plugin
return wrapper
接下来是text_processor/plugins/__init__.py文件。这个文件只是定义__all__变量,指示了plugins这个package,当通过from plugins import *
导入时,都具体导入哪些module。text_processor/plugins/__init__.py文件中的__all__变量,与text_processor/__init__.py文件中的from .plugins import *
协作,共同完成对所有插件的导入。
__all__ = ['clean_q2b', 'clean_nbsp, 'clean_htmltag, 'clean_dupspace, 'clean_punctuation']
然后我们以text_processor/plugins/plugins/clean_dupspace.py为例,说明插件是如何与上层package互动的。第一句导入上层的TextProcessor,是为了调用该类的类方法plugin_register。
from ..textProcessor import TextProcessor
import re
re_space = re.compile(u'\s+')
@TextProcessor.plugin_register('cleanDupSpace')
class CleanDupSpace(object):
def process(self, text):
cleanText = re_space.sub(u' ', text)
cleanText = cleanText.replace(u"\u3000", ' ')
cleanText = cleanText.strip()
return cleanText
这个文件重点需要关注的是用@TextProcessor.plugin_register('cleanDupSpace')
修饰了类CleanDupSpace的定义。decorator是Python的一个语法特性,相当于做了下面的事情。
class CleanDupSpace(object):
def process(self, text):
cleanText = re_space.sub(u' ', text)
cleanText = cleanText.replace(u"\u3000", ' ')
cleanText = cleanText.strip()
return cleanText
CleanDupSpace = TextProcessor.plugin_register('cleanDupSpace')(CleanDupSpace)
@
符号后面的字符串TextProcessor.plugin_register('cleanDupSpace')
,eval
后应该是个wrapper函数。该wrapper只有一个参数,就是修饰符后定义的东西,并将修饰后的东西(类、函数)赋给原来那个变量CleanDupSpace
。这个逻辑是在类定义,也就是import当前module时完成的。
最后,使用该package的方法如下。
from text_processor.text_processor import TextProcessor
tp = TextProcessor()
cleanText = tp.process(rawText)
3.2 其他实现
在Github上还找到一个更复杂的plugin实现,pluginbase。这个代码实现我没有仔细看。
Python实现Plugin的更多相关文章
- Python实现Plugin(插件化开发)
https://www.cnblogs.com/terencezhou/p/10276167.html
- 使用Python开发chrome插件
本文由 伯乐在线 - xianhu 翻译,Daetalus 校稿.未经许可,禁止转载!英文出处:pythonspot.com.欢迎加入翻译小组. 谷歌Chrome插件是使用HTML.JavaScrip ...
- HRPlugin For Xcode发布(附源码地址)
今天给大家介绍的这个插件,是我在IOS平台上开发以来,一些想法的集合体.因为本人时常感觉在开发过程中无论从GOOGLE资料查找和SQL数据库查询,正则表达式测试,SVN等,这些经常要做的操作中,耽误了 ...
- VIM使用(一) VIM插件管理利器-vundle
有关VIM的文件网上一大堆,这里只是记录一下我新配置环境的步骤.以备查看参考. sudo apt-get install gitgit clone https://github.com/gmarik/ ...
- elasticsearch插件大全
Elasticsearch扩展性非常好,有很多官方和第三方开发的插件,下面以分词.同步.数据传输.脚本支持.站点.其它这几个类别进行划分. 分词插件 Combo Analysis Plugin (作者 ...
- Notepad++ Emmet安装方法教程
Notepad++ Emmet安装后出现 unknown exception提示插件无效Python Script Plugin did not accept the script.以下为记录解决方法 ...
- 安装elasticsearch
安装elasticsearch 来自:http://www.cnblogs.com/huangfox/p/3541300.html 一)安装elasticsearch 1)下载elasticsea ...
- vim下使用YouCompleteMe实现代码提示、补全以及跳转设置
配置YouCompleteMe 1. 安装vundle vundle是一个管理vim插件的工具,使用vundle安装YouCompleteMe比较方便. 按照作者在https://github.com ...
- vim目录说明
plugin.autoload.ftplugin有什么区别 很多初用vim的朋友在安装插件时都会有些疑惑.同样的插件,有些教程说安装在plugin目录,有些说安装在ftplugin目录,有些说安装在a ...
随机推荐
- 关于miniconda的安装,配置以及包批量安装和使用
由于时间很晚了.就不写废话了. conda官方文档地址:http://conda.pydata.org/docs/ 一切其实都可以从miniconda的文档找到,这里只纪录自己操作的时候遇到的值得一说 ...
- BZOJ4012 HNOI2015开店(树链剖分+主席树)
考虑这样一个问题:一棵树初始全是白点,有两种操作:把一个点染黑:询问某点到所有黑点的距离之和. 注意到树上两点x和y的距离为depth[x]+depth[y]-depth[lca(x,y)]*2.要求 ...
- easyui webapi
今天算是踩雷了.... 先说一下,由于项目需要,我目前开发PO模块, 由于需要提供手机端,所以我在mvc项目中创建了 webapi.提供手机端调用. 然后我就考虑,easyui也使用webapi来提 ...
- Django实现websocket完成实时通讯,聊天室,在线客服等
一 什么是Websocket WebSocket是一种在单个TCP连接上进行全双工通信的协议 WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在WebS ...
- pipreqs 组件
作用 帮助你查询所有需要用的组件 安装 pip install pipreqs 使用 """ 切换到项目的路径下 执行查询当前文件下所需要的所有的组件 会生成一个 r ...
- Leetcode 189.旋转数组 By Python
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数. 示例 1: 输入: [1,2,3,4,5,6,7] 和 k = 3 输出: [5,6,7,1,2,3,4] 解释: 向右旋转 1 ...
- HTML 百度地图API调用示例源码
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- Android ListView 列表视图
列表显示的三个元素 ListView : 用来显示列表的View Adapter : 适配器 用来把数据映射到ListView 上的中介 Data: 数据 将被映射的字符串,图片或者基本组件等资源 根 ...
- 神奇的操作——线段树合并(例题: BZOJ2212)
什么是线段树合并? 首先你需要动态开点的线段树.(对每个节点维护左儿子.右儿子.存储的数据,然后要修改某儿子所在的区间中的数据的时候再创建该节点.) 考虑这样一个问题: 你现在有两棵权值线段树(大概是 ...
- A1053. Path of Equal Weight
Given a non-empty tree with root R, and with weight Wi assigned to each tree node Ti. The weight of ...