Modulübergreifende Globale Variablen und Klassenvariablen

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.
Mikel
User
Beiträge: 4
Registriert: Samstag 10. Juni 2006, 20:56
Wohnort: Karlsruhe

Hallo,
ich suche nun schon einige Zeit herum und habe den Eindruck, dass es in Python nicht möglich ist auf irgendeine Weise eine Modulübergreifende globale Variable zu erstellen.

Stimmt das so???

Ich will in einer Methode der Klasse A im Modul A einen Wert ablegen und in einer anderen Methode der Klasse B im Modul B auf diesen Wert zurückgreifen finde aber keine Möglichkeit.

Zuletzt bin ich auf die Idee gekommen, den Wert in der Klasse A als Klassenvariable abzuspeichern und in der Klasse B einfach eine neue Instanz der Klasse A anzulegen um so auf die Klassenvariable wieder zugreifen zu können. - Aber selbst diese Methode funktioniert nicht da hier nur der Initialisierungswert der Klassenvariablen zurückgegeben wird, nicht aber der veränderte Wert.

Erstelle ich jedoch im Modul A eine neue Instanz der Klasse A kann ich korrekt auf die Klassenvariable zugreifen.

Daraus schließe ich das Klassenvariablen in Python nur dann korrekt funktionieren wenn das gesamte Programm in einem Modul geschrieben wird. Und das dürfte für größere Projekte doch wohl nicht angemessen sein.

Was habe ich da übersehen??
alles bezieht sich auf Python Version 2.4.1

mit Dank im Voraus
Mikel
murph
User
Beiträge: 622
Registriert: Freitag 14. April 2006, 19:23
Kontaktdaten:

ganz einfach, das schlüsselwort ist global:

Code: Alles auswählen

class A:
    def set_var(self):
        global a
        a = 99
class B:
    def get_var(self):
        print a
A().set_var
B().get_var
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Mikel hat geschrieben:Modulübergreifende globale Variable
Hi Mikel!

Vorab: Das ist jetzt eine persönliche Meinung von mir und es kann sein, dass sich diese mit irgendeiner Python-Philosophie nicht verträgt.

Du erstellst zwei Module um den Code zu **trennen**. Warum tust du das, wenn du diese Module doch wieder mit "Globalen Variablen" zusammenhalten möchtest? Trenne nur das, was du auch wirklich getrennt haben möchtest. Mache keine Ausnahmen.

Hier ein paar rechthaberische ;-) Gedanken dazu:

- Verbanne globale Variablen so gut es geht.

- Übergebe an Funktionen alles was diese zum Arbeiten brauchen und gib das Ergebnis zurück. Und das ohne wiederum irgendeine globale Variable zu verwenden.

- Wenn sich Funktionen auf einen nicht übergebenen Datenbestand ändernd auswirken, dann sollten diese Funktionen in Klassenmethoden umgewandelt werden und nur die Daten einer Klasseninstanz ändern.

- Module sind eine natürliche Trennmauer. Kein Modul sollte auf irgendwelche globale Variablen eines anderen Modules zugreifen oder sogar davon abhängig sein. Wenn das der Fall ist, dann sollte man am Programmdesign so lange drehen, bis wieder alles als Parameter an Funktionen oder Methoden übergeben werden kann.

- Es gibt wenige Situationen, unter denen globale Variablen sinnvoll sind -- aber es gibt sie. Global gehaltene Daten können z.B. als **Einstellungen** sinnvoll sein. Aber auch dann gehören diese in eine Datei (z.B. INI) und nicht in globale (modulübergreifende) Variablen.

Falls es sich wirklich um Einstellungen handelt, dann empfehle ich ein eigenes Modul, das sich um die Einstellungen kümmert. Dieses Modul hält die Daten in einer INI-Datei, was mit dem ConfigParser kein Problem sein sollte, und steht für alle anderen Module zum Importieren zur Verfügung.

Jetzt ist genug mit der Rechthaberei. :twisted:

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Mikel
User
Beiträge: 4
Registriert: Samstag 10. Juni 2006, 20:56
Wohnort: Karlsruhe

