Was ist eine globale Variable und warum soll man sie nicht benützen?

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.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Mir ist nicht ganz klar, was eine globale Variable ist. Ich würde mich freuen, wenn man das einmal im ersten Schritt abklären kann, bevor man zum zweiten Teil übergeht.

Mir ist es nicht gelungen eine globale Variable zu erstellen. Wenn ich eine im Main Script erstellt hatte, war sie in einem importierten Modul nicht bekannt, Wenn ich sie in einem Modul erstellt hatte, dann war sie im Main Script nur direkt bekannt, wenn man einen Sternchenimport machte.

In anderen Modulen, in denen man keinen Sternchenimport macht, ist diese Variable nicht direkt bekannt. Variablen sind also normalerweise gar nicht wirklich global bekannt, sondern nur modul global.

Meint man jetzt, dass es in einem Modul keine modulspezifischen Variablen geben soll? Auch als Ersatz kein Dictionary, kein Objekt oder sonst etwas. Ja wie soll das ohne etwas gehen?

Auf ein Modul kann ich zugreifen, indem ich den Modulnamen oder ein Alias und einen Punktoperator verwende. Wenn ich eine Klasse habe und ein Objekt instanziere, kann ich auch mit dem Punktoperatur zugreifen. Darf ich das, wenn ich das Objekt außerhalb einer Klasse also modul global definiert habe.

Wenn ich das darf, warum darf ich das nicht bei einem Dictionary?

Wenn ich das bei einem Objekt nicht darf, warum darf ich dann auf ein Modul zugreifen, das ist ja syntaktisch auch nichts anderes als ein Objekt?

Mir ist das also ziemlich unklar.
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Alfons Mittelmeyer hat geschrieben:Variablen sind also normalerweise gar nicht wirklich global bekannt, sondern nur modul global.
Genau.

Es gibt zu der Frage im Titel schon jahrelange Diskussionen und entsprechende Texte, die man online nachschlagen kann, ohne alles nochmal ausführlich zu wiederholen.

Grundsätzlich "kann" man so etwas benutzen, dann sollte man aber kein Anfänger sein und schon gut programmieren können. Die Fehlersuche wird aber deutlich erschwert. Deshalb rät man grundsätzlich davon ab.

Variablen sollen explizit beim Aufruf von Funktionen übergeben werden und nicht einfach "vom Himmel fallen", so dass man den kompletten restlichen Code durchsuchen muss, wo sie nun wann warum erstellt oder modifiziert wurde.

Nun daraus zu schließen, dass gar keine Objekte erstellt werden sollen... ja, wie finde ich das? Seltsam logisch. Nicht sehr naheliegend. Vor allem auch offensichtlich falsch. Jedenfalls wieder ein schöner Troll. Willkommen zurück, Alfons!
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: ein globaler Zustand ist ein Zustand, der irgendwo verändert wird und an einer anderen Stelle Auswirkungen hat, ohne dass explizit klar gemacht wird, wie diese beiden Stellen miteinander verknüpft sind. Normalerweise findet solch eine Verknüpfung durch eine Aufrufhierarchie mit Argumenten statt, man hat also nur lokale Variablen. Ob der globale Zustand jetzt über ein Modul, eine Liste, ein Wörterbuch, ein Objekt referenziert wird, ist vollkommen egal.

Die einfachste Regel, woran man einen globalen Zustand erkennt, ist dann, wenn eine Variable/ein Objekt in einer Funktion verändert wird, ohne dass dieses Objekt als Argument übergeben wurde.

Ob eine Variable global ist oder nicht, hängt also nicht davon ab, wie oder wo sie definiert wurde, sondern nur, wie sie verändert wird.

Alles, was nicht verändert wird, ist eine Konstante, und somit auch global erlaubt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Kebap hat geschrieben:
Alfons Mittelmeyer hat geschrieben:Variablen sind also normalerweise gar nicht wirklich global bekannt, sondern nur modul global.
Genau.
Danke, jetzt ist geklärt, dass es also nicht um ganz global sondern nur um Modul global geht.

Die weitere Frage, was ist eine Variable?

