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

jerch hat geschrieben:Übrigens ist es völlig legitim, Konstanten schrittweise zu initialisieren, wenn es sich anbietet. Das sollte aber vor der ersten Benutzung im Hauptkontrollfluß fertig sein, also nix nachträglich ranfummeln
Was ist der Hauptkontrollfluß?

Ist dies das, was bei tkinter in der mainloop geschieht? Vorher, beim Aufbau der Gui - vor mainloop - sollen Verweise auf bestimmte Methoden bestimmter Objekte in das Dictionary eingetragen werden, unmittelbar nach der Erzeugung der Objekte.
BlackJack

Damit ist der Code gemeint der auf Modulebene ausgeführt wird wenn das Modul importiert wird und der nur Konstanten, Funktionen, und Klassen definiert.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Alfons Mittelmeyer hat geschrieben:Ist dies das, was bei tkinter in der mainloop geschieht? Vorher, beim Aufbau der Gui - vor mainloop - sollen Verweise auf bestimmte Methoden bestimmter Objekte in das Dictionary eingetragen werden, unmittelbar nach der Erzeugung der Objekte.
Mit tkinter kenne ich mich zu wenig aus, daher kann ich dazu nicht viel sagen.

Eine andere populäre GUI-Bibliothek ist Qt/PyQt. Diesem merkt man die Herkunft von C++ teilweise noch an, da es eine ziemlich strikte Trennung von Definitionen und Ausführungslogik möchte:

Code: Alles auswählen

# Definitionsteil
import ...

SOME_CONST = ...

class XY(QObject):
    ...

def functionXY():
    ...

# Einsprung für Ausführung (als Hauptskript)
# ab hier keine neuen Klassen, Konstanten oder Funktionen mehr,
# sondern nur noch Benutzung des Definierten (gilt auch für modulweite Importe)
if __name__ == '__main__':
    app = QApplication()
    xy = XY()
    ...
Was man hin und wieder sieht ist ähnlich wie (bitte nicht nachmachen):

Code: Alles auswählen

import ...

app = QApplication()

class XY(QObject):
    ....
    def do_something_with_SOME_GLOBAL(self):
        global SOME_GLOBAL
        ...

xy = XY()
SOME_GLOBAL = ...
import z
xy.do_something_with_SOME_GLOBAL(z.foo)
Zweiteres verursacht unbestimmtes Verhalten, da Qt es nicht mag, wenn Klassen nach `QApplication` erstellt werden. Python an sich verbietet das nicht. Hintergrund bei Qt sind übrigens globale Zustände, bei denen die Reihenfolge der Abarbeitung plötzlich wichtig wird ;). (In C++ selbst ist dies kein Problem, da zur Laufzeit alle Typen/Klassen bekannt sind.)
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

