Python-based plug-in system architecture test

Source: Internet
Author: User


Since Python supports runtime dynamic loading, designing a plug-in structure is relatively straightforward. If you use PYQT, you can easily create a plug-in UI structure. However, in many cases, the main program is written using C++/stl, which implements the plug-in extension through Python. This article focuses on the "pure Python" implementation of the plug-in structure. C++python mode is followed (for reference, C + + embedded python:http://www.vckbase.com/index.php/wv/1258,c++ embedded Python essentials:/http blog.chinaunix.net/uid-13148801-id-2906720.html).

If you want to write a Python module in C + + to load, you can refer to: http://my.oschina.net/u/2306127/blog/369997.

In order to expand the functionality of the software, we usually design the software into a plug-in structure. Dynamic languages such as Python are inherently support for plug-in programming. Compared to C + +, Python has already defined the interface of the module, and to load a plug-in, one __import__() can easily be done. No specific underlying knowledge is required. Python's plug-in architecture is more flexible than static languages such as C + +. Since the plug-in is loaded, the dynamic nature of the Python language can be used to fully modify the core logic.

It may not be clear to say one simple word __import__() . Now let's look at one of the simplest plug-in architecture programs. It scans plugins all files under the folder .py . And then load them in.

#-*- encoding: utf-8 -*-#main1. Pyimport osclass platform:    def  __init__ (self):         self.loadplugins ()      def sayhello (self, from_):        print  "Hello  from %s. "  % from_    def loadplugins (self):         for filename in os.listdir ("plugins"):             if not filename.endswith (". Py")  or filename.startswith ("_"):                 continue             self.runplugin (filename)      def runplugin (self, filename):         pluginname= Os.path.spliteXT (filename) [0]        plugin=__import__ ("Plugins.") +pluginname, fromlist=[pluginname])          #Errors  may  Be occured. handle it yourself.        plugin.run ( Self) if __name__== "__main__":     platform=platform ()

Then plugins put two files in the subdirectory:

#plugins1. Pydef Run (Platform): Platform.sayhello ("Plugin1") #plugins2. Pydef Run (Platform): Platform.sayhello (" Plugin2 ")

Then create an empty __init__.py plugins folder inside. packagewhen importing modules from the inside, Python asks for one __init__.py .

Run main1.py and look at the results of the run. The first is to print the folder structure for the convenience of people understand:

h:\projects\workon\testplugins>tree/f/A Volume Data folder PATH list volume serial number is ****-****h:.|  Main1.py|\---plugins plugin1.py plugin2.py __init__.pyh:\projects\workon\testplugins>main1.pyhello From Plugin1.hello from Plugin2.

Generally, before loading the plug-in, you should first scan the plug-in and then load and run the plug-in. The same is true of our example program main1.py , which is divided into two functions. The first loadPlugins() scan plugin. It treats plugins all .py the files under the directory except as __init__.py plugins. runPlugin()load and run the plug-in. There are two keys: using __import__() a function to import a plugin as a module, it requires all plugins to define a run() function. The plug-in architecture implemented in various languages is basically divided into these two steps. What's different is that the Python language is more concise to implement.

Maybe it sounds a little mysterious. Say it in detail __import__() . It import is similar to a common statement, except that it is replaced by a function and returned to the module for invocation. Equivalent, import module __import__("module") from module import func __import__("module", fromlist=["func"]) But a little different from the imagination, the import package.module equivalent __import__("package.module", fromlist=["module"]) .

How to call a plugin generally has a convention. As we've agreed, each plugin implements one run() . Sometimes you can also agree to implement a class, and require this class to implement a management interface to facilitate the core at any time to start, stop plug-ins. All plugins are required to have these interface methods:

#interfaces. Pyclass plugin:def setplatform (self, Platform): Self.platform=platform def start (self): p The "I"-def Stop (self): Pass

Want to run this plugin, we runPlugin() want to change, add another shutdown() to stop the plugin:

Class platform:    def __init__ (self):         self.plugins=[]        self.loadplugins ()      def sayhello (self, from_):        print  "Hello  from %s. "  % from_    def loadplugins (self):         for filename in os.listdir ("plugins"):             if not filename.endswith (". Py")  or filename.startswith ("_"):                 continue             self.runplugin (filename)      def runplugin (self, filename):         pluginname= Os.path.splitEXT (filename) [0]        plugin=__import__ ("Plugins.") +pluginname, fromlist=[pluginname])         clazz= Plugin.getpluginclass ()         o=clazz ()          o.setplatform (self)         o.start ()          self.plugins.append (o)     def shutdown (self):         for o in self.plugins:             o.stop ()              o.setplatform (None)         self.plugins=[]if __ name__== "__main__":     platform=platform ()     platform.shutdown ()