Hallo Gerold,
in den systemischen Überlegungen gebe ich dir grundsätzlich recht. Und den Config Parser kenne ich bisher noch nicht - werde ich mir mal ansehen. Es handelt sich in der Tat um Daten, die durchaus in einer ini-Datei gut untergebracht würden.


Lege ich die Daten in einer Datei ab, so sollte es in der Regel für einen Programmlauf reichen die Datei einmal zu lesen und ich würde die Daten (z.B. eine Datenbank, einen Pfad o.ä.) im Programm zur Verfügung haben. Aber dazu bräuchte ich die Möglichkeit die Information programmweit im Speicher abzulegen oder ich müßte entweder die Daten oder aber das Objekt dieser Klasse (die die ini-Datei gelesen hat) ständig an alle Klassen weiterreichen oder gar die Datei mehrfach lesen. Aufgrund der Verschachtelungstiefe der Klassen mißfällt mir das jedoch, da es unübersichtlich und aufwendig auch in der Ausführung wird, die Daten hundertemale in jedem Objekt separat zu speichern.

Auch in der Realität gibt es doch Rahmenbedingungen, die einmal für alle verbindlich festgelegt werden ohne sie jedem einzelnen separat zu erklären.

Aber unabhängig von der Theorie bleibt meine Frage, ob es dazu eine praktische Lösungsmöglichkeit in Python gibt.

Gruß
Mikel
Mikel
User
Beiträge: 4
Registriert: Samstag 10. Juni 2006, 20:56
Wohnort: Karlsruhe

Sorry Grold, bin auf Deine Frage gar nicht eingegangen,

Warum ich zwei Module erstelle?
Ein Grund der mir selbst überhaupt nicht gefällt ist ein merkwürdiges Verhalten des Debuggers in Eclipse mit Pydef, der je länger die Datei wird, immer häufiger Syntax-Error anzeigt an denen defenitiv keine sind und mich immer wieder zwingt einen Teil des Codes auzuschneiden und wieder einzufügen damit er ihn akzeptiert.
Aber auch aus Gründen der Übersichtlichkeit, ich bin zwar kein Verfechter der Doktrien der strengen OOP, jede Klasse in ein eigenes Modul zu schreiben, habe den Code bisher aber dennoch nach Kriterien der Übersichtlichkeit mit wenigen Klassen innerhalb eines Moduls getrennt.
Den Grundsatz trenne nur das was nicht zusammengehört sehe ich dann auf Paketebene.

mfg
Mikel
ryu
User
Beiträge: 41
Registriert: Dienstag 7. Februar 2006, 19:34

Mikel hat geschrieben:Ein Grund der mir selbst überhaupt nicht gefällt ist ein merkwürdiges Verhalten des Debuggers in Eclipse mit Pydef, der je länger die Datei wird, immer häufiger Syntax-Error anzeigt an denen defenitiv keine sind und mich immer wieder zwingt einen Teil des Codes auzuschneiden und wieder einzufügen damit er ihn akzeptiert.
Ich nutze Pydev nicht, aber es ist definitiv keine gute Idee sich, evtl. schlechteren, Codestil von der IDE aufzwingen zu lassen.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Mikel hat geschrieben:Es handelt sich in der Tat um Daten, die durchaus in einer ini-Datei gut untergebracht würden.
[...]
Aber unabhängig von der Theorie bleibt meine Frage, ob es dazu eine praktische Lösungsmöglichkeit in Python gibt.
Hi Mikel!

Ich kenne keine Möglichkeit, außer dem Weg über eine Datei, einem Server oder über die Windows-Registry. Allerdings sehe ich das Auslagern der Einstellungen in eine INI-Datei nicht so tragisch. Auch wenn du hundert Module hast, in denen du die Einstellungen brauchst, wirst du das Einlesen der INI-Datei wohl kaum bemerken. Die Datei bleibt im Cache und ist blitzschnell wieder ausgelesen.

Hier ein kleines Beispiel für die Verwendung des ConfigParsers:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-

import os.path
from ConfigParser import SafeConfigParser