jerch hat geschrieben:[Ürbigens ist es völlig legitim, Konstanten schrittweise zu initialisieren, wenn es sich anbietet. Das sollte aber vor der ersten Benutzung im Hauptkontrollfluß fertig sein, also nix nachträglich ranfummeln
Es geht nicht um ranfummeln. Fummeln wäre, wenn die Schnittstellen nicht für nachträgliche Registrierungen ausgelegt wären. Wenn die Schnittstellen aber dafür ausgelegt sind, dann ist es ein spezifiziertes Verhalten.

Ein Temperatursensor darf ruhig die Außentemperatur senden, auch wenn noch keine Anzeige dafür aktiv ist. Er darf aber natürlich keine noch undefinierte Callbackfunktion aufrufen.

Durch entsprechende Interfaces kann man dafür sorgen, dass es keine undefinierten Zustände gibt

Ein einfaches dictionary würde für einfache überschaubare Applikationen genügen.
Jedoch für größere Applikationen wäre dieses ungeeignet

Code: Alles auswählen

# Für eine kleine Anwendungen mit genau einer Anzeige durchaus ausreichend und sinnvoll
vehicle.statusbus['aussentemperatur'](23)
# Falsch für komplexe Anwendungden, denn dieses würde crashen, wenn noch keine Callbackfunktion registriert ist
# ausserdem funktioniert das auch nur bei genau einer Anzeige

# Auch für komplexe Anwendungen geeignet
vehicle.statusbus.broadcast("aussentemperatur",23)
# Richtig, alle dafür registrierte Anzeigen erhalten die Nachricht
# Ist noch keine Anzeige registriert, dann wird auch kein Callback aufgerufen
# Wer alles diese Information erhält, geht dem Temperatursensor nichts an
# Ausserdem ist in einem solchen Interface auch implementiert, dass bei Entfernung von Anzeigen
# auch die Registrierungen für deren Empfangsnachrichten wieder gelöscht werden
# und somit keine nicht mehr vorhandenen Callbackfunktionen aufgerufen werden.
# Das wäre etwa der Fall, wenn der User ein Toplevel Window schliesst
# und dadurch zerstört. Die verwendeten abgeleiteten Klassen für Gui Container
# nehmen in ihrer destroy Methode auch die Deregistrierung der für sie registrierten
# Empfangsnachrichten vor. Gleiche Empfangsnachrichten für andere Empfänger bleiben dabei unberührt
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: schön, Dein Message-Broker, wobei Zeile 2 und 7 nur verschiedene Varianten sind, wie man einen Aufruf schreiben könnte. Es fehlt noch vehicle.statusbus.aussentemperatur(23). Aber was hat das jetzt mit dem Thema des Threads „globale Variablen“ zu tun?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: schön, Dein Message-Broker, wobei Zeile 2 und 7 nur verschiedene Varianten sind, wie man einen Aufruf schreiben könnte. Aber was hat das jetzt mit dem Thema des Threads „globale Variablen“ zu tun?
Ja das möchte ich gerne von Dir wissen, denn Du bist es ja, der schrieb:
Sirius3 hat geschrieben:Nimmt man aber statt "cat" self.example_method_one ist das keine Konstante mehr, damit kann man auch nicht behaupten, das Wörterbuch wäre konstant.
Hier ist ja der Fall, dass später noch Appplikationen mit Anzeigen dafür hochfahren und entweder gleich eine noch nicht eingetragene Message oder nur noch sich als zusätzlichen Empfänger für bereits eingetragene Messages (vom Interface her kein Unterschied) zusammen einer eigenen Callback Funktion eintragen.

Und da wurde global variabel soll man nicht machen und nachträglich ranfummeln soll man auch nichts, weil es dann eine Variable wäre, mehrfach geäußert.
Zuletzt geändert von Alfons Mittelmeyer am Mittwoch 26. April 2017, 19:52, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Sirius3 hat geschrieben:Und da wurde global soll man nicht machen und nachträglich ranfummeln soll man auch nichts, mehrfach geäußert.
Und was folgerst Du daraus? vehicle und vehicle.statusbus sind keine Konstanten.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:
Sirius3 hat geschrieben:Und da wurde global soll man nicht machen und nachträglich ranfummeln soll man auch nichts, mehrfach geäußert.
Und was folgerst Du daraus? vehicle und vehicle.statusbus sind keine Konstanten.
Genau, das habt ihr gesagt und weiter, dass man deshalb nichts mehr anfügen soll.

Wenn ich eine Texteditor habe und Text schreibe, dann ist der Text auch keine Konstante, soll man den gleich lassen?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: niemand hat geschrieben, dass man keine Variablen benutzen darf, nur global sollen sie nicht sein.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: Vieleicht komme ich jetzt langsam dahinter. Wenn ich einen Texteditor schreiben würde, der nur ein einziges Fester zum Bearbeiten genau eines Textes hätte, dann könnte man den Inhalt dieses Fensters als global ansehen. Wenn man ihn allerdings so schreibt, dass man auch mehrere Fenster haben könnte, dann wären die Zustände fensterspezifisch, also interne Zustände.

Weiterhin könnte man aber den Texteditor für nur ein Fenster so implementieren, dass man auch weitere Fenster instanzieren könnte, wenn man wollte. Und dann wären das wohl auch interne Zustände.

Es geht also darum, dass man auch dann objektorientiert programmieren soll, also mit möglicher Instanzierung, auch wenn das im betreffenden Anwendungsfall unnötig wäre oder auch gar nicht ginge?

So kann man etwa mit tkinter meist nicht mehrere GUIs also mehrere Anwendungsfenster haben, aber man sollte wohl so implementieren, als ob man das könnte?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: dass man mehrere Fenster haben kann, ist ein Vorteil, wenn man keine globalen Zustände hat, aber nicht der einzige und auch nicht der Wichtigste.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: dass man mehrere Fenster haben kann, ist ein Vorteil, wenn man keine globalen Zustände hat, aber nicht der einzige und auch nicht der Wichtigste.
Die Frage ist, wie kann man globale Variablen und globale Zustände vermeiden, und warum dürfen das andere tun, aber ich nicht? Siehe etwa hier, die API für den Raspberry Pi zum Ansteuern der GPIO Ports:

Code: Alles auswählen

import RPi.GPIO as GPIO

# RPi.GPIO Layout verwenden (wie Pin-Nummern)
GPIO.setmode(GPIO.BOARD)

# Pin 18 (GPIO 24) auf Input setzen
GPIO.setup(18, GPIO.IN)

# Pin 11 (GPIO 17) auf Output setzen
GPIO.setup(11, GPIO.OUT)
Hier wird in Zeile 1 von Modul RPI das Objekt GPIO importiert und das ist daher global. In Zeile 4 sehen wir, daß es tatsächlich ein Objekt ist, weil es die Methode setmode hat. Setmode stellt den Modus um auf GPIO.BOARD, das ist die Umstelluing eines globalen Zustandes. In den Zeilen 7 und 10 werden Pins auf Input bzw. Output gesetzt. Das sind wieder globale Zustandsänderungen.

GPIO ist ein global vorhandenes Objekt, für welches Zustände umgestellt werden, also eine globale Variable. Was denken sich die Macher dieser API nur, oder denken sie richtig und hier einige verkehrt?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: willst Du dafür eine Antwort, oder willst Du selbst überlegen warum das hier so ist?
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Meine Güte. Immer und immer und immer derselbe Unfug. SCHAUT HER, JEMAND ANDERES BENUTZT GLOBALEN ZUSTAND, WARUM DARF ICH NICHT!!!!!!!

Was würde denn passieren, wenn das GPIO Modul mehrere Instanzen hat? Vermehren sich dann plötzlich die verfügbaren pins an deinem PI? Wohl kaum. Ergibt also keinen Sinn.

WAS aber Sinn ergibt ist Code zu schreiben, der das GPIO Modul nicht per Import nutzt, sondern als Instanz übergeben bekommt. Und damit dann zB auch gegen eine mock Implementierung arbeite, zu testzwecken. Oder den Austausch von RPi.GPIO (das mist ist) durch pigpio & einen Adapter zur API Vereinheitlichung zu erleichtern.

Globaler Zustand existiert. Niemand hat das Gegenteil behauptet. Aber in den allermeisten Fällen lässt er sich vermeiden, und da wo es nicht geht kann man ihn immer noch explizit als Abhängigkeit reinreichen. Damit die nächste Ebene wenigstens sauber Test Bär und wiederverwendbar bleibt.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Alfons Mittelmeyer:
Das ist simples "Aufräumen des Arbeitsplatzes". "Ich erwarte das *immer* so, bevor ich mit meiner Arbeit loslege..." - damit ist global ok (weil vor der Arbeit). Beim GIO Modul müsste man trotzdem mal schauen, ob das seiteneffektsfrei ist, Da GIO auf Pins einer Hardwareschnitstelle zugreift, ist das zu bezweifeln. Das wäre dann schlecht programmiert, u.a. weil das Modul in Python Singleton ist und die Annahme über die Zustände falsch werden kann, da durch andere Zugriffe in der Zwischenzeit geändert. Bei Interaktion mit der Hardwareschnittstellen gelten aber eh nochmal andere (verschärfte) Regeln, da hier der veränderliche globale Zustand unvermeidbar ist.

Was mir vor Jahren geholfen hat, diese Dinge zu verstehen, war Bibliothekscode zu schreiben. Die Grundanforderung an solchen Code ist seiteneffektsfreie Wiederverwendbarkeit. Am eigenen Quellcode lernt man dann sehr schnell, dass variable globale Zustände eher nicht dazu gehören ;)
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

