Laden von eigenen Modulen über einen Objekt - Manager

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.
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

Guten Morgen Zusammen,

ich möchte zur Zeit in Zuge eines Projekts eine Anwendung entwickeln,
die einen Objekt Manager nutzt, um eigene Module zu laden. Dies möchte
ich mit Klassen / Objekten realisieren. Folgende Voraussetzungen habe
ich Vornherein festgelegt :

1. Der Objekt Manager 'OM.py' wird nur einmalig beim Start der Anwendung initialisiert
2. Der Objekt Manager hat eine ( globale ? ) Variable, die überall in der Anwendung verfügbar ist
3. Die ( globale ) Variable heisst "Registered_Modules" & ist ein Dictionary
4. Das Paar im Dictionary 'Registered_Modules" hat immer das Format { "Name" : <Objekt des jeweiligen Moduls }
5. Über das o. g. Objekt kann man auf die Variablen der jeweiligen Module zugreifen, die dieses
bei der Objekterstellung im OM zurückgibt.

Code: Alles auswählen

class OM:

    Registered_Modules = {}

    def __init__(Self, Module="", Debug_Mode=False):

        Self.Debug_Mode = Debug_Mode
        Self.Registered_Modules = OM.Registered_Modules

        print(f"Debug Mode : {Debug_Mode}")
4. Module werden über die Methode 'Load_Module' im Objekt Manager geladen :

Code: Alles auswählen

def Load_Module(Self, Module=""):
    
        if Module:
    
            Module_Properties = OM.Register_Module(Self, Module)
            return Module_Properties
    
        else:
            print(f"[  ERROR  ] No Module Specified !") 

Die Load - Methode ist leitet das Modul an die Methode 'Register_Module' :

Code: Alles auswählen

def Register_Module(Self, Module):

    if Module not in Self.Registered_Modules:
        print(f"[   MSG   ] The Specified Module '{Module}' Is Not Registered !")
    else:
        print(f"[  ERROR  ] The Specified Module '{Module}' Is Already Registered !")
        return Registered_Modules[Module]

    Module_Descriptor = Module.split("::", -1)[-1]

    Module_FH = Module
    Module_FH = Module_FH.replace("::", "/") + ".py"

    Module_Instance = importlib.import_module(Module.replace("::", "."))

    Module_Methods = [
        method_name
        for method_name in dir(Module_Instance)
        if callable(getattr(Module_Instance, method_name))
    ]

    if Module_Descriptor not in Module_Methods:
        print(
            f"[ ERROR ] The Constructor Of The Specified Module '{Module}' Could Not Be Found !"
        )
        exit(0)

    Module_Attributes = getattr(Module_Instance, Module_Descriptor)()

    Self.Registered_Modules.update({Module_Descriptor: Module_Attributes})

    return Module_Instance
Im Startskript meiner Anwendung lade ich das OM - Modul mit

Code: Alles auswählen

from OM import *
und initialisiere den Objekt - Manager, bisher so :

Code: Alles auswählen

from OM import *


class VTE:
    def __init__(Self):

        Self.OM_Manager = OM()

        Self.OM_Manager.Load_Module("Testumgebung::Test::Neu")
        CFG = Self.OM_Manager.Load_Module("Config")
        CFG.Config_Loader(Self)


Leider habe ich hier noch ein paar offenen Probleme bzw. Fragen :

1. Da ich überall in der Anwendung auf die Variable "Registered_Modules" zugreifen
möchte, muss ich diese als globale Variable definieren oder sehe ich das falsch ?
2. Ich gebe zur Zeit im OM bei Laden eines Moduls anschließend die Modul - Instanz
zurück :

Code: Alles auswählen

return( Module_Instance )

Vorher habe ich das Objekt aus der Variable 'Registered_Modules" zurückgeben :

Code: Alles auswählen

return( Self.Registered_Modules[ Module_Descriptor ] )

Hintergrund ist, dass ich so auch direkt Klassen aus den Modulen aufrufen kann.
Welche Variante ist die bessere / richtigere von Beiden ?

3. Seht ihr grundsätzlich ( außer bei der Schreibweise der Variablen ) Verbesserungspotential,
kann man die Funktionalität noch verbessern.

Für Eure Hilfe & Ratschläge wäre ich sehr dankbar.

