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

__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: 17737
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: 17737
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.
BlackJack

@Alfons Mittelmeyer: `sys.stdout` ist *die* Standardausgabe eines Prozesses. Das ist tatsächlich etwas prozessglobales, da kann aber Python nicht wirklich etwas dran ändern, denn das wird vom Betriebssystem so vorgegeben.

Wenn man nicht tatsächlich über die Standardausgabe, und *nur* über diese, etwas ausgeben möchte, dann muss man die Datei für die Ausgabe in der tat, mindestens optional übergeben können. Die `print()`-Funktion ist da ein gutes Beispiel, denn ein ``print('Hallo')`` gibt Hallo auf `sys.stdout` aus, aber das ist nicht fest verdrahtet, es ist auch möglich eine andere Datei zu übergeben: ``print('Hallo', file=some_other_file)``.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: nein da hast jetzt auch keine lokale Variable gemacht. Mit: logger = Logger(sys.stdout)
hast Du lediglich noch ein zusätzliches neues globales Objekt namens logger, welche nur die Methode error hat.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: Du hast es falsch gelesen, ich schrieb, das Objekt sys.stdout ist im Kontext des Objekts Logger eine lokale Variable, nämlich self.output. Und Zeile 8 ist nur ein Beispielaufruf, der an irgendeiner Stelle, also auch in einem lokalen Namensraum stehen könnte.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: das ändert aber nichts an der Tatsache, daß sys.stdout global zur Verfügung steht. Ist das jetzt eine Variable? Wenn dem so wäre, sollte sie nicht global zur Verfügung stehen, das heißt also, sys.stdout sollte es nicht geben!
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: nochmal: es gibt globale Zustände, so ist das Leben nun mal. Globale Zustände bereiten Schwierigkeiten, daher sollte man sie überall dort vermeiden wo es geht. Geht nicht immer, also muß man wenigstens dafür sorgen, dass man in die Schwierigkeiten nicht umbeabsichtigt reinläuft. Niemand zwingt Dich sys.stdout zu benutzen; und in einer portablen Bibliothek wird es auch nicht vorkommen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: daß es globale Zustände gibt, ist klar. Aber dieses bereitet doch nicht unbedingt Schwierigkeiten. Oder hat jemand Schwierigkeiten damit, dass etwa print immer in dasselbe Fenster ausgibt?

Wenn man etwa statt dem Objekt sys.stdout eine Klasse hätte, die ein neues Fenster aufmacht, etwa:

stdout2 = sys.NewStdout()

Wäre dieses sinnvoll, wenn man stets in dasselbe Fenster hineinschreiben will? Sollte da auf obere Ebene, dieses stdout2, damit es immer dasselbe ist, als Parameter an alle Subsysteme übdrgeben werden.

Sollten man bei Aufruf von Bibliotheken, die etwas ausgeben, evtl. immer sys.stdout als Parameter mit angeben müssen?

Möglich ist alles, sollte man daher dieses möglichst so tun, um das Prinzip des Nichtglobalen zu erfüllen? Oder sollte man nicht nur schauen, ob es möglich ist sondern auch, ob des sinnvoll ist?

Und sinnvoll ist es, daß Module, die etwas von außerhalb brauchen, das selber importieren. Nicht sinnvoll ist es, wenn man auf oberster Ebene alles importieren würde, was vielleicht auf unterer Ebene benötigt werden könnte und dies alles als Parameter nach unten durchreicht.

Wenn man eine unübersichtliche, fehlderanfällige und verwaltungsintensive Programmierung haben möchte, kann man das ja tun, wenn man denkt, Hauptsache, daß es dann nicht global ist.
Zuletzt geändert von Alfons Mittelmeyer am Donnerstag 27. April 2017, 15:36, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es ist schon 100 mal gesagt worden, aber es hat offensichtlich die Schaedeldecke noch nicht penetriert - darum zum 101en mal, vielleicht mal mit ner Metapher:

Die Tatsache das man etwas nicht tun sollte impliziert nicht, dass man es nicht tun darf. Man sollte es nur unter Abwaegung der Alternativen tun.

Es ist allgemein eine schlechte Idee, mit einem scharfen Gegenstand die Bauchhoehle zu eroeffnen. Das bedeutet aber deshalb nicht, dass man Chirurgie verbietet. Man sollte aber zum befestigen einer Hose einen Guertel verwenden, statt sie mit Darmschlingen zu verknoten.

Globaler Zustand ist allgemein der Anus Praeter der Programmierung. Man will das nicht, es ist haesslich und macht das Leben schwerer, aber man kann's halt nicht immer vermeiden.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Alfons Mittelmeyer hat geschrieben: Und sinnvoll ist es, daß Module, die etwas von außerhalb brauchen, das selber importieren. Nicht sinnvoll ist es, wenn man auf oberster Ebene alles importieren würde, was vielleicht auf unterer Ebene benötigt werden könnte und dies alles als Parameter nach unten durchreicht.
Dieser Satz in seiner Absolutheit ist Unfug. Ich schreibe genug Code, bei dem explizit Abhaengigkeiten in tiefere Programmteile uebergeben werden. Es ist also durchaus sinnvoll. Nicht immer und nicht unter allen Umstaenden (print fuer Debugausgaben so wegzuabstrahieren ist ueberkandidelt, doch das logging-Modul mit seiner klaren Trennung von 'ich produziere Nachrichten' vs. 'ich entscheide spaeter, zur Laufzeit, ob ich sie benutzen moechte' ist zB schon ein *schritt* in diese Richtung. Kein vollstaendiger, weil natuerlich die Logger immer noch global sind, aber logging ist auch ein besonderer Teil.)
BlackJack

@Alfons Mittelmeyer: `print()` gibt nicht immer in dasselbe Fenster aus. Das gibt, wenn man beim Aufruf nichts anderes hinreicht, was ja möglich ist, auf der Standardausgabe des Prozesses aus. Und was mit dieser Standardausgabe verbunden ist bestimmt der Aufrufer. Das kann ein ”Fenster” sein, das kann aber auch eine Datei sein.

Die Alternative die Ausgabe durchzureichen ist das ersetzen von der globalen Variable, was IDEs die keinen getrennten Python-Interpreter für die Ausführung dann ja auch machen um die Ausgaben in einem Fenster anzuzeigen. Das ist auch fehleranfällig und skaliert nicht, denn das kann man nur einmal machen. Wenn zwei Bibliotheken die Ausgaben für ihren Teil woanders hinlenken wollen, dann kracht's wenn die beide am globalen Zustand rumfummeln.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Alfons Mittelmeyer hat geschrieben:Sollten man bei Aufruf von Bibliotheken, die etwas ausgeben, evtl. immer sys.stdout als Parameter mit angeben müssen?
Warum nicht? Genau dies geschieht beispielsweise bei der print-Funktion. Angenehmerweise per default. Vielleicht hast Du dies tatsächlich nicht gewusst. So denn: nun weißt Du es und ich freue mich, Dir geholfen haben zu können — obgleich ich davon ausgehe, dass Du die Hilfe, die Du suchst, in diesem Forum nicht finden wirst ...
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@Alfons Mittelmeyer: Schau dir mal diesen Vortrag von Brandon Rhodes an: The Clean Architecture in Python
In specifications, Murphy's Law supersedes Ohm's.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

__deets__ hat geschrieben:Die Tatsache das man etwas nicht tun sollte impliziert nicht, dass man es nicht tun darf. Man sollte es nur unter Abwaegung der Alternativen tun.
Was man nicht tun sollte, ist, ein Dogma aus etwas zu machen, das für einen ganz bestimmten Zusammenhang gemeint war.