__deets__ hat geschrieben:Globaler Zustand existiert. Niemand hat das Gegenteil behauptet. Aber in den allermeisten Fällen lässt er sich vermeiden, und da wo es nicht geht kann man ihn immer noch explizit als Abhängigkeit reinreichen. Damit die nächste Ebene wenigstens sauber Test Bär und wiederverwendbar bleibt.
Bisher hat man mir hier gesagt, dass nicht nur Variablen, denen man mit dem Zuweisungsoperator etwas zuweist, Variablen sind, sondern auch Dictionarys, die man erweitert und damit auch Methoden und Funktionen, denen man Werte als Parameter übergibt.

Somit ist also auch "print()" eine Variable. Funktionen in Python können sich nur etwas merken, indem sie es außerhalb ihrer speichern. In C heißen Variablen, die nur in ihrem Modul sichtbar sind, interne Variablen. In Python kann man nur durch einen Unterstrich markieren, daß das eigentlich interne Variablen sein sollen, und man nennt sie dann trotzdem Globale Variablen.

Die Funktion print bzw. davon weiter aufgerufene Funktionen merken sich die Zeile, in die ausgegeben wurde. Außerdem wird diese Zeile hochgezählt, sofern sie nicht am unteren Rand ist. Wenn sie bereits am unteren Rand ist, wird der ganze Terminalscreen außerdem um eine Zeile nach oben gescrollt. Die Funktion print ändert also ganz massiv einen globalen Zustand und ist damit eine Variable und sogar eine globale.