Vielen Grüße
YAPD
-----
Yet Another Python Developer
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@YAPD: Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Die `print()`-Ausgaben sollten Logging-Ausgaben sein. Zum Beispiel mit dem `logging`-Modul aus der Standardbibliothek.

Der Klassenname sollte nicht `OM` abgekürzt werden. Wenn der Benutzer das unbedingt in seinem Code machen möchte, kann er das ja mit ``as`` tun.

`registered_modules` sollte nicht auf der Klasse definiert sein. Das ist globaler Zustand den man nicht haben will. Wenn der Benutzer der Bibliothek nur einen Objektmanager haben möchte, dann erstellt der einfach nur ein Exemplar davon. Das ist ja kein Problem. Problematisch ist es dagegen wenn er mehr als einen haben möchte, weil dass dann nicht mehr geht wenn man das mit globalem Zustand löst.

`module` ist ein guter Name für ein Modul, aber kein guter Name für eine Zeichenkette die für ein Modul steht. Module sind ja ein Datentyp in Python.

Default-Argument sind dazu da, das man das Argument beim Aufruf weglassen kann. Defaultargumente die zu einer Fehlermeldung führen machen deshalb keinen Sinn. Das sollte ausserdem zu einer Ausnahme führen, und nicht einfach nur zu einer `print()`- oder Log-Ausgabe. Der Aufrufer muss ja eine Chance haben auf solche Fehler zu reagieren.

`load_module()` ist effektiv letztlich nur ein anderer Name für `register_module()`. Was ist der Sinn davon?

``ObjectManager.register(self, module_name)`` ist dort falsch, das sollte ``self.register(model_name)`` heissen. Man ruft normale Methoden nicht über die Klasse auf. Das verhindert das man eine Klasse von `ObjectManager` ableiten kann.

Bei `str.split()` eine -1 zu übergeben macht keinen Sinn. Interessanterweise löst das keine Ausnahme aus, da würde ich mich aber nicht drauf verlassen.

Wenn man nur an einer Stelle auftrennen will, dann gibt man dort eine 1 an, oder man verwendet die `partition()`-Methode. Wenn man von hinten bzw. rechts kommt, dann `rsplit()` oder `rpartition()`.

`Module_FH`, was immer eine Module-Fachhochschule ist, wird nirgends verwendet.

Pfade bastelt man nicht mit Zeichenkettenoperationen zusammen. Die "::"" durch "/" zu ersetzen macht nur Sinn auf Systemen wo "/" als Pfadtrenner funktioniert. Es gibt das `pathlib`-Modul um von solchen Details zu abstrahieren. Aber es wird ja nirgends verwendet.

Wenn sowieso die "::" durch "." ersetzt werden um das dann als qualifizierten Modulnamen an `importlib` zu verfüttern, warum dann überhaupt die "::" und nicht gleich "."? Dann muss der Benutzer nichts Neues lernen, sondern kann die Modulangaben verwenden die er schon von überall anders her kennt.

Vom Konstruktor eines Moduls zu sprechen ist komisch. Ebenfalls von aufrufbaren Objekten auf Modulebene als Methoden. Und das in einem Modul eine Funktion existieren muss die wie das Modul heisst und dann eine besondere Bedeutung hat, ist auch eher einschränkend. Und falls diese Funktion nicht existiert, das Programm mit `exit()` abzubrechen geht gar nicht. Wenn es ginge, wäre 0 hier der falsche Rückgabecode.

Statt einer Funktion mit einem festen Namen pro Modul würde man hier besser Dekoratoren zum registrieren bereit stellen. Dann kann der Benutzer festlegen was zum initialisieren des Plugins verwendet wird. Plugin ist übrigens ein Begriff der mir in der gesamten Beschreibung irgendwo fehlt.

Warum wird nur der letzte Teil, also der tatsächliche Modulname in `registered_modules` als Schlüssel verwendet? Das verhindert effektiv, dass man "foo::test" und "bar::test" als verschiedene Plugins registrieren kann.

`OM_Manager` heisst dann nicht abgekürzt `ObjectManager_Manager`‽ Abkürzungen sind doof.

Bevor ich anfangen würde so etwas selber zu programmieren: Das Problem „Plugins“ haben andere schon gelöst. Ich würde erst mal schauen was es da schon fertig gibt. `pluggy` & Co sind nicht erst seit gestern bei verschiedenen Projekten im Einsatz.