Und der Zusammenhang ist, dass viele Anfänger nicht OOP programmieren und statt Klassen und statt Aufräumen eine Unzahl vom Modulinternen Variablen haben auf die sie zugreifen. Klar, dass man das nicht haben soll, sondern sauber aufgeräumt und gekapselt und sinnvoll gegliedert implementieren sollte.
__deets__ hat geschrieben:Es ist allgemein eine schlechte Idee, mit einem scharfen Gegenstand die Bauchhoehle zu eroeffnen. Das bedeutet aber deshalb nicht, dass man Chirurgie verbietet. Man sollte aber zum befestigen einer Hose einen Guertel verwenden, statt sie mit Darmschlingen zu verknoten.
Wie kommst Du auf diese komische Metapher mit dem scharfen Gegenstand?
__deets__ hat geschrieben:Globaler Zustand ist allgemein der Anus Praeter der Programmierung. Man will das nicht, es ist haesslich und macht das Leben schwerer, aber man kann's halt nicht immer vermeiden.
Allles ist möglich, man kann es es wahrscheinlich immer vermeiden, auch wenn man dann manchmal statt etwas Einfachem eine häßliche fehleranfällige Programmierung zu machen hat.

Bei diesem Beispiel: http://code.activestate.com/recipes/413 ... ython-way/
ist in Zeile 26 ein globales Objekt definiert:

features = FeatureBroker()

Warum wird das in diesem Forum so gesehen, als ob das ein Verbrechen ist oder jedenfalls als etwas, was es auf Teufel komm raus zu vermeiden gilt?
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das wird nicht nur so in diesem Forum so gesehen, sondern von jedem, der gut programmieren kann.

Und wann kommt mal der Moment an dem du begreifst, das ein Gegenbeispiel fuer eine *Richtlinie* nicht die gleichen Konsequenzen hat wie fuer einen mathematischen Beweis?

Wenn du hier Code schreibst, der unnoetigerweise global verwendet oder globalen Zustand zur Funktion vorraussetzt, bleibt das schlechter Code, und das wird dir dann auch weiterhin so gesagt werden. Die Existenz von anderem schlechten Code, oder Code der aufgrund bestimmter Randbedingungen so geschrieben werden musste aendert daran nix.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Es ist doch ganz einfach. Ein gutes Programm sieht typischerweise so aus:

Bild

Ein weniger gutes typischerweise so:

Bild

Ersteres zeigt das Innenleben eines VIntage Hiwatt Amps (ich hab zwei davon), zweiteres das eines Vintage Fender Amps. Globaler Zustand führt typischerweise zu Programm-Strukturen, die aussehen wie das zweite Bild, das Vermeiden von globalem Zustand zu solchen, die aussehen wie das erste.
In specifications, Murphy's Law supersedes Ohm's.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

pillmuncher hat geschrieben:Es ist doch ganz einfach. Ein gutes Programm sieht typischerweise so aus
Genau das ist der Zweck, warum man manchmal dasselbe Objekt global verfügbar machen sollte, anstatt es überall durchzureichen.

Ein System, besteht aus diversen Subsystemen. Bei der Initialisierung mancher Subsysteme wird zuerst ein Objekt instanziert, das noch weiter durch diverse Daten konfiguriert wird. Um mit diesen Subsystemen zu arbeiten, braucht man dann diese konfigurierten Objekte.
Doch was tun, wenn es heißt, daß man keine globalen Objekte haben soll, sonder nur Funktionen und Klassen?

Ich habe jetzt die Lösung gefunden.

Code: Alles auswählen

# statt 
my_object = _MyClass()

# und dann globalen Zugriff auf my_object, laesst sich das auch mit einem Singleton loesen:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

# python3
class MyClass(metaclass=Singleton):

    def __init__(self):
        self.number = 0

# test ----
a = MyClass()
a.number = 123
b = MyClass()
print(b.number)
Antworten