Handelt es sich bei einer Variablen um ein Objekt, bei dem man den Zuweisungoperator anwenden kann und ihn nicht nur einmal anwendet, weil es dann eine Konstante wäre? Oder sind auch die übrigen Objekte Variablen, weil man die auch ändern kann, etwa bei einem Button die Farbe.

Ich würde denken, weil man den Button nur einmal erzeugt und die Referenz einem Namen zuweist, dass es sich dabei um eine Konstante handelt. Und auch dass ein Dictionary eine Konstante wäre, wenn man die Referenz nicht ändert, also keine Neuzuweisung macht, etwa wieder mit my_dictionay = {} oder den Verweis auf ein anderes Dictionary.

Das Ansprechen mit my_dictionary['key'] = value ist keine Neuzuweisung, weil das dictionary dasselbe bleibt. Dieses Assignment bedeutet hier nur etwas ähnliches wie ein Methodenaufruf für das Dictionary, sozusagen eine set Methode

Ich würde nicht einsehen, dass ich hier extra eine erweiterte Dictionaryklasse mit einer set Methode machen muss, damit es nicht wie eine Variablenzuweisung aussieht.
BlackJack

@Alfons Mittelmeyer: Eine Konstante ist wie schon gesagt ein Konstanter *Wert*. Wenn Du das Dictionary änderst, oder den Zustand eines Buttons, dann hat sich der Wert geändert, und ist damit nicht konstant. Und wenn es nicht konstant ist, dann ist es variabel.
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es ist fuer die Betrachtung der "Schaedlichkeit" von globalem Zustand zuerstmal irrelevant, ob das "global" statement in Python nun modul-globalen Zustand erzeugt, oder interpreter-weiten. Denn ich kann auch nur auf ein einziges Modul schauen & die Auswirkungen schlechter Programmierung diskutieren. Und wenn ich auf eben solchen globalen Zustand immer nur mit dem Modul zugreife, anstatt den Namen lokal neu zu binden durch einen "from"-import, dann verhaelt sich der ganze Kram auch so, wie es "global" suggeriert. Und wenn ich wirklich will, dann kann ich Dinge auch in __builtins__ packen, und dann ist das ein ueberall definierter Nman. Das ist also wirklich ein Nebenkriegsschauplatz.

Der Grund warum globaler *veraenderlicher* Zustand schlecht ist, ist einfach: ich habe keine Ahnung, wer den Zustand wann wie veraendert hat, und kann darum keine Aussage darueber treffen, ob ein bestimmtes Stueck Code aus dem Kontext gegriffen tut was es tun soll.

Und dafuer ist es auch voellig unerheblich, ob ich nun Namen mit global verwende, oder ein Objekt wie dein my_dictionary welches *veraenderbar* ist. Womit auch deine Denke, das ein einmal erzeugtes Objekt automatisch um eine Konstante handelt. Das ist Unfug. Die Objektidentitaet ist zwar fix, der Code, der darauf arbeitet, kann sich aber anders verhalten.

Code: Alles auswählen

def foo():
     if button.color == "red":
         print "alles gut'
     else:
         raise Exception("Fuerchterlicher Fehler")
Analoges gilt fuer alle veraenderlichen Datentypen, in Python also *fast* alles.

Nun ist es natuerlich in einer Sprache wie Python (ist ja nicht Haskell) nur sinnvoll moeglich zu programmieren, wenn man Zustand veraendert.

Nur sollte man das eben so explizit wie moeglich machen. Eine kleine Hierarchie:

Code: Alles auswählen

liste = [1, 2, 3]

def am_schechtesten(): 
      # veraendert ein globales Objekt
      liste[:] = [v**2 for v in liste]
      
def schlechter(liste):
      # veraendert ein als Parameter uebergebenes Objekt
      liste[:] = [v**2 for v in liste]
    
def besser(liste):
      return  [v**2 for v in liste]
      
def woanders():
      liste = besser(liste)  # oder ggf. liste[:] = besser(liste)
Ich ueber "besser" nachdenken, ohne dabei im Kopf haben zu muessen, ob die liste, die ich daran uebergeben habe, noch woanders gebraucht wird, oder auch nicht - denn ich veraendere sie nicht.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

