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. package
when 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.zip
There 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.txt
the contents are:
[General]name=plugin1description=just a Test code=plugin1. Plugin1
plugin1.py
Similar 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