class Settings(SafeConfigParser):
    """
    Verwaltet die Einstellungen
    """

    _initial_settings = {
        "PERSONAL": {
            "vorname": "Gerold",
            "nachname": "Penz",
            "home": os.path.abspath(os.path.expanduser("~")),
        },
        "DB": {
            "host": "(local)",
            "username": "gerold",
            "password": "irgendetwas",
        },
    }
    
    
    def __init__(self, filename, nodefaults = False):
        """
        Liest die Einstellungen aus einer INI-Datei aus, gleicht diese mit
        den Standardeinstellungen ab und schreibt diese wieder zurück.

        :param filename: Dateiname und Pfad zur INI-Datei
        :param nodefaults: Wenn True, dann werden keine Standardwerte in die
                           INI-Datei geschrieben. Das bedeutet auch, dass eine
                           nicht vorhandene INI-Datei nicht angelegt wird.
        """
        
        SafeConfigParser.__init__(self)
        
        self.filename = filename
        self.nodefaults = nodefaults
        
        # Eventuell vorhandene Einstellungen einlesen.
        # Ob die Datei existiert, oder nicht, ist egal.
        self.read(self.filename)
        
        # Nicht vorhandene Einstellungen setzen
        if not self.nodefaults:
            for section, options in self._initial_settings.iteritems():
                if not self.has_section(section):
                    self.add_section(section)
                for option, value in options.iteritems():
                    if not self.has_option(section, option):
                        self.set(section, option, value)
            self.save()
    
    
    def save(self):
        """
        INI-Datei schreiben
        """
        
        f = file(self.filename, "w")
        self.write(f)
        f.close()
    


if __name__ == "__main__":
    
    import time
    
    # Test 1 (100 x neu schreiben)
    alt = time.time()
    for i in range(100):
        settings = Settings("test.ini", nodefaults = False)
    print "Jedes mal die Standardwerte schreiben:", time.time() - alt
    
    # Test 2 (1 x neu schreiben und 99 x auslesen)
    alt = time.time()
    settings = Settings("test.ini", nodefaults = False)
    for i in range(99):
        settings = Settings("test.ini", nodefaults = True)
    print "Nur einmal die Standardwerte schreiben:", time.time() - alt
    print

    # Test 3 (Zusätzlichen Wert hinzufügen)
    try:
        settings.set("DB", "readonly", "True")
        settings.save()
    except:
        pass
    
    # Test 4 (Werte anzeigen)
    for section in settings.sections():
        print section
        print "-" * 30
        for option in settings.options(section):
            print "%-15s: %s" % (option, settings.get(section, option))
        print "-" * 30
        print
Ergebnis:

Code: Alles auswählen

Jedes mal die Standardwerte schreiben: 0.186999797821
Nur einmal die Standardwerte schreiben: 0.0310001373291

PERSONAL
------------------------------
nachname       : Penz
vorname        : Gerold
home           : C:\Dokumente und Einstellungen\Gerold
------------------------------

DB
------------------------------
username       : gerold
host           : (local)
password       : irgendetwas
readonly       : True
------------------------------
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
BlackJack

Mikel hat geschrieben:Daraus schließe ich das Klassenvariablen in Python nur dann korrekt funktionieren wenn das gesamte Programm in einem Modul geschrieben wird. Und das dürfte für größere Projekte doch wohl nicht angemessen sein.
Kannst Du dazu mal ein bischen Quelltext zeigen? Klassenvariablen funktionieren auch mit mehreren Modulen. Das wäre in der Tat eine komische und ziemlich willkürliche Einschränkung.
Mikel
User
Beiträge: 4
Registriert: Samstag 10. Juni 2006, 20:56
Wohnort: Karlsruhe

Hallo,

zuerst einmal Danke für die Antwort und gleich noch den ausführlichen Test von Gerold. Habe mich mittlerweile damit abgefunden das Python hier anders funkt als ich mir das gedacht habe. Halte den Weg über die Festplatte zwar immer noch für umständlich, aber was ich wissen wollte, ob es einen einfachen Weg in Python gibt ist damit wohl beantwortet.

Und was ich jetzt auch weiß ist, dass ich mir eine ordentliche Menge Programmiererei hätte sparen können, da es einen fertigen ConfigParser in Python gibt. Habe den ganzen Kram nämlich zu Fuß codiert.

man lernt halt nie aus! Denke, das war aber eine gute Übung, um mich mit Python erstmal vertraut zu machen.


Und nun zur Frage von BlackJack nach dem merkwürdigen Verhalten der Klassenvariablen.