Edit: Das gezeigte Beispiel ist natürlich nicht sinnvoll, weil man da auch einfach normale ``import``-Anweisungen hätte verwenden können.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

Hey Blackjack,

vielen Dank für deine ausführliche Antwort.
Warum wird nur der letzte Teil, also der tatsächliche Modulname in `registered_modules`
als Schlüssel verwendet? Das verhindert effektiv, dass man "foo::test" und "bar::test" als
verschiedene Plugins registrieren kann.
Wenn ich die Module in 'Registered_Modules', verwende ich den Shortcut, z.B. "Neu", um auf die
Attribute zuzugreifen. Dieser Einwand ist allerdings berechtigt. Danke.


1. Die print() Ausgaben werde ich noch entsprechend anpassen
2. Der Klassename OM wird noch geändert

3. Ich habe die __init__ Funktion nun abgeändert, damit keine
globale Variable mehr verwendet wird :

Code: Alles auswählen

class OM:

    def __init__(Self, Module="", Debug_Mode=False):

        Self.Debug_Mode = Debug_Mode
        Self.Registered_Modules = {}

        print(f"Debug Mode : {Debug_Mode}")
4. Nach deinem Ratschlag habe ich nun 'ObjectManager.register(self, module_name)'
in 'self.register(self, module_name)' geändert.

5. Anschließend habe ich wirklich einen Fehler bei folgender Zeile erhalten :

Code: Alles auswählen

Module_Descriptor = Module.split("::", -1)[-1]

Als ich in "Register_Module" alle Variablen von 'Module'
'zu Self.Module' abgeändert, habe, war der Fehler weg.

6. Wie rufe ich denn die Variable "Registered_Modules" nun auf ?

Wenn ich z. B. die geladene Config - Datei habe :

Code: Alles auswählen

   import OM
   
   OM_Manager = True
   
   class Config( ) :
       
       def __init__( Self ) :
           Self.Test = "Cool"
           Self.Config_Entries = { }
   
           
       def Config_Loader( Self ) :
       
           print( 'Testumgebung' )      
           Self.Config_Entries.update( { "Probe" : "1234" } )
		   
		   Test = OM.Registered_Modules	# GEHT NICHT
		   Test = Self.Registered_Modules    # GEHT NICHT
           
           print( OM.Registered_Modules )
Viele Grüße
YAPD
-----
Yet Another Python Developer
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@YAPD: Warum willst Du da drauf zugreifen? Das ist ja eigentlich Zustand der zum `ObjectManager` gehört. Und natürlich gehen die beiden kommentierten Zeilen nicht. Das hätte vorher / beim schreiben schon klar sein sollen. Und falls es das nicht ist, dann sollte man vielleicht noch nicht so komische komplizierte Konstruktionen bastelt. Wo ich noch nicht gesehen habe, dass das tatsächlich sinnvoll eingesetzt wird, weil ich da noch keinen wirklichen Mehrwert über normale ``import``-Anweisungen gesehen habe in der Verwendung. Und ich störe mich auch immer noch massiv an der Namensgebung. Dem viel zu generischen `ObjectManager` und das in `Registered_Modules` überhaupt keine Module stecken. Und die Namensschreibweise mag ich auch nicht mehr lesen. Dazu dann noch das `update()` um *ein* Schlüssel/Wert-Paar in ein Wörterbuch einzutragen — das ist alles kein *Python*. Das sieht extrem danach aus, als wenn das eigentlich eine andere Programmiersprache hätte sein sollen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

Hey Blackjack,

ich habe jetzt noch ein paar Dinge ausprobiert und es scheint, als müsste
ich die Variable "Registered_Modules" als globale Variable außerhalb der
__init__ Funktion definieren.

Code: Alles auswählen

CFG = Self.OM_Manager.Load_Module( 'Testumgebung::Test::Neu' )
CFG = Self.OM_Manager.Load_Module( 'Config' )
Vergleich der selben Funktionen mit lokaler & globaler Variable :

OM :

Code: Alles auswählen

class OM:

    def __init__(Self, Module="", Debug_Mode=False):

        Self.Debug_Mode = Debug_Mode
        Self.Registered_Modules = {}
Config :

Code: Alles auswählen

    def Config_Loader( Self ) :
    
        Test = OM.OM( )
        print( f"Registered Modules : {Test.Registered_Modules}" )