@Alfons Mittelmeyer: du solltest dir _dringend_ nochmal Scoping (=Gültigkeitsbereich von Variablen), Namespacing und das Import-System von Python verinnerlichen. Genau so solltest du dein Denke über Bord werfen, dass _deine_ Assoziation zu einem Wort (wie Variable) nicht die sein muss wie der Kontext, in dem eine Programmiersprache (wie Python) es benutzt. Ansonsten wirst du beim Programmieren konstant an deiner eigenen Einstellung (und nicht an deinem Wissen) scheitern.

Gruß, noisefloor
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Eine Konstante ist wie schon gesagt ein Konstanter *Wert*. Wenn Du das Dictionary änderst, oder den Zustand eines Buttons, dann hat sich der Wert geändert, und ist damit nicht konstant. Und wenn es nicht konstant ist, dann ist es variabel.
Da fast alles den Zustand ändern kann, sollten dann wohl nur Klassendefinitionen und Funktionsdefinitionen und imports und Aufrufe auf oberer Ebene vorkommen?

Funktionen sind also für modulinterne Verwaltung nicht geeignet, da sie ja modulinterne Daten verarbeiten sollen. Und die darf es nicht geben, da ein Objekt ja auch eine globale Variable wäre.

Also muss sich alles innerhalb eines Objektes einer Klasse abspielen. Wie aber kann das gehen, wenn ein Modul dieselbe Instanz davon benötigt, wie ein anderes Modul. Eine Funktion soll keine Daten modul global speichern. Und ein erneuter Klassenaufruf würde eine neue Instanz liefern, die unbrauchbar wäre.

Wie also wie mache ich das ohne globale Variable?

Code: Alles auswählen

class Manegement():

    def __init__(self):
        self.dictionary = {}

    def set(self,key,value):
        self.dictionary[key] = value

    def get(self):
        return self.dictionary[key]


def init_modul():
    return Manegement()


def get_management():
    # ??? soll dieselbe Instanz wie bei init_modul liefern
    # aber wie mache ich das ohne globale Variable
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du machst das ohne globale Variable indem du auf der *Aufrufer*-Seite statt einem init_modul einfach ein Management-Objekt erzeugst, und darauf arbeitest.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

__deets__ hat geschrieben:Du machst das ohne globale Variable indem du auf der *Aufrufer*-Seite statt einem init_modul einfach ein Management-Objekt erzeugst, und darauf arbeitest.
Nein, das Dictionary soll von verschiedenen Modulen genutzt werden können. Wenn es in einem Modul gefüllt wird, soll ein anderes Modul deswegen kein anderes haben, welches leer ist.

Es macht keinen Sinn, dass ein Wörterbuch nur der lesen darf, der es schreibt.
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dann gibst du dem anderen Modul die konkrete Instanz eines Managment-Objektes mit. Als explizites Argument an Aufrufe, oder wiederum per Konstruktor als Member, auf dem dann sukzessive zugegriffen wird.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

__deets__ hat geschrieben:Dann gibst du dem anderen Modul die konkrete Instanz eines Managment-Objektes mit.
Das geht nicht, denn dann wäre es eine globale Variable

Code: Alles auswählen

class Management():
 
    def __init__(self):
        self.dictionary = {}
 
    def set(self,key,value):
        self.dictionary[key] = value
 
    def get(self):
        return self.dictionary[key]
 
# Das soll man nicht tun, weil das modul global ist
_my_management = Management()
 
# jetzt eigentlich überflüssig, weil gleich wie get_manegement
def init_modul():
    return _my_management
 
 
def get_management():
    # ??? soll dieselbe Instanz wie bei init_modul liefern
    # aber wie mache ich das ohne globale Variable
    return _my_management
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Je mehr ich darüber nachdenke, umso mehr bin ich derselben Überzeugung wie Rich Hickey:

Bild

