Flexibles Plugin-System mit entry-points

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Phenex
User
Beiträge: 6
Registriert: Freitag 20. Juni 2008, 16:25

Ich bin auf der Suche nach einer eleganten Möglichkeit, wie ich es Plugins ermöglichen kann sich an definierten Punkten in den Programmfluss einhaken zu können und diesen zu beinflussen. Dies wäre sehr praktisch für Webaplikationen aber ich brauche es momentan auch für eine andere Sache, daher poste ich ins allgemeine Forum. Die Idee habe ich von dem Boardsystem PunBB und dessen sehr coolem Plugin-System das folgendermaßen funktioniert.
Ich habe meinen Code und darin definiere ich sogenannte Hooks, das sieht dann so aus:

Code: Alles auswählen

[Code...]
($hook = get_hook('li_login_form_submitted')) ? eval($hook) : null;
[Code...] 
An dieser Stelle können dann Plugins ihren zusätlichen Code einhängen. Unschön ist natürlich das eval - das kommt daher, dass die Codergänzungen in einer XML-Datei stehen, das will ich natürlich nicht so machen.
Am liebsten wäre es mir daher, wenn ich an dieser Stelle Funktionen einhängen kann, die dann aufgerufen werden, mal grob ungefär so:

Code: Alles auswählen

a = 12
set_hook('some_hook_name')
b = a + 2
Und im Plugin irgendwie sowas:

Code: Alles auswählen

def myFunction():
  ...
hooks['some_hook_name'].append(myFunction)
Nur kann ich nicht so einfach auf Variablen in dem Namensraum außerhalb des Funktionsaufrufes zugreifen. global geht hier ja auch nicht, weil die Funktion an einer anderen Stelle definiert wird und das globals-array erlaubt laut Doku keinen Schreibzugriff.

Nun ist meine Frage ob jemand eine Idee hat dies elegant zu lösen.
Freue mich schon auf Vorschläge.
BlackJack

@Phenex: Man könnte der Hook-Funktion ja Argumente übergeben.
Phenex
User
Beiträge: 6
Registriert: Freitag 20. Juni 2008, 16:25

Das wäre irgendwie umständlich, außerdem hätte man dann auf die Attribute nur Lesezugriff.
tordmor
User
Beiträge: 100
Registriert: Donnerstag 20. November 2008, 10:29
Wohnort: Stuttgart

Phenex hat geschrieben: Nur kann ich nicht so einfach auf Variablen in dem Namensraum außerhalb des Funktionsaufrufes zugreifen.
Warum nicht? Das nennt sich dann import.

Module hooks.py:

Code: Alles auswählen

from collections import defaultdict

_hooks = defaultdict(lambda: [])

class Hook():
    def __init__(self, hook_name, function):
        self.hook_name = hook_name
        self.function = function
        _hooks[self.hook_name].append(self)
        
    def __call__(self, *args, **kwargs):
        self.function(*args, **kwargs)
        
def call(hook_name, *args, **kwargs):
    for hook in _hooks[hook_name]:
        hook(*args, **kwargs)
Definieren mit

Code: Alles auswählen

import hooks
def my_func():
    pass

hooks.Hook('blubb', my_func)
Aufrufen mit

Code: Alles auswählen

import hooks

a = 5
hooks.call('blubb')
b = a + 3
Oder hab ich was nicht verstanden?
Zuletzt geändert von tordmor am Mittwoch 14. April 2010, 07:19, insgesamt 1-mal geändert.
http://www.felix-benner.com
Pekh
User
Beiträge: 482
Registriert: Donnerstag 22. Mai 2008, 09:09