Das kann auch Seiteneffekte haben, wenn nämlich verschiedene Module auf den selben Screen schreiben. print ist nicht instanzierbar und schreibt immer auf den selben screen. Das ist eine globale Variable, die viele Fehler bei der Ausgabe verursachen kann, wenn auf diese in undisziplinierter Weise zugegriffen wird. Daher sollte man diese Funktion als Parameter übergeben, damit dann kein globaler Zugriff erfolgt. So kann man das etwa implementieren:

Code: Alles auswählen

# statt print('Application has started')
# schreibe man:

class Print_Class():

    def __init__(self,print_function):
        self.print_function = print_function

    def output(self,value):
        self.print_function(value)


class Application():

    def __init__(self,print_object):
        self.print_object = print_object

        self.print_object.output('Application has started')
    

def main():
    Application(Print_Class(print))

main()
Das print in Python ist also falsch implementiert. Richtig wäre eigentlich eine Print Klasse, die einen neuen Ausgabescreen aufmacht.

Also in Python wird das meist falsch gemacht, wie etwa auch vom Modul sys:
sys.stdout.write('Dive in')

sys.stdout ist ein bereits instanziertes Objekt und damit eine globale Variable. Mit sys.stdout.write('Dive in'), wird dann ein globaler Zustand geändert.
Zuletzt geändert von Alfons Mittelmeyer am Donnerstag 27. April 2017, 11:03, insgesamt 1-mal geändert.
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Alfons Mittelmeyer hat geschrieben:
__deets__ hat geschrieben:Globaler Zustand existiert. Niemand hat das Gegenteil behauptet. Aber in den allermeisten Fällen lässt er sich vermeiden, und da wo es nicht geht kann man ihn immer noch explizit als Abhängigkeit reinreichen. Damit die nächste Ebene wenigstens sauber Test Bär und wiederverwendbar bleibt.
Bisher hat man mir hier gesagt, dass nicht nur Variablen, denen man mit dem Zuweisungsoperator etwas zuweist, Variablen sind, sondern auch Dictionarys, die man erweitert und damit auch Methoden und Funktionen, denen man Werte als Parameter übergibt.

Somit ist also auch "print()" eine Variable.
Ähm.
kbr hat geschrieben:@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.
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: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: Deinem Geschwafel kann ja niemand folgen. Du mischt da Zeugs zusammen, was nicht zusammenpaßt. Funktionen sind keine Variablen, man kann aber Funktionen unterscheiden in solche, die einen globalen Zustand ändern (z.B. print), einen internen Zustand ändern (list.sort), von einem globalen Zustand abhängen (datetime.now), von einem internen Zustand abhängen (list.__len__) oder zustandslos sind. Funktionale Programmierung propagiert die Zustandslosigkeit so weit es geht. Aber viele Programme sind nicht zustandslos und die Welt erst recht nicht. Oft sind Probleme einfacher zu verstehen, wenn man einen Zustand annimmt. Solange es sich um einen internen Zustand handelt, bleibt die Sache noch recht übersichtlich. Man muß aber klar kennzeichnen, dass z.B. eine Funktion eine Liste ändert, wie es sort z.B. tut.
Vieles hat aber einen globalen Zustand (Bildschirm, Festplatte, Uhrzeit), wobei vieles durch Modularisierung abgemildert wird (Fenster, Verzeichnisse).

Es gibt sozusagen eine Hierarchie, funktional, interner zustandsbehaftet, extern zustandsbehaftet, wobei man sich soweit links halten sollte, wie es geht, als z.B. neue Listen erzeugen, statt den Inhalt einer Liste verändern.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: Funktionen sind keine Variablen
Dann ist das zumindest für Funktionen geklärt. Wie aber ist es dann mit Objekten, für die man Methoden aufruft? Was ist dann etwa mit:

sys.stdout

Denn genau um so etwas geht es mir. Und da wurde mir gesagt, dass solche Objekte globale Variablen wären und man sie stattdessen als Parameter übergeben sollte.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: um was geht es Dir genau? Etwas aus der Luft gegriffen, weil es das Logging-Modul schon gibt aber:

Code: Alles auswählen

class Logger(object):
    def __init__(self, output):
        self.output = output

    def error(self, msg):
        self.output.write("Error: {}\n".format(msg))

logger = Logger(sys.stdout)
Damit habe ich das eigentlich globale Objekt sys.stdout im Context des Objekts Logger zu einem lokalen gemacht, das ich durch ein beliebig anderes mit einer write-Methode asutauschen kann.
Antworten