The

Plugin changes to this:

#plugins1. Pyclass plugin1:    def setplatform (self, platform):         self.platform=platform    def start (self):         self.platform.sayhello ("Plugin1")     def  Stop (self):         self.platform.saygoodbye ("Plugin1") def  Getpluginclass ():     return plugin1#plugins2.pydef saygoodbye (SELF, FROM_):     print  "goodbye from %s."  % from_class plugin2:    def setplatform (Self, platform):         self.platform=platform         if platform is not None:             platform.__class__.saygoodbye=saygoodbye   &nbSp;def start (self):         self.platform.sayhello ("plugin2")     def stop (self):         Self.platform.sayGoodbye ("Plugin2") Def getpluginclass ():     return plugin2

Operation Result:

H:\projects\workon\testplugins>main.pyhello from Plugin1.hello from Plugin2.goodbye to Plugin1.goodbye from Plugin2.

The detailed observations of the friends may find, above, main.py a plugin1.py plugin2.py few amazing things done.

First, plugin1.py and plugin2.py the plug-in classes inside do not inherit from interfaces.Plugin , and platform still can call them directly start() and stop() methods. This matter in Java, C + + may be a troublesome thing, but in Python is a sparse ordinary thing, as if eating water is generally normal. In fact, this is exactly what Python encourages as a contract programming. Python's file interface protocol only prescribes a few read(), write(), close() methods. Most functions that use a file as a parameter can pass in a custom file object, as long as one or two of these methods are implemented without having to implement a single thing FileInterface . If that's the case, there are more functions to implement, maybe more than 10.

If you look closely, getPluginClass() you can return the type as a value. In fact, not only the type, Python functions, modules can be used as ordinary objects. It is also simple to generate an instance from a type, and a direct call clazz() creates an object. Not only that, Python can also modify the type. In the example above, we demonstrate how to Platform add a method. Within two plugins stop() we have all called sayGoodbye() , but the definition of careful observation is Platform not defined. Here's how it works:

#plugins2. Pydef Saygoodbye (Self, from_): print ' Goodbye from%s. '% From_class plugin2:def setplatform (self, PLATFO RM): Self.platform=platform If platform is not None:platform.__class__.saygoodbye=saygoodbye

This is done first by platform.__class__ getting the Platform type and then Platform.sayGoodbye=sayGoodbye adding a new method. Using this method, we can let the plugin arbitrarily modify the core logic. This is at the beginning of the text of the Python implementation of the plug-in structure of flexibility, static language such as C + +, Java and other incomparable. Of course, this is just a demo, and I'm not suggesting this approach, which changes the core API and may confuse other programmers. However, the original method can be replaced in this way, and the function of the system can be enhanced by using "aspect-oriented programming".

Next we need to improve the method of loading the plugin, or the deployment method of the plugin. The main drawback of the plug-in system we implemented earlier is that each plugin can have only one source code. If you want to attach some pictures, sound data, but also fear that they will conflict with other plugins. Even if it does not conflict, it is inconvenient to separate files when downloading. It is best to compress a plugin into a file for download and installation.

Firefox is a well-known software that supports plugins. Its plug-in .xpi as an extension, is actually a .zip file containing JavaScript code, data files and many other content. It will download the plug-in package to be copied and extracted into the %APPDATA%\Mozilla\Firefox\Profiles\XXXX.default\extensions inside, and then call the install.js installation. Similarly, a practical Python program is unlikely to have only one source code, as well as support for .zip package formats like Firefox.

Implementing a plug-in deployment system like Firefox is not difficult because Python supports reading and writing .zip files, just writing a few lines of code to do compression and decompression. First, take a look at zipfile this module. The code used to decompress it is as follows:

Import ZipFile, osdef installplugin (filename): With ZipFile.        ZipFile (filename) as pluginzip:subdir=os.path.splitext (filename) [0] Topath=os.path.join ("plugins", SubDir) Pluginzip.extractall (Topath)

ZipFile.extractall()is a new function after Python 2.6. It directly extracts all the files in the archive package. However, this function can only be used for trusted compressed packages. If the compressed package contains an / absolute path that starts with a letter or a drive, it is likely to damage the system. It is recommended to take a look at zipfile the documentation for the module and filter the illegal path names beforehand.