Je weniger Zustandsänderungen - egal ob global oder lokal - in meinen Programmen vorkommen, umso einfacher fällt mir das Nachdenken über die Programme. Ohne Zustandsänderungen kann man zwar nicht programmieren, aber man sollte sie soweit wie möglich isolieren, damit man nicht an anderer Stelle darüber stolpert. Ich nenne das den ceteris paribus style. Glücklicherweise habe ich bereits früh gelernt (meine 2. Programmiersprache war Prolog), wie oft man (fast) gar keinen Zustand braucht um ein nützliches Programm zu schreiben. Als Programierer sollte man Prolog, Lisp und/oder Haskell o.ä. lernen, damit man sieht, dass das Zustands-Manipulieren fast immer unnötig ist und nur dem Denken in Maschinen-Strukturen geschuldet ist. John McCarthy hat damals Lisp explizit dazu erfunden, um seinen Studenten Turing-Maschinen einfacher und mathematischer erklären zu können. Die gängige Hardware ähnelt zwar immer noch mehr einer klassischen Turing-Maschine, als dem Ersetzungs-Modell von Lisp, aber das bedeutet ja nicht, dass wir Programmierer in den Kategorien einer Turing-Maschine denken müssen - schließlich sind auch Lisp, Prolog und Haskell Turing-vollständig. Programmiersprachen sind für Programmierer und deren Probleme gemacht, sonst bräuchten wir alle bloß in Assembler oder gleich binär zu programmieren. Und unsere menschlichen Gehirne sind nicht besonders gut darin, globale Auswirkungen von Zustandsänderungen zu beurteilen. Daher ist es sinnvoll, globalen Zustand (und IMO jede Art von Zustandsänderungen) soweit wie möglich zu vermeiden.

Apropos John McCarthy:

Bild
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

was hast du denn am Ende _wirklich_ vor bzw. was ist das Ziel? Das Beispiel ist ja hochgradig abstrakt. Zumal die Funktion `init_modul` Quatsch ist, weil die nichts macht. Und die `get` Methode funktioniert so auch nicht.

Sollen _alle_ Instanzen von Management das gleiche `dictrionary` sehen und nicht - wie es im Moment ist - jede Instanz sein eigenes?

Grundsätzlich kann man Variablen so definieren, dass alle Instanzen einer Klasse die selben Werte sehen. Das macht man aber nur mit Konstanten (siehe BlackJacks Post), weil sonst pro Instanz ja nicht klar ist, welche Werte von Methoden gerade verarbeitet werden, was man nicht will (siehe div. Post weiter oben).

@pillmuncher: sehe ich ähnlich. Wobei ich als Hobby-Programmierer und Nicht-Berufs-ITler, der zu 95% Python benutzt, Haskell schwer verständlich fand, also den Ansatz der Die-hard funktionalen Programmierung umzusetzen.

Gruß, noisefloor
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Natuerlich geht das. Du zeigst doch gar eh keinen Code, der das benutzt, der deshalb nicht funktioniert.

Code: Alles auswählen

# main.py
from mein_manager import Management()
from wasanderes import ObjektDasManagementBraucht

def main():
      m = Management()
      o = ObjektDasManagementBraucht(m)      
Damit habe ich die Abhaengigkeiten klar modelliert, und kann ggf. Code schreiben, der mehrere, nicht-implizite Instanzen von Management benutzen *koennte*. Nur weil man das akut nicht muss, heisst es nicht, das es nicht irgendwann sinnvoll waere. Und so schreibt man dann seinen Code.

Und ein entscheidender Vorteil (gerade bei einer dynamisch typisierten Sprache wie Python) ist, dass ich ObjektDasManagementBraucht auch mit einem MockManagement fuettern kann, dass zB fuer tests andere Dinge tut.

Ohne diesen Gedanklichen Sprung wirst du global nicht vermeiden koennen - es einfach nur wegzulassen als Schluesselwort, aber immer noch gedanklich globalen Zustand zu wollen, ist in die Tasche gelogen. Statt "global name" einfach "ein_dict["name"]" zu schreiben ist immer noch die gleiche Semantik.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Alfons Mittelmeyer: zum Thema globaler Zustand ist Sirius3s Antwort ausführlich und gut verständlich.
Alfons Mittelmeyer hat geschrieben:Die weitere Frage, was ist eine Variable?
Nun halte dich fest oder setze Dich: Python kennt gar keine Variable.

Python kennt nur Objekte. Und Typen von Objekten. Diese können mutable, immutable oder callable sein. Und Python kennt Namensräume, die wiederum Objekte sind. Sowie Referenzen auf Objekte.

Sobald dies verinnerlicht ist steht einer klaren, kompakten, sauberen und flüssig lesbaren pythonischen Programmierung nichts mehr im Wege. Und dann gibt es auch keine Entschuldigungen mehr, es anders zu machen; jedenfalls nicht mit Python.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

pillmuncher hat geschrieben: wie oft man (fast) gar keinen Zustand braucht um ein nützliches Programm zu schreiben.
Ja es geht um das fast. Weil es da immer wieder welche gibt, die darauf bestehen, dass man globale Variablen gar nicht und auf gar keinen Fall benützen soll.

Und in Python selber gibt es globale Zustände. So verwaltet etwa import Zustände in dieser Art:

Code: Alles auswählen

_imported = {}

def _module_load(filename):
    # load the module and create something like an instance, for example:
    exec(compile(open(filename, "r").read(), filename, 'exec'))
    return locals()['Access']()

def module_import(filename):
    if filename in _imported:
        return _imported[filename]
    else
        module_instance = _module_load(filename)
        _imported[filename] = module_instance
        return module_instance
Nach dem ersten import erhält man mit weiteren imports dieselbe Referenz, also keine neue Instanz des Moduls. Der import merkt sich also einen globalen Zustand und liefert so das Gemerkte.

Ist das falsch?

Ach so, diesen Code würde man nicht benützen, weil es ja schon import gibt. Für mich interessant ist dagegen die Funktion _module_load, die ich anders benannt habe und gerne benütze.
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es ist erstaunlich zu sehen, wie sehr du dich weigerst dein Weltbild an neue Erkenntnisse anzupassen, und stattdessen per mal mehr mal weniger gelungenem Gegenbeispielen versuchst zu belegen, das was wir dir hier erklären, nicht stimmt.

Ja, es gibt globalen zustand. Python kann das, mehr noch, braucht das. ZB für Code-Objekte (Also Module etc), Konstanten. Und ja, es ist auch gelegentlich sinnvoll Dinge zu cachen. Das ist so sinnvoll, man hat ein Lehnwort dafür eingeführt.

Nur ist der Umkehrschluss nicht richtig. Nur weil es etwas gibt, ist es nicht das Mittel der Wahl für alles. Jedes Stück Code das du hier gepostet hast, in dem eine lokale Variable vorkommt, benutzt offensichtlich keinen globalen Zustand für alles, wofür das möglich wäre. Deiner verqueren Logik nach darf das aber nicht sein, denn man kann ja global, also muss auch. Also denn. Ich will hier nie wieder eine lokale Variable sehen von dir. Falls du sie doch gebrauchst, erklärst du das hoffentlich genauso ausufernd, wie du das hier von uns verlangst.
BlackJack

@Alfons Mittelmeyer: Es geht nicht um das fast. Das man Zustände braucht bedeutet nicht das die global sein müssen. Eben das sollten sie nicht.

Das Importbeispiel ist kein gutes weil es sich bei den Modulen um Konstanten handelt — sofern sich der Programmierer daran hält globalen Zustand zu vermeiden, was er ja sollte. Es sollte keinen Unterschied machen ob ein Modul mehr als einmal importiert wurde, oder ob es beim ersten Import gecached wurde.

Und natürlich gibt es Ausnahmen, die wurden aber hoffentlich von Leuten geschrieben die sich der Problematiken bewusst sind, also nicht fragen was Variablen und Konstanten sind.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Es geht nicht um das fast. Das man Zustände braucht bedeutet nicht das die global sein müssen. Eben das sollten sie nicht.
Unbedingt global muss natürlich nichts sein. Es ginge auch anders, aber ist anders immer sinnvoll? Ein größeres Programm besteht aus Komponenten, diese Komponenten müssen miteinander kommunizieren, Ein Fahrzeug hat dafür Bussysteme, etwa MOST Bus, CAN Bus, LIN Bus, Flexray.

Soll man nach deiner Auffassung allen Objekten, bei denen Unterobjekte diese Bussysteme eventuell benötigen, die Zugriffsobjekte auf diese Bussysteme als Parameter übergeben und sie von Ebene zu Ebene weiterreichen?

Denn genau darum geht es mir, nämlich ob diese zentralen Signalsysteme global zur Verfügung stehen dürfen oder ob man sie überall als Parameter mit übergeben soll?
Antworten