Ergebnis - Registered_Modules : { }

--------------------------------------------------------------------------------------------------------------------------------------------

Code: Alles auswählen

class OM:

  Registered_Modules = {}
  
    def __init__(Self, Module="", Debug_Mode=False):

        Self.Debug_Mode = Debug_Mode
        Self.Registered_Modules = Registered_Modules
Config :

Code: Alles auswählen

    def Config_Loader( Self ) :
    
        Test = OM.OM( )
        print( f"Registered Modules : {Test.Registered_Modules}" )
Ergebnis - Registered_Modules :

Code: Alles auswählen

{'Testumgebung::Test::Neu': <Testumgebung.Test.Neu.Neu object at 0x000001E4315DAAC0>, 'Config': <Config.Config object at 0x000001E4315DAAF0>}
Sehe ich das falsch ? Wie bekomme ich das gleiche Ergebnis OHNE globale Variable ??

Viele Grüße
YAPD
-----
Yet Another Python Developer
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Du solltest Deine Schreibweise dringend anpassen, ein "Self" sieht extrem komisch aus. Um Klammern werden keine Leerzeichen gesetzt und vor Doppelpunkten auch nicht.

Wenn man den selben Objekt-Manager nutzen möchte, würde man die Instanz am einfachsten überall herumreichen.

Ich habe aber noch nicht verstanden, was für ein Problem Du eigentlich lösen möchtest.
Warum heißt der Module-Lader Objekt-Manager? Was sind das für Objekte? Warum importierst Du Module nicht einfach mit `import`?
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@YAPD: Um das mal an Deinem eigenen Beispiel zu demonstrieren, so könnte das ohne die ganze unnötige Komplexität aussehen:

Code: Alles auswählen

from config import Config
from testumgebung.test.neu import Neu


class VTE:
    def __init__(self):
        Neu()  # ← Das hier sieht komisch/falsch aus!
        config = Config()
        config.load(self)
Wobei ein Modul pro Klasse wo Modul und Klasse dann immer gleich heissen, auch schon wieder so gar nicht nach Python aussieht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

Code: Alles auswählen

from config import Config
from testumgebung.test.neu import Neu


class VTE:
    def __init__(self):
        Neu()  # ← Das hier sieht komisch/falsch aus!
        config = Config()
        config.load(self)
Danke für das Beispiel, noch eine Frage dazu. Wenn ich am
Ende 20 Module habe, muss hier 20 mal am Anfang des
Scriptes importiert werden ? Wenn es hier nur um 1 oder
2 Module ging, würde ich wahrscheinlich die import - Funktion
nutzen ;)

Könntest du bitte noch meine Frage aus meinem Post von
Freitag 2. Juni 2023, 13:24 beantworten ? Da globale Variablen
offensichtlich ein NOGO sind ( warum wurden sie dann
implementiert ?? ), würde ich trotzdem gerne wissen,
wie ich es ohne realisiere.

VG
YAPD
-----
Yet Another Python Developer
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

Sirius3 hat geschrieben: Freitag 2. Juni 2023, 12:34 Wenn man den selben Objekt-Manager nutzen möchte, würde man die Instanz am einfachsten überall herumreichen.

Das ist doch genau mein Problem, oder nicht ? In meinem Post vom Freitag 2. Juni 2023, 13:24 habe ich
versucht zu erklären, dass ich eigentlich keine globale Variable verwenden möchte, der Object Manager aber nur einmal geladen werden soll.
Ich habe aber noch nicht verstanden, was für ein Problem Du eigentlich lösen möchtest.
Warum heißt der Module-Lader Objekt-Manager? Was sind das für Objekte? Warum importierst Du Module nicht einfach mit `import`?
An Ende hat die Anwendung ca. 20 Module. Daher möchte ich diese über einen Objekt Manager laden und dann bequem aufrufen können. Eine Variante mit 20 imports wie weiter unten von BlackJack vorgeschlagen,
würde ich für unsauberer halten, als diese Lösung :shock:

VG
YAPD
-----
Yet Another Python Developer
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@YAPD Ich sehe jetzt gerade nicht den Unterschied zwischen 20 mal ``import`` und 20 mal `load_module()` aufrufen‽