Phenex hat geschrieben:Das wäre irgendwie umständlich, außerdem hätte man dann auf die Attribute nur Lesezugriff.
Jein. Wenn du z.B. eine Liste übergibst, kannst du diese manipulieren und die Änderungen werden an allen Stellen sichtbar, an denen dieses Objekt (keine Kopie!) verwendet wird. Normalerweise versucht man solche Effekte gerade zu vermeiden, aber gehen tut es. Das mindeste wäre aber, diese Seiteneffekte an prominenter Stelle so zu dokumentieren, daß sie einem Leser sofort ins Auge stechen. Sonst kann man nämlich ewig nach Fehlern suchen.
Phenex
User
Beiträge: 6
Registriert: Freitag 20. Juni 2008, 16:25

tordmor hat geschrieben:
Phenex hat geschrieben: Oder hab ich was nicht verstanden?
Naja, nur könnte ich jedoch den Wert a in myFunction nicht überschreiben, so dass dies die Wertzuweisung von b beeinflusst.

Seiteneffekt, so eine dämliche Übersetztung^^
Schön wäre es, wenn ich eben solche Dinge nicht tun müsste, sindern es eine Möglichkeit gäbe sozusagen auf einen fremden Scope zuzugreifen. Wenn ich dem hook das localdict übergebe, kann ich zwar Variablen aus dem Scope auslesen, in dem der Hook gesetzt ist - jedoch kann ich diese nicht verändern.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Phenex hat geschrieben:Seiteneffekt, so eine dämliche Übersetztung^^
Sie ist ja auch falsch. Nebeneffekt ist die richtige Uebersetzung.
Phenex hat geschrieben:Wenn ich dem hook das localdict übergebe, kann ich zwar Variablen aus dem Scope auslesen, in dem der Hook gesetzt ist - jedoch kann ich diese nicht verändern.
Testen! Das geht durchaus, sofern du das Dictionary uebergibst. Am saubersten waere aber ein eigenes Dictionary, in dem du die Umgebung aufbaust, die du mit den Plugins teilen moechtest.
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Als Ergänzung zu Pekhs Beitrag:
Phenex hat geschrieben:Das wäre irgendwie umständlich, außerdem hätte man dann auf die Attribute nur Lesezugriff.
Nein, in Python werden immer Referenzen übergeben. Wenn du eine Referenz auf einen Datentyp übergibst, kannst du dessen Attribute sozusagen global verändern. Dabei musst du halt beachten, dass du bei immutable Datentypen wie int, str die Attribute selbst nicht verändern kannst, sondern nur neue Instanzen anlegen.

Code: Alles auswählen

>>> i = 5
>>> id(i)
37420776
>>> i += 1
>>> id(i)
37420764
# vs.
>>> l = list()
>>> id(l)
37714000
>>> l += [5]
>>> id(l)
37714000
Als Lösung könntest du einen eigenen Datentyp anlegen (als Klasse).

Alternativ gibt es ja auch noch die Möglichkeit, aus einer Funktion Werte zurückzugeben...

Edit: Ja, wie cofi gesagt hat, ein Environment als dict oder eigener Datentyp wäre die sauberste Lösung.
„Lieber von den Richtigen kritisiert als von den Falschen gelobt werden.“
Gerhard Kocher

http://ms4py.org/
Phenex
User
Beiträge: 6
Registriert: Freitag 20. Juni 2008, 16:25

cofi hat geschrieben:
Phenex hat geschrieben:Seiteneffekt, so eine dämliche Übersetztung^^
Sie ist ja auch falsch. Nebeneffekt ist die richtige Uebersetzung.
Jep ich weiß, wer kommt bloß auf so einen Quatsch...
cofi hat geschrieben:
Phenex hat geschrieben:Testen! Das geht durchaus, sofern du das Dictionary uebergibst. Am saubersten waere aber ein eigenes Dictionary, in dem du die Umgebung aufbaust, die du mit den Plugins teilen moechtest.
Laut Python-Dokumentation sollte man aber nicht schreibend darauf zugreifen, hat sicher auch einen Sinn (http://docs.python.org/library/functions.html#locals).

Danke erstmal für die Antworten. Klar könnte ich ein Dictionary übergeben aber ich suche ja gerade eine Möglichkeit das machen zu können ohne da groß was an extra Anpassungen für jeden Hook zu machen.
Antworten