Cómo diseñar un programa con estructura de complemento y hablar sobre el lenguaje Python
Para ampliar las funciones del software, generalmente diseñamos el software en una estructura de complemento. Los lenguajes dinámicos como Python admiten inherentemente la programación de complementos. En comparación con C++, Python ya ha definido la interfaz del módulo. Si desea cargar un complemento, puede hacerlo fácilmente con __import__(). No se requieren conocimientos subyacentes específicos. Y en comparación con lenguajes estáticos como C++, la estructura de complementos de Python es más flexible. Porque una vez cargado el complemento, la dinámica del lenguaje Python se puede utilizar para modificar completamente la lógica central.
Puede que no quede claro decir simplemente __import__(). Ahora veamos el programa de estructura de complemento más simple. Escaneará todos los archivos .py en la carpeta de complementos. Luego cárgalos.
#-*- codificación: utf-8 -*-#main1.pyimport osclass Plataforma:
def __init__(self):
self.loadPlugins( )
def decirHola(self, from_):
imprimir "hola de %s." % from_
def loadPlugins(self):
para el nombre de archivo en os.listdir("plugins"):
si no es nombre de archivo.endswith(".py") o nombre de archivo.startswith("_"):
continuar
self.runPlugin(nombre de archivo)
def runPlugin(self, nombre de archivo):
pluginName=os.path.splitext(nombre de archivo)[0]
plugin=__import__("plugins."+pluginName, fromlist=[pluginName])
#Es posible que se produzcan errores. Manéjelo usted mismo.
plugin.run. (self)if __name__=="__main__":
platform=Platform()
Luego coloque dos archivos en el subdirectorio de complementos:
# plugins1.pydef ejecutar(plataforma):
plataforma.sayHello("plugin1")#plugins2.pydef ejecutar(plataforma):
plataforma.sayHello("plugin2")
Cree un __init__.py vacío en la carpeta de complementos. Al importar un módulo desde un paquete, Python requiere un __init__.py.
Ejecute main1.py y vea los resultados.
Primero, imprima la estructura de carpetas para que todos la comprendan:
h:\projects\workon\testplugins>árbol /f /a
Lista de RUTA de carpetas de datos de volumen
El número de serie del volumen es ****-****
H:.
| main1.py
|
\---plugins
plugin1.py
plugin2.py
__init__.py
h:\projects\workon \testplugins>main1.py
hola desde el complemento1.
hola desde el complemento2.
Generalmente, antes de cargar el complemento, primero debe escanear el complemento y luego cárguelo en secuencia. Inicie sesión y ejecute el complemento. Lo mismo ocurre con nuestro programa de muestra main1.py anterior, que está dividido en dos funciones. El primer loadPlugins() escanea complementos. Trata todos los archivos .py en el directorio de complementos excepto __init__.py como complementos. runPlugin() carga y ejecuta el complemento. Dos de las claves: utilizar la función __import__() para importar complementos como módulos, lo que requiere que todos los complementos definan una función run(). La estructura del complemento implementada en varios idiomas se divide básicamente en estos dos pasos. La diferencia es que el lenguaje Python es más sencillo de implementar.
Tal vez suene un poco misterioso. Hablemos de __import__() en detalle. Es muy similar a la declaración de importación común, excepto que se cambia a un formato de función y devuelve el módulo para llamar. importar módulo es equivalente a __import__("module"), desde el módulo import func es equivalente a __import__("module", fromlist=["func"]), pero es un poco diferente de la imaginación, import package.module es equivalente a __import__(" paquete.módulo", fromlist=["módulo"]).
Generalmente existe un acuerdo sobre cómo llamar a los complementos. Como aquí, acordamos que cada complemento implementa una ejecución(). A veces, también puede aceptar implementar una clase y exigir que esta clase implemente una determinada interfaz de administración para facilitar que el núcleo inicie y detenga el complemento en cualquier momento.
Todos los complementos deben tener estos métodos de interfaz:
#interfaces.pyclass Complemento:
def setPlatform(self, platform):
self.platform =plataforma
def inicio(self):
pasar
def stop(self):
pasar
Para ejecutar este complemento, necesitamos cambiar nuestro runPlugin() y agregar un apagado() para detener el complemento:
clase Plataforma:
def __init__(self ):
self.plugins=[]
self.loadPlugins()
def decirHola(self, from_):
imprimir "hola de %s." % from_
def loadPlugins(self):
para el nombre del archivo en os.listdir("plugins"):
si no nombre de archivo. termina con(".py") o nombre de archivo.comienza con("_"):
continuar
self.runPlugin(nombre de archivo)
def runPlugin( self, nombre de archivo):
pluginName=os.path.splitext(filename)[0]
plugin=__import__("plugins."+pluginName, fromlist=[pluginName])
clazz=plugin.getPluginClass()
o=clazz()
o.setPlatform(self)
o.start() p>
self.plugins.append(o)
def apagado(self):
para o en self.plugins:
o .stop ()
o.setPlatform(Ninguno)
self.plugins=[]if __name__=="__main__":
plataforma=Plataforma() p>
platform.shutdown()
Cambie el complemento a esto:
#plugins1.pyclass Complemento1:
def setPlatform( self, plataforma):
self.platform=plataforma
def start(self):
self.platform.sayHello("plugin1")
def stop(self):
self.platform.sayGoodbye("plugin1")def getPluginClass():
return Plugin1#plugins2.pydef sayGoodbye(self, from_ ):
imprimir "adiós de %s." % from_class Plugin2:
def setPlatform(self, plataforma):
self.platfo
rm=plataforma
si la plataforma no es Ninguna:
plataforma.__class__.sayGoodbye=sayGoodbye
def start(self):
self.platform.sayHello("plugin2")
def stop(self):
self.platform.sayGoodbye("plugin2")def getPluginClass():
devolver Plugin2
Resultado de ejecución:
h:\projects\workon\testplugins>main.py
hola desde el complemento1.
hola del complemento2.
adiós del complemento1.
adiós del complemento2.
Los amigos que observen en detalle pueden encontrar que el main.py, complemento1 anterior. py, plugin2.py hace varias cosas sorprendentes.
En primer lugar, las clases de complemento en plugin1.py y plugin2.py no heredan de interfaces.Plugin, y la plataforma aún puede llamar a sus métodos start() y stop() directamente. Este asunto puede ser problemático en Java y C ++, pero en Python es algo escaso y ordinario, tan normal como comer y beber. De hecho, este es exactamente el tipo de programación convencional que fomenta Python. El protocolo de interfaz de archivos de Python solo estipula algunos métodos: leer (), escribir () y cerrar (). La mayoría de las funciones que toman un archivo como parámetro pueden pasar un objeto de archivo personalizado. Solo necesita implementar uno o dos de los métodos sin tener que implementar una FileInterface. En ese caso, habrá muchas funciones que deberán implementarse, tal vez más de una docena.
Mirando con atención, getPluginClass() puede devolver el tipo como valor. De hecho, no solo los tipos, sino también las funciones y módulos de Python se pueden utilizar como objetos ordinarios. Generar una instancia a partir de un tipo también es muy simple. Simplemente llame a clazz() para crear un objeto. No sólo eso, Python también puede modificar tipos. En el ejemplo anterior, demostramos cómo agregar un método a la plataforma. Llamamos a sayGoodbye() en stop() de ambos complementos, pero si miramos detenidamente la definición de Plataforma, no hay ninguna definición en ella. El principio está aquí:
#plugins2.pydef decirAdiós(self, from_):
imprimir "adiós de %s." % from_class Plugin2:
def setPlatform(self, plataforma):
self.platform=plataforma
si la plataforma no es Ninguna:
plataforma.__class__.sayGoodbye=sayGoodbye
Aquí primero obtenemos el tipo de plataforma a través de platform.__class__ y luego agregamos un nuevo método Platform.sayGoodbye=sayGoodbye. Con este método, podemos dejar que el complemento modifique la lógica central de forma arbitraria. Esta es la flexibilidad de la estructura de complementos de Python mencionada al principio del artículo, que no tiene comparación con lenguajes estáticos como C ++ y Java. Por supuesto, esto es solo una demostración. No recomiendo usar este método. Cambia la API principal y puede causar confusión a otros programadores.
Sin embargo, este método se puede utilizar para reemplazar el método original y también se puede utilizar la "programación orientada a aspectos" para mejorar la funcionalidad del sistema.
A continuación, tenemos que mejorar el método de carga de complementos o el método de implementación de complementos. La principal desventaja del sistema de complementos que implementamos anteriormente es que cada complemento solo puede tener un código fuente. Si desea adjuntar algunas imágenes y datos de sonido, teme que entren en conflicto con otros complementos. Incluso si no hay conflicto, es inconveniente separarlos en archivos separados durante la descarga. Es mejor comprimir un complemento en un archivo para descargarlo e instalarlo.
Firefox es un software muy conocido que admite complementos. Su complemento tiene una extensión .xpi y en realidad es un archivo .zip, que contiene código JavaScript, archivos de datos y muchos otros contenidos. Descargará, copiará y descomprimirá el paquete del complemento en %APPDATA%\Mozilla\Firefox\Profiles\XXXX.default\extensions y luego llamará a install.js para instalarlo. De manera similar, es poco probable que un programa Python práctico tenga un solo código fuente y debe admitir el formato de paquete .zip como Firefox.
No es difícil implementar un sistema de implementación de complementos similar a Firefox, porque Python admite la lectura y escritura de archivos .zip. Solo necesita escribir unas pocas líneas de código para comprimir y descomprimir. Primero debemos echar un vistazo al módulo zipfile. El código para usarlo para descomprimir es el siguiente:
importar archivo zip, osdef installPlugin(nombre de archivo):
con zipfile.ZipFile(nombre de archivo) como complementozip:
subdir= os.path.splitext(nombre de archivo)[0]
topath=os.path.join("plugins", subdir)
pluginzip.extractall(topath) p>
ZipFile.extractall() es una nueva función agregada después de Python 2.6. Descomprime directamente todos los archivos del paquete comprimido. Sin embargo, esta función sólo se puede utilizar para archivos confiables. Si el paquete comprimido contiene una ruta absoluta que comienza con / o una letra de unidad, es probable que dañe el sistema. Se recomienda leer la documentación del módulo zipfile y filtrar los nombres de rutas ilegales con anticipación.
Aquí solo hay una pequeña porción de código descomprimido. Hay mucho código relacionado con la interacción de la interfaz durante el proceso de instalación, por lo que es imposible dar un ejemplo aquí. Creo que la interfaz de usuario es una parte muy desafiante para los diseñadores de software. El software común requiere que los usuarios vayan al sitio web para buscar y descargar complementos. Firefox y KDE proporcionan una "interfaz de administración de componentes (partes)". Los usuarios pueden buscar directamente complementos en la interfaz, ver su descripción y luego hacer clic para instalarlos directamente. Después de la instalación, nuestro programa recorre el directorio de complementos y carga todos los complementos. En general, el software también debe proporcionar a los usuarios funciones de habilitación, deshabilitación, dependencia y otras funciones de complementos, e incluso permite a los usuarios calificar complementos directamente en la interfaz del software, lo cual no se detallará aquí.
Un pequeño truco es que el complemento instalado en complementos/subdirectorio puede obtener su propia ruta absoluta a través de __file__. Si este complemento contiene imágenes, sonidos y otros datos, puede utilizar esta función para cargarlos.
Por ejemplo, si el plug-in1.py anterior quiere reproducir message.wav en el mismo directorio cuando se inicia, puede ser así:
#plugins1.pyimport osdef alert():
soundFile=os.path.join(os.path.dirname(__file__), "message.wav")
intenta:
importar winsound
winsound .PlaySound(soundFile, winsound.SND_FILENAME)
excepto (ImportError, RuntimeError):
passclass Plugin1:
def setPlatform(self, platform) :
p>self.platform=plataforma
def start(self):
self.platform.sayHello("plugin1")
alert()
def stop(self):
self.platform.sayGoodbye("plugin1")def getPluginClass():
return Plugin1
A continuación, presentaremos un método de administración de complementos comúnmente utilizado en el lenguaje Python/Java. No requiere un proceso de descompresión del complemento por adelantado, porque Python admite la importación de módulos desde archivos .zp, al igual que Java carga código directamente desde archivos .jar. Para instalarlo, simplemente copie el complemento en un directorio específico y el código Python escaneará y cargará automáticamente el código desde el archivo .zip. El siguiente es el ejemplo más simple que, al igual que los ejemplos anteriores, contiene un main.py, que es el programa principal, y un subdirectorio de complementos para almacenar complementos. Aquí solo tenemos un complemento, llamado plugin1.zip.
plugin1.zip tiene los siguientes dos archivos, de los cuales descripción.txt guarda información como la función de entrada en el complemento y el nombre del complemento, mientras que plugin1.py es el código principal del complemento:
descripción.txt
plugin1.py
El contenido de descripción.txt es:
[general]name=plugin1description=Solo un código de prueba=plugin1.Plugin1
plugin1.py es similar al ejemplo anterior Para evitar problemas, hemos eliminado el método stop(). Su contenido es:
clase Plugin1:
self.platform=plataforma
def start(self):
self.platform.sayHello("plugin1" )
El contenido del main.py reescrito es:
# -*- codificación: utf-8 -*-import os, zipfile, sys, ConfigParserclass Plataforma:
def __init__(self ):
self.loadPlugins()
def sayHello(self, from_):
imprimir "hola de %s ." % from_
def loadPlugins(self):
para el nombre del archivo en os.listdir("plugins"):
si no es el nombre del archivo.endswith(" .zip"):
continuar
self.runPlugin(nombre de archivo)
def runPlugin(self, nombre de archivo):
pluginPath= os.path.join("plugins ", nombre de archivo)
pluginInfo, plugin = self.getPlugin(pluginPath)
imprimir "cargando complemento: %s, descripción: %s" % \ (pluginInfo["nombre"] , pluginInfo["descripción"])
plugin.setPlatform(self)
plugin.start()
def getPlugin (self, pluginPath):
p>
pluginzip=zipfile.ZipFile(pluginPath, "r")
description_txt=pluginzip.open("description.txt")
parser=ConfigParser.ConfigParser()
parser.readfp(description_txt)
pluginInfo={}
pluginInfo["nombre"] =parser.get("general", "nombre")
pluginInfo["descripción"]=parser.get("general", "descripción")
pluginInfo[
"code"]=parser.get("general", "code")
sys.path.append(pluginPath)
moduleName, pluginClassName=pluginInfo["code"]. rsplit(".", 1)
módulo=__import__(nombre del módulo, fromlist=[nombre de clase del complemento, ])
pluginClass=getattr(módulo, nombre de clase del complemento)
plugin=pluginClass()
return pluginInfo, pluginif __name__=="__main__":
platform=Platform()
La principal diferencia con el ejemplo anterior es obtenerPlugin(). Primero lee la información de descripción del archivo .zip y luego agrega el archivo .zip a sys.path. Finalmente, importe el módulo y ejecútelo de manera similar al anterior.
Descomprimir o no descomprimir, ambas opciones tienen sus propias ventajas y desventajas. Generalmente, descomprimir un archivo .zip en una carpeta separada requiere un proceso de descompresión, ya sea manualmente o mediante software. La eficiencia operativa después de la descompresión será mayor. Si usa el paquete .zip directamente, solo necesita pedirle al usuario que copie el complemento en una ubicación específica, pero debe descomprimirlo en la memoria cada vez que se ejecuta, lo que reduce la eficiencia. Además, leer datos de archivos .zip siempre resulta engorroso. Se recomienda utilizar cuando no se incluyen archivos de datos.
Leer el texto completo