2013-02-12 3 views
5

Sto costruendo una piattaforma molto semplice sotto forma di un modulo Python 2.7. Questo modulo ha un ciclo read-eval-print dove i comandi utente inseriti sono mappati alle chiamate di funzione. Poiché sto cercando di semplificare la compilazione dei moduli di plugin per la mia piattaforma, le chiamate alle funzioni saranno dal mio modulo principale a un modulo di plugin arbitrario. Mi piacerebbe che un plugin builder fosse in grado di specificare il comando che desidera attivare la sua funzione, quindi ho cercato un modo Pythonic per inserire in remoto una mappatura nella funzione command-> dict nel modulo Main dal modulo pluginLa maggior parte dei metodi Pythonic per fornire i metadati della funzione in fase di compilazione?

Ho guardato diverse cose:

  1. Nome metodo di analisi: il modulo principale sarebbe importare il modulo plug- ed eseguire la scansione per i nomi dei metodi che corrispondono a un determinato formato. Per esempio , potrebbe aggiungere il metodo download_file_command (file) al suo dict come "download file" -> download_file_command. Tuttavia, ottenere un numero di comando conciso, facile da digitare (ad esempio "dl") richiede che il nome della funzione sia anche breve, il che non va bene per la leggibilità del codice . Richiede inoltre che lo sviluppatore del plugin sia conforme a un formato di denominazione preciso .

  2. decoratori Cross-modulo: decoratori avrebbero lasciato lo sviluppatore del plugin nominare il suo funzione di quello che vuole e semplicemente aggiungere qualcosa di simile @ Main.register ("dl"), ma avrebbero necessariamente richiedere che io sia modificare lo spazio dei nomi di un altro modulo e mantenere lo stato globale nel modulo principale. Capisco che sia molto brutto.

  3. decoratori con lo stesso modulo: utilizzando la stessa logica di cui sopra, ho potuto aggiungere un decoratore che aggiunge il nome della funzione di qualche comando nome-> funzione di mappatura locale al modulo plug- e recuperare la mappatura al Modulo principale con una chiamata API . Ciò richiede che alcuni metodi siano sempre presenti o ereditati da e, se la mia comprensione dei decoratori è corretta, la funzione si registrerà solo la prima volta che verrà eseguita e si registrerà inutilmente ogni volta successiva, .

Così, quello che ho veramente bisogno è un modo Pythonic per annotare una funzione con il nome del comando che deve innescare, e in questo modo non può essere il nome della funzione. Devo essere in grado di estrarre il nome del comando-> mappatura delle funzioni quando imparo il modulo, e qualsiasi lavoro in meno sul lato dello sviluppatore del plugin è un grande vantaggio.

Grazie per l'aiuto, e le mie scuse se ci sono dei difetti nella mia comprensione di Python; Sono relativamente nuovo alla lingua.

+1

Proprio controllando compreso questo, ma decoratori (e funzione definizioni) eseguite in fase di esecuzione, l'unica cosa che Python fa al momento della compilazione è compilare il codice. – Duncan

+1

Non c'è niente di sbagliato con il metodo 2. Fagli fare qualcosa come 'import plugin' e fai' @ plugin.register ('my_method_name') \ ndef somefunc_meeting_an_api_spec(): ... ' –

+0

Potresti voler dare un'occhiata a come [plac] (http://pypi.python.org/pypi/plac) lo fa. – georg

risposta

4

Le funzioni definite dall'utente possono avere attributi arbitrari. Quindi potresti specificare che le funzioni plug-in hanno un attributo con un certo nome. Per esempio:

Poi, nel modulo si potrebbe costruire una mappatura come questo:

import inspect #from standard library 

import plugin 

mapping = {} 

for v in plugin.__dict__.itervalues(): 
    if inspect.isfunction(v) and v.hasattr('command_name'): 
     mapping[v.command_name] = v 

per leggere gli attributi arbitrari per le funzioni definite dall'utente vedere the docs

+0

Questo è esattamente il tipo di cosa che stavo cercando! – mieubrisse

+0

Mi rendo conto che questa domanda è un po 'vecchia, ma vedi la mia domanda correlata qui http://stackoverflow.com/questions/37192712/how-can-attributes-on-functions-survive-wrapping. Hai avuto problemi con il wrapping utilizzando gli attributi memorizzati sulle funzioni? – matthewatabet

1

ci sono due parti in un sistema di plugin:

  1. Scoprite i plugin
  2. trigger l'esecuzione di codice in un plugin

Le soluzioni proposte nella Vostra questione solo la seconda parte.

Ci molti modi per implementare entrambi a seconda delle vostre esigenze per esempio, per abilitare i plugin, potrebbero essere specificate in un file di configurazione per l'applicazione:

plugins = some_package.plugin_for_your_app 
    another_plugin_module 
    # ... 

Implementare caricamento dei moduli plugin:

plugins = [importlib.import_module(name) for name in config.get("plugins")] 

Per ottenere un dizionario: command name -> function:

commands = {name: func 
      for plugin in plugins 
      for name, func in plugin.get_commands().items()} 

L'autore del plug-in può utilizzare qualsiasi metodo per implementare get_commands(), ad esempio, utilizzando prefissi o decoratori: l'applicazione principale non dovrebbe interessare a condizione che lo get_commands() restituisca il dizionario dei comandi per ciascun plug-in.

Ad esempio, some_plugin.py (sorgente completo):

def f(a, b): 
    return a + b 

def get_commands(): 
    return {"add": f, "multiply": lambda x,y: x*y} 

e definisce due comandi add, multiply.

+0

Grazie; Mi piace la spiegazione del design che sta dietro a questo. Questa spiegazione suggerisce che una superclasse che implementa l'elenco get_commands() potrebbe essere una buona idea, quindi gli sviluppatori devono fare il minor lavoro possibile. – mieubrisse

+0

@mieubrisse: non sono richieste classi. Ho aggiunto l'esempio completo del plugin. – jfs

4

costruzione o in piedi su la prima parte della risposta di @ ericstalbot, potresti trovare conveniente usare un decoratore come il seguente.

################################################################################ 
import functools 
def register(command_name): 
    def wrapped(fn): 
     @functools.wraps(fn) 
     def wrapped_f(*args, **kwargs): 
      return fn(*args, **kwargs) 
     wrapped_f.__doc__ += "(command=%s)" % command_name 
     wrapped_f.command_name = command_name 
     return wrapped_f 
    return wrapped 
################################################################################ 
@register('cp') 
def copy_all_the_files(*args, **kwargs): 
    """Copy many files.""" 
    print "copy_all_the_files:", args, kwargs 
################################################################################ 

print "Command Name: ", copy_all_the_files.command_name 
print "Docstring : ", copy_all_the_files.__doc__ 

copy_all_the_files("a", "b", keep=True) 

uscita quando viene eseguito:

Command Name: cp 
Docstring : Copy many files.(command=cp) 
copy_all_the_files: ('a', 'b') {'keep': True}