Die Frage wie mal globale Variablen vermeidet hat Sirius3 ja schon beantwortet: Man reicht die Objekte halt als Argumente weiter.

Globale Variablen so wie Du sie nutzt wurden nicht implementiert. Es gibt keinen Code in Python der extra da ist damit man solche globale Variablen haben kann. Und warum es ``global`` gibt, kann man tatsächlich fragen. Man käme auch ohne dieses Schlüsselwort aus. Man könnte sogar recht einfach den gleichen Effekt erreichen in dem man das Modul selbst in Funktionen/Methoden importiert.

Ansonsten ist eine Python-Philosophy das Python-Programmierer „consenting adults“ sind, also dumme/problematische Sachen nicht machen weil das verboten ist, sondern weil es dumm/problematisch ist. Globaler Zustand kann in bestimmten Situationen nützlich und sinnvoll sein. In den allermeisten Fällen ist es aber einfach nur unübersichtlich und begünstigt Fehler.

Edit: Zu den 20 mal was machen müssen: Falls da wirklich immer eine Klasse in einem eigenen Modul steckt, kann man da schon mal Importe sparen in dem man die sinnvoll zusammenfasst.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

__blackjack__ hat geschrieben: Freitag 2. Juni 2023, 19:33 @YAPD Ich sehe jetzt gerade nicht den Unterschied zwischen 20 mal ``import`` und 20 mal `load_module()` aufrufen‽
Der Unterschied ist, dass ich so in jeder Datei der Anwendung die notwendigen Module laden muss. Mit 'Load_Module( )' lade ich das Modul nur einmal über den Object Manager und rufe es dann über 'Registered_Modules' auf.
Die Frage wie mal globale Variablen vermeidet hat Sirius3 ja schon beantwortet: Man reicht die Objekte halt als Argumente weiter.
Ich habe nun schon mehrmals gefragt wie ich das realisiere und auf meinen Post vom Freitag 2. Juni 2023, 13:24 hingewiesen. Wie gebe ich denn das Objekt des Object Manager ( ich weiss, klingt blöd ) richtig weiter.
Leider kam keine Antwort darauf.
Globale Variablen so wie Du sie nutzt wurden nicht implementiert. Es gibt keinen Code in Python der extra da ist damit man solche globale Variablen haben kann.


Es tut mir leid, ich hatte vergessen, dass du bereits sämtlichen Python Code auf der Welt kennst & beurteilt hast. :lol: :lol: :D

Und warum es ``global`` gibt, kann man tatsächlich fragen. Man käme auch ohne dieses Schlüsselwort aus. Man könnte sogar recht einfach den gleichen Effekt erreichen in dem man das Modul selbst in Funktionen/Methoden importiert.
Globaler Zustand kann in bestimmten Situationen nützlich und sinnvoll sein. In den allermeisten Fällen ist es aber einfach nur unübersichtlich und begünstigt Fehler.
Könntest du mir kurze Beispiele geben, bei denen globale Zustände sinnvoll sind ?
Edit: Zu den 20 mal was machen müssen: Falls da wirklich immer eine Klasse in einem eigenen Modul steckt, kann man da schon mal Importe sparen in dem man die sinnvoll zusammenfasst.
Das werde ich auch versuchen.

VG
YAPD
-----
Yet Another Python Developer
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

YAPD hat geschrieben: Samstag 3. Juni 2023, 01:48 Der Unterschied ist, dass ich so in jeder Datei der Anwendung die notwendigen Module laden muss. Mit 'Load_Module( )' lade ich das Modul nur einmal über den Object Manager und rufe es dann über 'Registered_Modules' auf.
Das macht das Import-System von Python auch so. Module sind Singletons, d.h. wenn sie einmal importiert wurden, dann werden sie wiederverwendet. Ein Neuladen von bereits bekannten (importierten) Modulen findet nicht statt. Da stellt sich halt die Frage, was der Mehrwert von deinem Ansatz sein soll.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

