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_intmyprint.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。这个代码实现我没有仔细看。

原文地址:https://www.cnblogs.com/terencezhou/p/10276167.html

时间: 2024-08-29 03:18:39

Python实现Plugin的相关文章

Vim - python autocomplete plugin

pydiction简介 pydiction是一段python代码自动补全的脚本,它可以实现如下功能: 1.python关键字补全 2.函数补全 3.模块补全 4.模块内部函数.变量补全 5.被引入模块的子模块补全 下载地址:http://vim.sourceforge.net/scripts/script.php?script_id=850 Vim配置 版本要求: vim - 7.0以上 pydiction - 1.2以上 [[email protected] ~]# mkdir -p ~/.v

使用Python开发chrome插件

本文由 伯乐在线 - xianhu 翻译,Daetalus 校稿.未经许可,禁止转载!英文出处:pythonspot.com.欢迎加入翻译小组. 谷歌Chrome插件是使用HTML.JavaScript和CSS编写的.如果你之前从来没有写过Chrome插件,我建议你读一下这个.在这篇教程中,我们将教你如何使用Python代替JavaScript. 创建一个谷歌Chrome插件 首先,我们必须创建一个清单文件:manifest.json. 1 2 3 4 5 6 7 8 9 10 11 12 13

debian 系统python+uwsgi+nginx实现web环境

1,python web部署的实现: python+uwsgi+nginx实现web. 本文测试环境: 服务器:树莓派B+ 操作系统: [email protected]:~# cat /etc/issue Raspbian GNU/Linux 7 \n \l IP: [email protected]:~# ip ad s 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN      link/loopbac

elasticsearch(0.90.10)安装配置

一)安装elasticsearch 1)下载elasticsearch-0.90.10,解压,运行\bin\elasticsearch.bat (windwos) 2)进入http://localhost:9200/ 如下图 安装成功! 二)插件——head elasticsearch-head是一个elasticsearch的集群管理工具,它是完全由html5编写的独立网页程序,你可以通过插件把它集成到es. 安装命令:\bin>plugin -install mobz/elasticsear

elasticsearch插件大全

Elasticsearch扩展性非常好,有很多官方和第三方开发的插件,下面以分词.同步.数据传输.脚本支持.站点.其它这几个类别进行划分. 分词插件 Combo Analysis Plugin (作者 Olivier Favre, Yakaz) 简介:组合分词器,可以把多个分词器的结果组合在一起. Smart Chinese Analysis Plugin (作者 elasticsearch 团队) 简介:lucene默认的中文分词器 ICU Analysis plugin (作者 elasti

vim目录说明

plugin.autoload.ftplugin有什么区别 很多初用vim的朋友在安装插件时都会有些疑惑.同样的插件,有些教程说安装在plugin目录,有些说安装在ftplugin目录,有些说安装在autoload目录,到底这些目录的区别是什么呢?今天阿驹跟大家解说一下. 一般,我们安装好vim后,应该会创建一个用户vim文件夹,里面的子目录结构和原vim的目录结构几乎一样,例如在windows平台下这个名称是vimfiles,在unix类平台下是~/.vim.但它们的子目录结构都类似下面这样:

Vim使用YouCompleteMe达到类似IDE的代码提示、补全,以及其他实用设置

接触Linux有两年了,vim还是只会简单的操作.最近实在受不了sublime的代码提示,决定花点时间来配置下vim.本文讲自己认为方便的vim配置,称不上完美,只讲究简单实用. 使用 ctags 主要参考   http://roclinux.cn/?p=424 1.  ctags根据代码产生索引文件,支持ava, c, c++, python, perl, php, ruby, scheme, tcl, vim, yacc, bash/kron shell/zsh, cobol, awk, a

vim下使用YouCompleteMe实现代码提示、补全以及跳转设置

配置YouCompleteMe 1. 安装vundle vundle是一个管理vim插件的工具,使用vundle安装YouCompleteMe比较方便. 按照作者在https://github.com/gmarik/Vundle.vim#about提供的方法安装好vundle.(主要是下载解压,病略微修改 .vimrc) 2. 使用vundle安装YouCompleteMe 主要根据作者自己的建议整理 https://github.com/Valloric/YouCompleteMe 在.vim

mac 下的新玩具

tools zsh 为啥需要zsh,因为能更快更方便的进行代码提示与代码补全,每个人的精力都是有限的,能打一个字母出来结果就绝对不打第二个. plugins=(git brew mvn svn svn-fast-info sbt go pip zsh-syntax-highlighting npm) vim vim是大二学操作系统的时候看老师折腾红帽的时候看到的,发现这个编辑器很有b格,而且花花绿绿的很好看,后面就投入了一段时间,后面会贴出一些常用的插件,现在因为主力编程语言是scala,所以我