Plugin-basiertes Programm
Hallo Leute,
ich hoffe auf ein paar Tipps oder den Schubs in die richtige Richtung, weil ich so gar nicht weiter weiß.
Ich möchte einen Scraper bauen, der mir Daten von verschiedenen Websites in eine Datenbank holt und ggf. per Mail verschickt.
Das wäre jetzt soweit kein Problem. Aber ich möchte das Programm so gestalten, dass das Scraping der Seiten über Plugins läuft/erweitert werden kann.
Ich stell mir vor, dass ich einen Ordner ./plugins habe und alle Module/Plugins darin werden automatisch geladen und verwendet.
Dabei sollen die Plugins der Core-Applikation mitteilen für welche URLs sie zuständig sind.
Die Core-App bekommt per Config-File eine Liste von URLs, die sie dann entsprechend an die Plugins zum verarbeiten gibt und die Daten zurückbekommt.
Grüße,
Rango
ich hoffe auf ein paar Tipps oder den Schubs in die richtige Richtung, weil ich so gar nicht weiter weiß.
Ich möchte einen Scraper bauen, der mir Daten von verschiedenen Websites in eine Datenbank holt und ggf. per Mail verschickt.
Das wäre jetzt soweit kein Problem. Aber ich möchte das Programm so gestalten, dass das Scraping der Seiten über Plugins läuft/erweitert werden kann.
Ich stell mir vor, dass ich einen Ordner ./plugins habe und alle Module/Plugins darin werden automatisch geladen und verwendet.
Dabei sollen die Plugins der Core-Applikation mitteilen für welche URLs sie zuständig sind.
Die Core-App bekommt per Config-File eine Liste von URLs, die sie dann entsprechend an die Plugins zum verarbeiten gibt und die Daten zurückbekommt.
Grüße,
Rango
@Rango,
ich würde diese Plugins als einfache Funktionen erstellen. In der Core-Applikation gibt es dann einfache Zuordnung von URLs zu Plugin-Funktionen.
Nicht lauffähiger Beispiel-Code:
Da die Verwendung der URLs als Keys im Dictionary problematisch sein könnte, kann man sich dabei je nach Bedarf auch andere Varianten vorstellen:
Die Plugin-Funktionen werden wahrscheinlich auch teilweise gemeinsamen Code und teilweise speziellen Code haben. Daher sollte man sich auch noch überlegen, wie Plugins noch in Funktionen und Unterfunktionen aufgeteilt werden müssen.
ich würde diese Plugins als einfache Funktionen erstellen. In der Core-Applikation gibt es dann einfache Zuordnung von URLs zu Plugin-Funktionen.
Nicht lauffähiger Beispiel-Code:
Code: Alles auswählen
import plugins
PLUGINS = {
"https://www.url1.de": plugins.pluginfunction1,
"https://www.url2.de": plugins.pluginfunction2,
"https://www.url3.de": plugins.pluginfunction3,
"https://www.url4.de": plugins.pluginfunction4,
}
def scrape(urls):
for url, plugin in PLUGINS.items():
if url in urls:
collected_data = plugin()
store_in_database(collected_data)
else:
print(f"Für die URL {url} wurde noch keine Plugin-Funktion erstellt")
def main():
urls_to_scrape = ["https://www.url2.de", "https://www.url3.de"]
scrape(urls_to_scrape)
Code: Alles auswählen
PLUGINS = {
"Website1":{
"url":"https://www.url1.de",
"plugin": plugins.pluginfunction1
},
"Website2":{
"url":"https://www.url2.de",
"plugin": plugins.pluginfunction2
}
}
Danke schon mal für den Input, das hilft mir schon mal!
Ich hatte mir das nur etwas dynamischer vorgestellt, ich wollte den Core-Code nicht für jedes neues Plugin anfassen wollen.
Idealerweise würde PLUGINS von den Plugins selbst verwaltet werden. Morgen kommt Plugin 5 dazu und teilt mit, dass es für url5.de zuständig ist.
Ich hatte mir das nur etwas dynamischer vorgestellt, ich wollte den Core-Code nicht für jedes neues Plugin anfassen wollen.
Idealerweise würde PLUGINS von den Plugins selbst verwaltet werden. Morgen kommt Plugin 5 dazu und teilt mit, dass es für url5.de zuständig ist.
- __blackjack__
- User
- Beiträge: 13004
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Was meinst du damit? Wenn die Plugins etwas "mitteilen", sich also irgendwie registrieren, muss sich die Core-Applikation das ja auch irgendwie merken. Dann hätten diese Plugins auch ein gewisses "Eigenleben". Wie stellst du dir das vor? Das erscheint mir merkwürdig und unnötig kompliziert.Morgen kommt Plugin 5 dazu und teilt mit, dass es für url5.de zuständig ist.
Dann ist es doch besser wenn du eine Datenstruktur zur Verfügung stellst, die diese Informationen bereits hat. Wenn du ein neues Plugin schreibst, musste du den Dictionary einfach erweitern. Wahlweise kannst du dafür auch eine eigene Funktion schreiben, die diesen Dictionary irgendwie aus den Plugin-Funktionen extrahiert.
Das ist doch auch gar nicht nötig. Wenn man mal die scrape() Funktion als rudimentäre Core-Applikation betrachtet, sieht man doch dass der Code mit 5 , 10 oder 1000 plugins arbeiten kann ohne dass man dafür etwas ändern müsste.ich wollte den Core-Code nicht für jedes neues Plugin anfassen wollen
Und wenn dich der Dictionary im Main-Modul stört, kannst du aus dem Plugin-Modul eine Funktion get_plugin_functions() zur Verfügung stellen, die eben diesen Dictionary in die Core-Applikation holt.
Ich hatte vorhin noch ein wenig Zeit und mittlerweile halbwegs da angekommen, wo ich hinwollte.
Hier was ich zusammengefrickelt habe:
Gerne Anmerkungen und Verbesserungsvorschläge dazu.. mir ist klar, dass hier noch viel unsauber ist.
Grüße,
Rango
Hier was ich zusammengefrickelt habe:
Code: Alles auswählen
class App:
def __init__(self) -> None:
self.url_plugin_mapping = {}
def load_plugins(self):
import app.plugins
plugin_package = app.plugins
for _, modulename, _ in pkgutil.iter_modules(plugin_package.__path__):
plugin_path = "app.plugins." + modulename
plugin = importlib.import_module(plugin_path)
for url in plugin.return_accepted_urls():
self.url_plugin_mapping[url] = plugin
def scrape(self, urls_to_scrape):
for url_to_scrape in urls_to_scrape:
for plugin_url in self.url_plugin_mapping:
if re.search(plugin_url, url_to_scrape):
self.url_plugin_mapping[plugin_url].scrape_url(url_to_scrape)
Grüße,
Rango
@Rango,
Funktioniert das denn so?
der von __blackjack__ verlinkte Guide ist für Packages gedacht, für die man separat zum eigentlichen Package auch noch Plugins zur Verfügung stellen möchte. Daher würde ich mir überlegen, ob das für dich überhaupt nötig ist.
Es ist auch so gedacht, dass man über "pip install" die Plugins installiert. Im Beispiel von Flask würde Flask dann erkennen, dass der Endnutzer weitere Packages nachinstalliert hat und Flask könnte dann damit arbeiten.
Außerdem ist ja der Sinn und Zweck des Plugin-Systems, dass der Endnutzer deines Packages (Wenn wir mal davon ausgehen, dass du es auch anderen zur Verfügung stellen willst) selbst entscheiden kann, welche Plugins er installieren / importieren / nutzen möchte. Bei dir werden die Plugins aber immer importiert. Wenn man schon so ein Plugin-System verwendet, sollte es auch die Möglichkeit geben nur das zu importieren was man auch wirklich braucht.
Die Plugin-Discovery gehört meiner Meinung nach nicht in die App-Klasse, sondern sollte unabhängig sein.
Da ich selber das Plugin-Discovery noch nicht verwendet habe, bin ich vorsichtig, aber es scheint mir so als würdest du es nicht nur falsch verwenden, sondern auch nicht benötigen.
Noch ein paar Anmerkungen zum Code:
Wenn du Pfade zusammen setzen möchtest würde ich die pathlib verwenden. Die kann mit den vielen Besonderheit von Pfadnamen unter verschiedenen Betriebssystemen robust umgehen.
re.search() erwartet an erster Stelle ein Pattern. Das kann natürlich die URL als String sein. Aber dann ist regex eingentlich nicht nötig.
Ich würde zur Sicherheit auch verlangen, dass die URL genau stimmt (==).
Funktioniert das denn so?
der von __blackjack__ verlinkte Guide ist für Packages gedacht, für die man separat zum eigentlichen Package auch noch Plugins zur Verfügung stellen möchte. Daher würde ich mir überlegen, ob das für dich überhaupt nötig ist.
Es ist auch so gedacht, dass man über "pip install" die Plugins installiert. Im Beispiel von Flask würde Flask dann erkennen, dass der Endnutzer weitere Packages nachinstalliert hat und Flask könnte dann damit arbeiten.
Außerdem ist ja der Sinn und Zweck des Plugin-Systems, dass der Endnutzer deines Packages (Wenn wir mal davon ausgehen, dass du es auch anderen zur Verfügung stellen willst) selbst entscheiden kann, welche Plugins er installieren / importieren / nutzen möchte. Bei dir werden die Plugins aber immer importiert. Wenn man schon so ein Plugin-System verwendet, sollte es auch die Möglichkeit geben nur das zu importieren was man auch wirklich braucht.
Die Plugin-Discovery gehört meiner Meinung nach nicht in die App-Klasse, sondern sollte unabhängig sein.
Da ich selber das Plugin-Discovery noch nicht verwendet habe, bin ich vorsichtig, aber es scheint mir so als würdest du es nicht nur falsch verwenden, sondern auch nicht benötigen.
Noch ein paar Anmerkungen zum Code:
Wenn du Pfade zusammen setzen möchtest würde ich die pathlib verwenden. Die kann mit den vielen Besonderheit von Pfadnamen unter verschiedenen Betriebssystemen robust umgehen.
re.search() erwartet an erster Stelle ein Pattern. Das kann natürlich die URL als String sein. Aber dann ist regex eingentlich nicht nötig.
Ich würde zur Sicherheit auch verlangen, dass die URL genau stimmt (==).
Für meinen Fall wie ich ihn möchte tut das erstmal so.
Ich bin hier vermutlich nicht weit genug ins Detail gegangen oder habe mich falsch ausgedrückt.rogerb hat geschrieben: ↑Mittwoch 28. Juli 2021, 14:56 Es ist auch so gedacht, dass man über "pip install" die Plugins installiert. Im Beispiel von Flask würde Flask dann erkennen, dass der Endnutzer weitere Packages nachinstalliert hat und Flask könnte dann damit arbeiten.
Außerdem ist ja der Sinn und Zweck des Plugin-Systems, dass der Endnutzer deines Packages (Wenn wir mal davon ausgehen, dass du es auch anderen zur Verfügung stellen willst) selbst entscheiden kann, welche Plugins er installieren / importieren / nutzen möchte. Bei dir werden die Plugins aber immer importiert. Wenn man schon so ein Plugin-System verwendet, sollte es auch die Möglichkeit geben nur das zu importieren was man auch wirklich braucht.
Ich brauch grundsätzlich erstmal eine Applikation, die mir Stand heute von 17 verschiedenen Websites Daten holt und in eine Datenbank wegschreibt. Soweit so gut.
Jetzt kommt aber Monatstakt ein Kollegen und möchte noch die Seite x, die Seite y und die Seite z.
Dem möchte ich sagen: bau ein Modul mit einer Funktion return_accepted_urls(), die zurückgibt für welche Seite (super-seite.de z.B.) dein Scraper taugt und einer Funktion scrape_url(), die mir die Daten in einem Dict mit den Keys x, y und z zurückliefert. Alles andere soll mich nicht interessieren. In der globalen Konfigurationsdatei kommen dann seine URLs wie z.B. https://super-seite.de/foo/bar/bla.html und https://super-seite.de/bar/foo/muh.html rein. Mein Programm ruft dann eben sein Modul mit den URLs aus der Konfigurationsdatei auf und bekommt die Daten und schreibt sie weg.
Grüße,
Rango
Du könntest es natürlich auch so anlegen, dass ein Objekt - Manager die jeweiligen Module lädt.
Das hätte den Vorteil, dass du im Modul "XY" z.B. automatisch auf Dateien im "plugins" Ordner
suchen könntest und die Daten sind variabel ladbar, da er neue Dateien automatisch verarbeitet.
Beste Grüße
YAPD
Das hätte den Vorteil, dass du im Modul "XY" z.B. automatisch auf Dateien im "plugins" Ordner
suchen könntest und die Daten sind variabel ladbar, da er neue Dateien automatisch verarbeitet.
Beste Grüße
YAPD
-----
Yet Another Python Developer
Yet Another Python Developer
Könntest du das bitte noch etwas näher ausführen?YAPD hat geschrieben: ↑Mittwoch 28. Juli 2021, 15:48 Du könntest es natürlich auch so anlegen, dass ein Objekt - Manager die jeweiligen Module lädt.
Das hätte den Vorteil, dass du im Modul "XY" z.B. automatisch auf Dateien im "plugins" Ordner
suchen könntest und die Daten sind variabel ladbar, da er neue Dateien automatisch verarbeitet.
Beste Grüße
YAPD
Rango hat geschrieben: ↑Mittwoch 28. Juli 2021, 15:56Könntest du das bitte noch etwas näher ausführen?YAPD hat geschrieben: ↑Mittwoch 28. Juli 2021, 15:48 Du könntest es natürlich auch so anlegen, dass ein Objekt - Manager die jeweiligen Module lädt.
Das hätte den Vorteil, dass du im Modul "XY" z.B. automatisch auf Dateien im "plugins" Ordner
suchen könntest und die Daten sind variabel ladbar, da er neue Dateien automatisch verarbeitet.
Beste Grüße
YAPD
Hi,
ja natürlich kann ich das noch ausführlicher beschreiben
Als erstes importierst du 'importlib'
Code: Alles auswählen
import importlib
in diesem Fall "OM".
Wichtig für die richtige Ablage der Objekte ist, dass du direkt im "Kopf" der Klasse
das Dictionary "OM_Objects" anlegst.
Code: Alles auswählen
class OM:
OM_Objects = { }
def __init__( self ):
print( "Initialisierung des OM !" )
Nun definierst du in der Klasse "OM" zwei Sub - Routinen, Load & Builder :
Load :
Code: Alles auswählen
def Load( Self , Module = "" ) :
if Module == "" :
print( "WARNING : No Module Specified !" )
return
if Module in OM.OM_Objects.keys( ) :
print( "INFO : The Specified Module '" + Module + "' Is Already Loaded !" )
return OM.OM_Objects[ Module ]
OBJ = OM.Builder( Self , Module )
return OBJ
ist. Wenn dies der Fall ist, gibt es das vorhandene Objekt zurück ( return OM.OM_Objects[ Module ] ).
Andersfall ruft er den Builder mit dem Modul als Parameter auf.
Builder :
Code: Alles auswählen
def Builder( Self , Module ):
OM_Package = Module
OM_FH = Module
OM_FH = OM_FH.replace('::', "\\" )
OM_FH = OM_FH + ".py"
OM_FH_Status = os.path.exists( os.getcwd( ) + "\\" + OM_FH )
if OM_FH_Status == False :
print( "[ ERROR ] The Specified Module '" + Module + "' Is Not Available !" )
return
OM_Class = OM_Package
OM_Class_Seperator = OM_Class.count( "::" )
OM_Class = OM_Class.split( "::" , OM_Class_Seperator )
OM_UID = OM_Class[ OM_Class_Seperator ]
OM_Package = OM_Package.replace( '::' , "." )
OM_Module_Instance = importlib.import_module( OM_Package )
OM_Object_Methods = [ method_name for method_name in dir( OM_Module_Instance )
if callable(getattr( OM_Module_Instance , method_name ) ) ]
if OM_UID not in OM_Object_Methods :
print( "[ ERROR ] The Constructor For Module '" + Module + "' Could Not Be Found !" )
return
OM_OBJ = getattr( OM_Module_Instance , OM_UID )( )
OM.OM_Objects.update( { Module : OM_OBJ } )
return OM.OM_Objects[ Module ]
und wandelt den Namen des Moduls, das geladen werden soll, um, so dass es erkannt wird :
HINWEIS : Ich habe das Script so geschrieben, dass man ein Modul mit folgendem Befehl lädt :
test = OM( )
result1 = test.Load( "Config" ) -------> Lädt Dateien aus dem Hauptverzeichnis
result1 = test.Load( "Kernel::Testumgebung" ) -------> Lädt Dateien aus einem Unterverzeichnis, indem er
den Namen konvertiert ( OM_Package = OM_Package.replace( '::' , "." ) )
Anschließend initialisiert er das Modul mit dem Befehl : OM_Module_Instance = importlib.import_module( OM_Package )
Code: Alles auswählen
OM_Object_Methods = [ method_name for method_name in dir( OM_Module_Instance )
if callable(getattr( OM_Module_Instance , method_name ) ) ]
also z.B. in der "Config.py ---> class Config:" , in der "Testumgebung.py -----> class Testumgebung:" usw.
Vorher hat die Builder Funktion mit :
Code: Alles auswählen
OM_Class_Seperator = OM_Class.count( "::" )
OM_Class = OM_Class.split( "::" , OM_Class_Seperator )
OM_UID = OM_Class[ OM_Class_Seperator ]
Code: Alles auswählen
OM_OBJ = getattr( OM_Module_Instance , OM_UID )( )
Code: Alles auswählen
OM.OM_Objects.update( { Module : OM_OBJ } )
die wiederum das Objekt zurückgibt.
Dadurch erhältst du mit
Code: Alles auswählen
result1 = test.Load( "Config" )
das Objekt zurück und kannst bequerm auf die Werte zugreifen, z.B. :
String -> result1.String
Dictionary -> result1.Dictionary[ "Test" ]
Und natürlich kann man auch andere Subs aufrufen, z B. :
result1.Testumgebung( )
Puh, das war dann doch einiges an Text. Sorry, dass es länger gedauert hat
Beste Grüße
YAPD
-----
Yet Another Python Developer
Yet Another Python Developer
- __blackjack__
- User
- Beiträge: 13004
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@YAPD: Das sieht sehr nach Perl als Python verkleidet aus. Unter anderem würde ich einen Blick in den Style Guide for Python Code werfen was Namenschreibweisen und Leerzeichen bei Klammern angeht.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Also der gepostete Code funktioniert einwandfrei, solltest du Schwachpunkte entdeckt haben, die ( funktionell )__blackjack__ hat geschrieben: ↑Mittwoch 28. Juli 2021, 17:06 @YAPD: Das sieht sehr nach Perl als Python verkleidet aus. Unter anderem würde ich einen Blick in den Style Guide for Python Code werfen was Namenschreibweisen und Leerzeichen bei Klammern angeht.
Probleme verursachen würden, kannst du mir diese gerne mitteilen. Ich habe mein PERL Wissen natürlich
genutzt, um den Code zu schreiben. Aber "sieht nach Perl aus" verstehe ich nicht ? Wie sehe es denn nach
"Python" aus, deiner Meinung nach ?. Ich bin wie gesagt auch noch Neuling und lernfähig, hoffentlich XD
Zu der Schreibweise, die du bemängelt hast, das sind Empfehlungen, keine festen Vorgaben. Natürlich halte
ich mich mit Ausnahmen daran, es sollte also jedem Programmierer gelingen, meinen Code zu lesen.
Außerdem sagt es :
Und sorry, aber manches ist einfach nicht wirklich leserlich, wie :Some other good reasons to ignore a particular guideline:
1. When applying the guideline would make the code less
readable, even for someone who is used to reading code
that follows this PEP.
Code: Alles auswählen
result = some_function_that_takes_arguments(
'a', 'b', 'c',
'd', 'e', 'f',
)
Code: Alles auswählen
result = some_function_that_takes_arguments('a', 'b', 'c',
'd', 'e', 'f')
Beste Grüße
YAPD
-----
Yet Another Python Developer
Yet Another Python Developer
Wie bei langen Funktionsaufrufen eingerückt wird, ist wirklich nicht immer eindeutig, aber dass Variablennamen komplett klein geschrieben werden, dass man nicht jede Variable mit OM_ beginnt, und statt dessen aussagekräftige Namen verwendet, trägt wesentlich zur Lesbarkeit bei und deshalb sollte man sich daran halten.
Eingerückt wird immer mit 4 Leerzeichen pro Ebene, weil das alle machen, und man sonst nicht Deinen Code in anderen Kopieren kann, ohne dass man alle Einrückungen anpassen muß.
`OM.Builder( Self, ...)` schreibt man nicht, weil Methoden immer über die Instanz referenziert werden: `Self.Builder(...)`.
Man benutzt keine globalen Variablen, auch wenn sie sich in einer Klassendefinition verstecken.
`Load` liefert einmal implizit None zurück und einmal explizit OBJ, das sollte nicht sein, in dem Fall gibt man explizit None zurück, oder aber man löst gleich eine Exception aus, weil man im Fall None nicht sinnvoll weiterarbeiten kann.
Warum nennst Du in `Builder` `Module` in `OM_Package` um, warum nennst `Module` dann auch noch in `OM_FH` um, und in `OM_Class`? Wenn Du mit einer Variable arbeitest, mußt Du sie nicht erst gleich dem Ergebnis, das Du zum Schluß haben willst, umbenennen.
Pfade sind keine Strings, man stückelt sie nicht mit + zusammen, und benutzt auch keinen hart codierten \ als Pfadtrenner. Statt dessen benutzt man pathlib.Path. Das ist platformunabhängig und nicht so fehleranfällig.
Auf False prüft man nicht explizit per == sondern per not. Auch hier löst man eine Exception aus, statt implizit None zurückzuliefern.
Warum zählst Du, wie viele "::" vorkommen, um das dann mit split zu verwenden? split splittet ohne zweiten Parameter immer an allen Vorkommen.
Um ein Element einem Wörterbuch hinzuzufügen benutzt man nicht update.
Insgesamt könnte das also so aussehen:
Eingerückt wird immer mit 4 Leerzeichen pro Ebene, weil das alle machen, und man sonst nicht Deinen Code in anderen Kopieren kann, ohne dass man alle Einrückungen anpassen muß.
`OM.Builder( Self, ...)` schreibt man nicht, weil Methoden immer über die Instanz referenziert werden: `Self.Builder(...)`.
Man benutzt keine globalen Variablen, auch wenn sie sich in einer Klassendefinition verstecken.
`Load` liefert einmal implizit None zurück und einmal explizit OBJ, das sollte nicht sein, in dem Fall gibt man explizit None zurück, oder aber man löst gleich eine Exception aus, weil man im Fall None nicht sinnvoll weiterarbeiten kann.
Warum nennst Du in `Builder` `Module` in `OM_Package` um, warum nennst `Module` dann auch noch in `OM_FH` um, und in `OM_Class`? Wenn Du mit einer Variable arbeitest, mußt Du sie nicht erst gleich dem Ergebnis, das Du zum Schluß haben willst, umbenennen.
Pfade sind keine Strings, man stückelt sie nicht mit + zusammen, und benutzt auch keinen hart codierten \ als Pfadtrenner. Statt dessen benutzt man pathlib.Path. Das ist platformunabhängig und nicht so fehleranfällig.
Auf False prüft man nicht explizit per == sondern per not. Auch hier löst man eine Exception aus, statt implizit None zurückzuliefern.
Warum zählst Du, wie viele "::" vorkommen, um das dann mit split zu verwenden? split splittet ohne zweiten Parameter immer an allen Vorkommen.
Um ein Element einem Wörterbuch hinzuzufügen benutzt man nicht update.
Insgesamt könnte das also so aussehen:
Code: Alles auswählen
import importlib
from pathlib import Path
class ObjectManager:
def __init__(self):
self.objects = {}
def load(self, module_name=""):
if module_name == "":
raise RuntimeError("No Module Specified!")
if module_name in self.objects:
print(f"INFO : The Specified Module '{module_name}' Is Already Loaded!")
else:
self.builder(module_name)
return self.objects[module_name]
def builder(self, module_name):
package_parts = module_name.split("::")
path = Path(*package_parts).with_suffix('.py')
if not path.exists():
raise FileNotFoundError(f"The Specified Module '{module_name}' Is Not Available !")
uid = package_parts[-1]
package = ".".join(package_parts)
module = importlib.import_module(package)
try:
uid_method = getattr(module, uid)
except AttributeError:
raise ImportError(f"The Constructor For Module '{module_name}' Could Not Be Found !")
self.objects[module_name] = uid_method()
- __blackjack__
- User
- Beiträge: 13004
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@YAPD: Beim Einrücken der Argumente bis zur öffnenden Klammer spricht dagegen, was alles umformatiert werden muss, wenn sich beispielsweise die Länge des Funktionsnamens ändert. Das sind dann alles Änderungen im Diff/der Versionsverwaltung die nur kosmetisch sind, und es schwerer machen die *eigentliche* Änderung zu erkennen.
Und ja es sind nur Richtlinien. Aber eben auch wieder nicht, weil Du da jedes mal drauf hingewiesen wirst, und viele verbreitete Projekte mittlerweile ein Tool zur Formatierung verwenden das fast gar keine Einstellungsmöglichkeiten hat. Da sieht das dann so aus wenn es nicht mehr in eine Zeile passt, die Argumente selbst aber immer noch auf eine Zeile passen:
Die `builder()`-Methode hätte ich `build()` genannt, denn Funktionen und Methoden werden üblicherweise nach der Tätigkeit benannt, die sie durchführen um sie von eher passiven Werten unterscheiden zu können. `builder` wäre ein passender Name für ein komplexeres `Builder`-Objekt.
Und ja es sind nur Richtlinien. Aber eben auch wieder nicht, weil Du da jedes mal drauf hingewiesen wirst, und viele verbreitete Projekte mittlerweile ein Tool zur Formatierung verwenden das fast gar keine Einstellungsmöglichkeiten hat. Da sieht das dann so aus wenn es nicht mehr in eine Zeile passt, die Argumente selbst aber immer noch auf eine Zeile passen:
Code: Alles auswählen
result = some_function_that_takes_arguments(
"a", "b", "c", "d", "e", "f", "g"
)
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Das habe ich immer gemacht !Eingerückt wird immer mit 4 Leerzeichen pro Ebene, weil das alle machen, und man sonst nicht Deinen Code in anderen Kopieren kann, ohne dass man alle Einrückungen anpassen muß.
Diese 2. Punkte werde ich ändern. Dankeschön.`OM.Builder( Self, ...)` schreibt man nicht, weil Methoden immer über die Instanz referenziert werden: `Self.Builder(...)`.
Man benutzt keine globalen Variablen, auch wenn sie sich in einer Klassendefinition verstecken.
Ich schreibe gerade noch daran und es war für mich in diesem Status OK, dass er None ausgibt.`Load` liefert einmal implizit None zurück und einmal explizit OBJ, das sollte nicht sein, in dem Fall gibt man explizit None zurück, oder aber man löst gleich eine Exception aus, weil man im Fall None nicht sinnvoll weiterarbeiten kann.
Ein ordentliches Error Handling entwerfe ich gerade, also Log - Handler usw.
Das mit dem OM vorne habe ich geschrieben, weil es Objekte des Object Managers sind.Warum nennst Du in `Builder` `Module` in `OM_Package` um, warum nennst `Module` dann auch noch in `OM_FH` um, und in `OM_Class`? Wenn Du mit einer Variable arbeitest, mußt Du sie nicht erst gleich dem Ergebnis, das Du zum Schluß haben willst, umbenennen.
Kann man auch anders machen, aber das ist glaube ich unerheblich.
Ich verändere sowohl OM_FH als auch OM_Class während des Prozesses.
Da ich die Variable 'Module' aber später verwende :
OM.OM_Objects.update( { Module : OM_OBJ } )
kann ich sie nicht verändern, sondern muss Kopien erstellen.
Ja, das habe ich bereits hier im Forum gelesen, bin aber noch nicht dazu gekommen, es zu ändern.Pfade sind keine Strings, man stückelt sie nicht mit + zusammen, und benutzt auch keinen hart codierten \ als Pfadtrenner. Statt dessen benutzt man pathlib.Path. Das ist platformunabhängig und nicht so fehleranfällig.
Flüchtigkeitsfehler. Danke.Auf False prüft man nicht explizit per == sondern per not. Auch hier löst man eine Exception aus, statt implizit None zurückzuliefern.
Wie gesagt, ermittle ich damit den Namen. Da das Modul theoretischWarum zählst Du, wie viele "::" vorkommen, um das dann mit split zu verwenden? split splittet ohne zweiten Parameter immer an allen Vorkommen.
auch "Test::Kernel::Config::Testumgebung::Neu" sein kann, zähle ich
sie ab. Ich glaube nicht, dass split immer nur das letzte nimmt, muss
ich nochmal testen.
Wie gesagt, bin ich relativ neu in der Materie und ich habe in irgendeinerUm ein Element einem Wörterbuch hinzuzufügen benutzt man nicht update.
Anleitung gelesen, dass man es mit update( ) macht. Sorry, wenn das
eine Fehleinschätzung war.
Gruß
YAPD
-----
Yet Another Python Developer
Yet Another Python Developer
Danke für deinen Beitrag, blackjack. Ich bin gerade etwas geknickt, hab den Code komplett alleine__blackjack__ hat geschrieben: ↑Mittwoch 28. Juli 2021, 21:11 @YAPD: Beim Einrücken der Argumente bis zur öffnenden Klammer spricht dagegen, was alles umformatiert werden muss, wenn sich beispielsweise die Länge des Funktionsnamens ändert. Das sind dann alles Änderungen im Diff/der Versionsverwaltung die nur kosmetisch sind, und es schwerer machen die *eigentliche* Änderung zu erkennen.
Und ja es sind nur Richtlinien. Aber eben auch wieder nicht, weil Du da jedes mal drauf hingewiesen wirst, und viele verbreitete Projekte mittlerweile ein Tool zur Formatierung verwenden das fast gar keine Einstellungsmöglichkeiten hat. Da sieht das dann so aus wenn es nicht mehr in eine Zeile passt, die Argumente selbst aber immer noch auf eine Zeile passen:Die `builder()`-Methode hätte ich `build()` genannt, denn Funktionen und Methoden werden üblicherweise nach der Tätigkeit benannt, die sie durchführen um sie von eher passiven Werten unterscheiden zu können. `builder` wäre ein passender Name für ein komplexeres `Builder`-Objekt.Code: Alles auswählen
result = some_function_that_takes_arguments( "a", "b", "c", "d", "e", "f", "g" )
geschrieben, bis auf kleine Ausnahmen und hab mich echt gefreut, dass alles so funktioniert. Außerdem
habe ich mir super Mühe gegeben, alles verständlich zu erklären für den Beitragsersteller. Der übrigens
seitdem nicht mehr geschrieben hat. Aber auf eines kann man wetten. Egal, wie gut dein Code ist oder
auch nicht, es wird immer einen Oberlehrer geben, der einem zeigt, wie schlecht man ist.
Gruß
YAPD
-----
Yet Another Python Developer
Yet Another Python Developer
Jeder fängt mal mit etwas an, und sollte sich freuen, dazuzulernen.
Strings sind unveränderlich, die können also gar nicht verändert werden.
Offensichtlich kann ich bis 3 zählen.
Das sind keine Objekte sondern alles lokale Variablen. Und bei lokalen Variablen ist es klar, dass sie zu einer Methode gehören, die in der Klasse OM definiert ist. Das OM_ sind also drei Buchstaben, die keinen Mehrwert bieten, sondern nur das Lesen behindern.
Du hast eine falsche Vorstellung davon, was Variablen in Python sind; das sind nur Referenzen auf Objekte. Wenn man eine Referenz einer anderen Variable zuweist, werden keine Kopien erzeugt.
Strings sind unveränderlich, die können also gar nicht verändert werden.
Benutze einen Autoformatter wie `black`, dann kannst du dich zusätzlich noch freuen, dass dein Code immer gleich (gut?) aussieht. Und wir freuen uns, dass wir ihn lesen können.
Und zum `dict.update`: Das wird leider in vielen Tutorials so gezeigt, und man kann als Anfänger natürlich nicht einschätzen, ob man da gerade guten Stil vermittelt bekommt. Schön wäre es mit `objects[module] = obj` (wobei man da potentiell auch noch die Namen verbessern könnte).