`import modul` macht das selbe wie Dein `modul = OM.registered_modules["modul"]`, nur dass es eben schon in Python eingebaut ist, der übliche Weg, wie man Module in Python benutzt, viel flexibler ist, weniger fehlerhaft und jeder weiß, was damit gemeint ist.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Und ``from sys import modules as registered_modules`` gibt einem Zugriff auf die gewünschte Datenstruktur. Der einzige Unterschied ist, dass sich dies dann auf alle Module bezieht, die im Python-Interpreter geladen wurden. Vielleicht will man in einem bestimmten Kontext ja nur die Sichtbarkeit bzw. den Zugriff auf eine Teilmenge der Module erreichen. Das müsste man hier im Thread dann aber auch kommunizieren, um entsprechende Tipps zu erhalten. Und diese würden dann wohl auch auf Tools fürs Sandboxing verweisen. Auf diese Idee sind nämlich auch schon andere Leute gekommen.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@YAPD: Der Unterschied ist also, dass Dein Ansatz *mehr* Aufwand macht. Denn statt überall wo man ein Modul braucht einen ``import`` zu schreiben, muss man *zusätzlich* unterscheiden ob man ``load_module("package::modul")`` oder ``registered_modules["modul"]`` schreiben muss‽ Wobei Modulnamen nur einmal vorkommen dürfen, wohingegen ``import`` es erlaubt in verschiedenen Packages die gleichen Package/Modulnamen zu verwenden.

Und da es am normalen Importmechanismus vorbei geht, funktionieren auch Werkzeuge zur automatischen Dokumentation oder (semi)statischen Analyse nicht mehr. Selbst als menschlicher Leser wird es schwieriger nachzuvollziehen wie die Abhängigkeiten zwischen den Modulen aussehen wenn die nicht mehr alle oben im Modul, nach dem Docstring stehen.

Bei dem weitergeben verstehe ich die Frage nicht. Es gibt Funktions- und Methodenaufrufe, da kann man Argumente übergeben, und das macht man dann halt. Alles was eine Funktion oder Methode ausser Konstanten benötigt, wird als Argument(e) übergeben. Wenn eine Funktion den `ObjectManager` benötigt muss sie ein Argument dafür haben. Bei einer Methode entweder das gleiche, oder der wurde in der `__init__()` übergeben und an das Objekt gebunden.

Ich habe nicht bereits sämtlichen Python-Code auf der Welt gesehen oder beurteilt. Du hattest gefragt warum die Entwickler von Python globale Variablen implementiert haben. Das haben sie nicht. Das würde ja bedeuten, sie hätten extra Code geschrieben damit globale Variablen, so wie Du sie verwendest, funktionieren. Dann könnte man diesen Code ja einfach aus dem Python-Interpreter entfernen. Solchen Code gibt es dort aber nicht. Man müsste extra Code schreiben der das verhindert, aber so offen wie Python ist, wird sich das wahrscheinlich nicht verhindern lassen. Dazu müsste man zum Beispiel bei Objekten herausfinden können, ob sie veränderbar sind oder nicht. Denn Konstanten auf Modul- oder Klassenebene müssten ja weiterhin möglich sein. Oder man müsste alles auf Modul- und Klassenebene vor Veränderungen schützen. Da würde dann aber viel vorhandener Code nicht mehr funktionieren, denn Konstanten werden in einigen Fällen beim Import auch erst dynamisch aufgebaut. Klassen selbst beispielsweise. Monkey-Patching ginge dann nicht mehr. Und natürlich Code der globale Variablen verwendet, in den wenigen Fällen wo es Sinn macht. Und das alles um Leute davon abzuhalten sich in den Fuss zu schiessen, was Python so ziemlich grundsätzlich nicht macht. Wie gesagt ist die Philosophie der Python-Entwickler, dass die Programmierer wissen müssen was sie tun, und wo sie gefährliches Terrain betreten.

Beispiele für globale Variablen wären das `logging`-Modul aus der Standardbibliothek oder Qt das ein globales Objekt für das Anwendungsobjekt hat. Schlechtes Beispiel für globale Variablen ist die `pyplot`-API von Matplotlib. Superpraktisch für Leute die von Matlab & Co kommen und das interaktiv verwenden, aber wenn man das dann mal versucht in einem Programm nebenläufig zu verwenden, oder in einer länger laufenden (Web)Anwendung, passieren komische Sachen und es wird immer mehr Speicher verbraucht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
YAPD
User
Beiträge: 120
Registriert: Dienstag 27. Juli 2021, 23:23
Wohnort: Frankfurt am Main

Hi Blackjack,

ich habe nun noch 2 , 3 Dinge abgeändert und wollte wissen,
ob du das in Bezug auf globale Variablen und die Übergabe des
OM gemeint hast :