Hier bekomme ich das Verhalten nicht mehr ganz klar herausgearbeitet. Denke aber es lag daran, dass ich während die Initialisierung meiner ersten Instanz noch lief, durch eine neue Instanz - erzeugt in einem anderen Modul - auf die von der ersten Instanz bereits geänderten Klassenvariablen zugriff.
Das in Code darzustellen ist daher sinnlos (wahrscheinlich aber irgendwie möglich), da es voraussetzt, dass die Import-Anweisungen der Module zwangsweise einen Zirkelbezug aufweisen müssen, was ich bei meinem Test mitten in meinem Programm allerdings anfangs ignorierte und dann leider nicht mehr bedacht hatte.
Bei meinen Versuchen das Verhalten mit sauber programmiertem Code nachzuvollziehen funktionierten die Klassenvariablen auch über Modulgrenzen hinweg immer sauber.

mfg
Mikel
CapJo
User
Beiträge: 26
Registriert: Donnerstag 27. April 2006, 13:17

Wenn es keine Daten sind die man per Hand editieren soll, kann man auch ganz einfach den Pickler verwenden oder den cPickler(deutlich schneller).

Ich hab das bei einer grafischen Oberfläche gemacht und die zuletzt besuchten Verzeichnisse per dump in eine Datei geschrieben.
Leto
User
Beiträge: 6
Registriert: Donnerstag 27. Juli 2006, 15:23

Wie sähe es denn aus, wenn man den Inhalt der ini-Datei nur einmal ausliest und in eine eigen Klasse mit eigenen Attributen schreibt.

Auf diese Klasse kann doch dann jederzeit zugegriffen werden.
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Hallo,

mal eine Überlegung zur Ursprungsfrage: Ich mache es in GUI-Code manchmal so, daß ein Widget das "self" des Mainframes übergeben bekommt und dann über self.parent zugreifen kann. Damit ist eine Variable zwar nicht echt "global", aber es kommt dem Wunsch doch ziemlich nahe, oder?

Gruß,
Christian
Tristan
User
Beiträge: 3
Registriert: Donnerstag 10. Januar 2008, 09:52

Hi,

ich habe vor 6 Monaten angefangen mich mit Python auseinanderzusetzen und mich hat ziemlich lange das gleiche Problem geplagt (globale Variablen unter Modulen austauschen). Bin gerade über Google zu dieser Diskussion gelangt und wurde aus irgendeinem Grund derart inspiriert, dass mir prompt eine Lösung dazu eingefallen ist. Dieses Thema ist zwar anderthalb Jahre alt, aber ich denke die Sache ist interessant genug um sie für zukünftige Google-Hilfesuchende zu sichern.

Hier ist meine Lösung zu diesem Problem:

Man erstellt sich ein Modul, das die globalen Variablen enthalten soll, z.B. "globals". In diesem Modul trägt man nun die Namen der erwünschen globalen Variablen ein und setzt sie gleich None:

------------
globals.py
------------
globalVar1 = None
globalVar2 = None
------------

Im Programmstart-Modul (das Hauptmodul) importiert man nun globals.py und setzt dort die gewünschten Werte für die globalen Variablen BEVOR man irgendein anderes Modul importiert, das die globalen Variablen verwenden will.

-------------
main.py
-------------
import globals
globals.globalVar1 = "Mein globaler String"
globals.globalVar2 = 12

# andere Module erst importieren
# nachdem die globalen Variablen
# gesetzt wurden!

import anderesModul.py
-------------

Alle Module die von main.py importiert werden können nun ihrerseits globals.py importieren und auf die globalen Werte zugreifen.

-------------
anderesModul.py
--------------
from globals import *

print globalVar1
print globalVar2
---------------

Man kann das Modul "globals.py" auch vollkommen leer lassen, da die Namen nicht vorhanden sein müssen um in main.py gesetzt werden zukönnen. Zu Dokumentationszwecken kann es aber definitiv nicht schaden wenn man die Namen dort einträgt und halt gleich None setzt.

Ich hoffe das hilft.

Gruß, Tristan
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Tristan hat geschrieben:Man erstellt sich ein Modul, das die globalen Variablen enthalten soll, z.B. "globals".
Hallo Tristan!

Willkommen im Python-Forum!

Zwei Punkte:

1.) Verwende bitte nicht den Namen "globals" für dein Modul. ``globals()`` ist eine eingebaute Funktion, die die modul-globalen Variablen zurück gibt.

2.) Sobald du das Modul mit "*" Stern importierst, verlierst du die Beziehung zu den Variablen. Änderungen an den Variablen werden von den anderen Modulen nicht mehr bemerkt.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Tristan
User
Beiträge: 3
Registriert: Donnerstag 10. Januar 2008, 09:52

Hi Gerold,

danke für das Willkommenheisen. Okay, den Namen "globals" zu verwenden scheint keine gute Idee zu sein... :oops: , aber es war ja nur ein Beispiel. Im Folgenden werde ich das Modul mit den globalen Variablen "defs" statt "globals" nennen. So ist es auch in meinem Code, weshalb ich das Prob mit dem Namen "globals" nicht bemerkt habe.

Du hast Recht, sobald man die globalen Vars mit * importiert, sind sie quasi "read-only" und Veränderungen wirken sich nur auf das aktuelle Modul aus. Man kann dies aber einfach umgehen, indem man die globalen Vars, die von jedem Modul aus "gesetzt" werden sollen dürfen, immer (beim Lesen & Schreiben) mit dem Modulnamen referenziert. Also in meinem Beispiel statt

import * from defs
globalVar1 = "neuer Wert" # wirkt sich nicht auf andere Module aus!

macht man

import defs
defs.globalVar1 = "neuer Wert"

wirkt sich auf alle Module aus, solange module globalVar1 immer über das "defs" Modul referenzieren, also eben genauso:

import defs
print defs.globalVar1

So hat man effektiv von jedem Modul aus beschreibbare "globale" Variablen.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Happy programmglobales Debugging. Das ist ja noch toller als modulglobales Debugging, weil die Werte von überall geändert werden können.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Tristan
User
Beiträge: 3
Registriert: Donnerstag 10. Januar 2008, 09:52

Klar sollten von einer Software-Design Perspektive betrachtet möglichst keine oder nur sehr wenige Variablen global sein. In manchen Situationen erweisst es sich aber als extrem nützlich. So gibt es manchmal z.B. allgemeine Objekte die fast überall gebraucht werden und wo der Komfort an einer Stelle auf diese zugreifen zu können, gegenüber dem "Verkapselungs-Ideal" überwiegt.

Globale Variablen, spährlich verwendet, entsprechen vom Nutzen her in etwa dem "Singleton" design-pattern, das anders in Python nicht umzusetzen ist. Vorallem wenn solche globale Variablen nur zum lese-Zugriff verwendet werden, ist der Gebrauch in bestimmten Situationen durchaus berechtigt, denk ich.
Zap
User
Beiträge: 533
Registriert: Freitag 13. Oktober 2006, 10:56

Ich kann mich Tristan nur anschließen. Ich habe eine solche Methode ebenfalls angewendet in welcher Initialisierte Werte Projektweit zur verfügung gestellt wurden.
Allein der Gedanke das über irgendwelche externen Files zu händeln missfällt mir.
Die einzige Gefahr die man dabei immer im Auge behalten muss, ist (wie Gerold schon sagte), dass man das Modul immer auf dem gleichen Wege importieren muss.

Mal ne andere Frage: Wenn man das über Dateien machen würde kann man es ja schlecht über Tempfiles machen, da die anderen Module ja nicht wissen wie diese heissen. Macht man es über feste Namen gibts verherende Konflikte wenn man zwei Instanzen einer Applikation hat.
Wie könnte man diese Probleme geschickt lösen?
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo!

Ich möchte allgemein darauf hinweisen, dass dieser Thread noch aus dem Jahr 2006 stammt. Und sich inzwischen alle Beteiligten viel besser mit Python auskennen, als das vor zwei Jahren der Fall war.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Zap
User
Beiträge: 533
Registriert: Freitag 13. Oktober 2006, 10:56

Ich möchte allgemein darauf hinweisen, dass dieser Thread noch aus dem Jahr 2006 stammt.
Danke für den Hinweis, das ist mir voll nicht aufgefallen.
Und ich steig auch noch mit in die Diskusion ein :oops:
Antworten