There is only a small piece of code to decompress, the interface of the installation process related to a lot of code, it is not possible to illustrate here. I think the UI is part of a great test of software designers. Common software requires users to find and download plugins on the site. and Firefox and KDE provide a "component (part) management interface", users can directly in the interface to find the plug-in, view its description, and then directly click Install. After installation, our program traverses the plugin directory and loads all plugins. In general, the software also needs to provide users with plug-in activation, disabling, reliance and other functions, and even let users directly in the software interface to the plug-in scoring, here is no longer detailed.

The

has a little trick that the plug-in installed under plugins/subdir can get its own absolute path through __file__ . If the plugin has images, sounds, and other data, you can use this function to load them. such as the above plugin1.py This plug-in, if it wants to play the same directory at the start of the message.wav , you can like this:

#plugins1. Pyimport osdef alert ():     soundfile=os.path.join (Os.path.dirname (__ file__),  "Message.wav")     try:        import  winsound        winsound. PlaySound (Soundfile, winsound. Snd_filename)     except  (importerror, runtimeerror):         passclass plugin1:    def setplatform (self,  Platform):         self.platform=platform    def  start (self):         self.platform.sayhello ("Plugin1")          alert ()     def stop (self):         self.platform.saygoodbye ("Plugin1") Def getpluginclass ():     return plugin1

Next, we introduce a kind of plug-in management method commonly used in Python/java language. It does not require a plugin decompression process beforehand, because Python supports .zp importing modules from a file, much like Java loading code directly from a .jar file. The so-called installation, simply copy the plugin to a specific directory, Python code automatically scans and .zip loads code from the file. Here is a simple example, like the above example, contains a main.py , this is the main program, a plugins subdirectory, for storing plugins. We have only one plugin here, named plugin1.zip . plugin1.zipThere are two files, which description.txt save the entry function inside the plugin and the name of the plug-in and other information, and plugin1.py is the main code of the plug-in:

description.txtplugin1.py

description.txtthe contents are:

[General]name=plugin1description=just a Test code=plugin1. Plugin1

plugin1.pySimilar to the previous example, for the sake of convenience, we removed the stop() method, its content is:

Class Plugin1:def Setplatform (self, Platform): Self.platform=platform def start (self): Self.platform. SayHello ("Plugin1")

The contents of the rewrite main.py are:

# -*- coding: utf-8 -*-import os, zipfile, sys, configparserclass  platform:    def __init__ (self):         self.loadplugins ()     def sayhello (self, from_):         print  "hello from %s."  % from_    def loadplugins (self):         for filename in os.listdir ("plugins"):             if not filename.endswith (". zip"):                 continue             self.runplugin (filename)     def runplugin (self,  FileName):         pluginpath=os.path.join ("Plugins",  filename)         pluginInfo, plugin =  Self.getplugin (Pluginpath)         print  "Loading plugin:  %s, description: %s " % \                  (plugininfo["name"], plugininfo["description"])          plugin.setplatform (self)          Plugin.start ()     def getplugin (Self, pluginpath):         pluginzip=zipfile. ZipFile (pluginpath,  "R")         description_txt=pluginzip.open (" Description.txt ")         parser=configparser.configparser ()          PARSER.READFP (description_txt)          plugininfo={}        plugininfo["Name"]=parser.get (" General ", " name ")         plugininfo[" description "]=parser.get (" General ", " description ")         plugininfo[" code "]=parser.get (" General ", " code ")         sys.path.append (Pluginpath)          modulename, pluginclassname=plugininfo["Code"].rsplit (".",  1)         module=__import__ (Modulename, fromlist=[pluginclassname,  ])         pluginclass=getattr (module, pluginclassname)         plugin=pluginclass ()          return plugininfo, pluginif __name__== "__main__":     platform= Platform ()

The main difference from the previous example is that getPlugin() . It first .zip reads the description information from the file and then .zip adds the file to it sys.path . Finally, import the module and execute it in the same way as before.

Decompression or no pressure, the two programs have advantages and disadvantages of each. In general, .zip extracting files into a separate folder requires a decompression process, either manually or by software decompression. The operation efficiency will be higher after decompression. In the case of direct use of the .zip package, you just need to let the user copy the plugin to a specific location, but each time the operation needs to be decompressed in the memory, efficiency is reduced. In addition, .zip reading data from a file is always a hassle. It is not recommended to use when no data files are included.


Python-based plug-in system architecture test

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.