Entfernung der globalen Variable :

Code: Alles auswählen

class OM :
    
    def __init__(Self, Module = "" , Debug_Mode=False) :
       
        Self.Debug_Mode = Debug_Mode
        Self.Registered_Modules = { }

Aufruf der Methode 'Config_Loader' inkl. OM - Objekt :

Code: Alles auswählen

import OM as OM_Manager

class VTE:

    def __init__( Self ) :  
                
        Self.OM = OM_Manager.OM( )
        
        Self.Probe = Self.OM.Load_Module('Testumgebung::Test::Neu')
        Self.CFG   = Self.OM.Load_Module( 'Config') 
        Self.CFG.Config_Loader(Self.OM)   
Config :

Code: Alles auswählen

    def Config_Loader( Self , OM ) :
        print( OM.Registered_Modules ) 
Ausgabe :

Code: Alles auswählen

{'Testumgebung::Test::Neu': <Testumgebung.Test.Neu.Neu object at 0x000001EB374C17F0>, 'Config': <Config.Config object at 0x000001EB36E65850>}
VG
YAPD
-----
Yet Another Python Developer
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@YAPD: ist es wirklich so schwierig, sich einigermaßen an die üblichen Schreibweisen zu halten?

Code: Alles auswählen

class ObjectManager:
    def __init__(self, module="", debug_mode=False):
        self.debug_mode = debug_mode
        self.registered_modules = {}

Code: Alles auswählen

import object_management

class VineTestingExperiment:
    def __init__(self):  
        self.object_manager = object_management.ObjectManager()
        
        self.probe = self.object_manager.load_module('Testumgebung::Test::Neu')
        self.config = self.object_manager.load_module('Config') 
        self.config.config_loader(self.object_manager)
und

Code: Alles auswählen

    def config_loader(self, object_manager):
        print(object_manager.registered_modules)
Und ja, damit hast Du globale Variablen vermieden.

Und hier nochmal der Vergleich, wie man das wirklich macht:

Code: Alles auswählen

from testumgebung.neu import Neu
from config import Config

class VineTestingExperiment:
    def __init__(self):
        self.probe = Neu()
        self.config = Config()
        self.config.config_loader()
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei die letzten beiden Zeilen wieder etwas komisch aussehen und mal vermuten würde, das `Config` eine Klassenmethode zum Laden einer Konfiguration hätte, die dann auch eine Pfadangabe nimmt, wo die Konfiguration eigentlich her kommt.

Code: Alles auswählen

from pathlib import Path

from config import Config
from testumgebung.neu import Neu

CONFIG_PATH = Path("...")


class VineTestingExperiment:
    def __init__(self):
        self.probe = Neu()
        self.config = Config.load(CONFIG_PATH)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

@YAPD: Da du mit den Konventionen, wie man Python Code schreibt, nicht vertraut bist und auch Hinweise bezogen darauf nicht umsetzen kannst oder willst; dir das Verständnis fehlt, was das Problem mit globalen Variablen betrifft und deine gezeigte Umsetzung ja hier schon in Frage gestellt wurde, frage ich mich: Wo ist der Vorteil dieses "Modul Managers"? Und hast du genug Erfahrung in Python um das bewerten zu können? Erfahrung kommt mit der Anwendung nach und nach und hört hoffentlich nie aus. Und dazu gehört auch, seine Ideen und Kreationen zur Diskussion zu stellen um dann gesagt zu bekommen, dass das Blödsinn ist.
Für mich sieht das nach Blödsinn aus Unerfahrenheit aus. Passiert jedem ab und an.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ich habe ja nachdem ich mal gesucht hatte, weil mir dieser ObjectManager so bekannt vorkam, irgendwie das Gefühl YAPD sollte sich einfach mal auf Python einlassen, statt zu versuchen Perl-Nomenklatur und -Semantik irgendwie in Python prügeln zu wollen.

Hier ist das Original von 2021: viewtopic.php?p=391088#p391088

Die Begriffe Package, Module, Class, Methods werden da im Code verwendet wie Perl das handhabt, wo ein voll qualifizierter Name mit "::"-Trennern ein Package ist, Modul und Klasse im Grunde das gleiche sind, und Methoden einfach Funktionen in einem Modul sind. Python ist halt nicht Perl. 🙄
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten