global oder nicht global

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
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@gNeandr: Benenne doch mal konkret, wo du den Einsatz globaler Variablen benötigst. Ist dir z.B. klar, dass bei mehrfachen Importen des selben Moduls sich globale Zustandsveränderungen immer auf alle Stellen auswirken, wo das Modul benutzt wird? Ein ``import`` initialisiert ein Modul nämlich nur einmal. Nachfolgende Importe verwenden das bereits initialisierte Modul und damit auch dessen Zustand. Das kann zu äußerst merkwürdigem Verhalten führen. Beispiel:

Code: Alles auswählen

from __future__ import print_function

foo = 'foo'

def set_foo(value):
    global foo
    foo = value

def print_foo():
    print(foo)

Code: Alles auswählen

>>> import funwithfoo
>>> funwithfoo.print_foo()
foo
>>> funwithfoo.set_foo('bar')
>>> funwithfoo.print_foo()
bar
>>> import funwithfoo
>>> funwithfoo.print_foo()
bar
Es ist zwar ungewöhnlich, dass man im selben Modul ein anderes Modul mehrfach importiert, aber bei Projekten mit vielen Untermodulen kommt es ziemlich häufig vor, dass z.B. in mehreren Modulen das ``os``-Modul importiert wird. Und da kann einem dieses Verhalten durchaus um die Ohren fliegen.
Zuletzt geändert von snafu am Samstag 31. Mai 2014, 12:23, insgesamt 1-mal geändert.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

gNeandr hat geschrieben:wo finde ich gute Hinweise zu Getter/Setter in Python?
Hier.
In specifications, Murphy's Law supersedes Ohm's.
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@pillmuncher .. werde das mal durcharbeiten, Danke

@snafu
Die global Diskussion wurde angestoßen in diesem Thread:
http://www.python-forum.de/viewtopic.php?f=1&t=33895
Dort verwende ich verschiedene 'Parameter' in unterschiedlichen Routinen, aber das ganze wird (immer) ein einziges py Programm bleiben (soweit ich es momentan sehe)
BlackJack

@gNeandr: Eine allgemein gültige Handhabung dürfte sein statt globalen Zustand zu verwenden, den mit Argumenten/Parametern zu lokalem Zustand zu machen der herum gereicht wird.

Zum Closure: Anstelle eines solchen würde man in Python eine Klasse schreiben. Allerdings ist das ja letztendlich nur ein Wörterbuch, also könnte man auch gleich ein solches verwenden. Was wieder unschön ist, weil es ja eine globale Datenstruktur wäre.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wenn man viele Werte herumreichen muss, aber Klassen einem noch zu hoch sind oder man die aus anderen Gründen nicht verwenden möchte, dann bietet Python diverse Möglichkeiten an: BlackJack hatte ja schon Wörterbücher genannt. Was noch geht, wären ``namedtuple``s oder ggf auch ``Namespace``-Objekte (wird von ``argparse`` so gemacht). Und die erstellt man dann natürlich nicht global, sondern lässt sich einmalig einen Kontext mit Default-Werten erzeugen ("options", "settings", "configuration", was-auch-immer), den man dann herumreichen kann.
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

@snafu: Du hast beim zweiten `import` nicht das Modul nochmal importiert; die Zeile bewirkte nichts, weil sich das Modul noch im Cache befand. Mit `reload` wird es wirklich neu geladen und es gibt ein anderes Verhalten als du es dargestellt hast:

Code: Alles auswählen

>>> import funwithfoo
>>> funwithfoo.print_foo()
foo
>>> funwithfoo.set_foo('bar')
>>> funwithfoo.print_foo()
bar
>>> reload(funwithfoo)
<module 'funwithfoo' from 'funwithfoo.pyc'>
>>> funwithfoo.print_foo()
foo
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

:? .. so ein wenig raucht der Kopf.
Die Lektüre über Namespaces, descriptoren usw usw sind alle schön & gut, aber die grundsätzliche Methodik wie zB von BlackJack beschrieben
Eine allgemein gültige Handhabung dürfte sein statt globalen Zustand zu verwenden, den mit Argumenten/Parametern zu lokalem Zustand zu machen der herum gereicht wird.
kann ich noch nicht damit lösen (oder nur noch nicht erkennen?).

Da ich innerhalb eines Programms zwei/ggf. mehrere Threads haben werde, aber bestimmte Parameter in den Threads verwendet oder auch in diesen verändert werden müssen, war mein erster Gedanke diese als 'global' aufzusetzen. Und ja, ich sehe die Gefahr, dass deren Zustand nicht immer überschaubar ist (wer hat wann was geändert).
Und hat ev. ein Programm/Modul 'unbeabsichtigten' Schreib-Zugriff (Nameskonflikte).

Aus Javascript kenne ich Closures und letztlich ist das doch in Python auch möglich?!

Warum das "herumreichen" der Daten die bessere Lösung ist erschließt sich mir noch nicht, insbesondere mit den erforderlichen Threads .. siehe oben.
Sorry ...
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Und schon hast Du den nächsten Punkt getroffen, wann man niemals globale Variablen benutzen darf: Threads. Da Threads zu nicht kontrollierbaren Zeitpunkten globale Variablen ändern können, ist es auch unmöglich, Fehler, die dadurch entstehen zu finden, zu reproduzieren und zu beseitigen. Threads müssen in ihrer eigenen Welt leben und der Kontakt zu anderen Threads muß genau geplant werden (z.B. mit Queues).
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Sirius3 hat geschrieben:Und schon hast Du den nächsten Punkt getroffen, ... (z.B. mit Queues).
Um des Himmels Willen ... was mach ich denn jetzt damit?????
BlackJack

@gNeandr: Closures sind in Python „vollwertig” erst ab 3.0 möglich weil es vorher noch kein ``nonlocal``-Schlüsselwort gibt, aber auch damit wäre es nicht „pythonisch”, denn der eine, offensichtliche Weg, sind nun mal Klassen. Python hat und hatte schon immer Klassen dafür, also wäre es komisch die mit Closures nachzubauen, als wenn es diese Möglichkeit nicht gäbe.

Ich verstehe ehrlich gesagt nicht so ganz wo das Problem liegt. Es gibt keine technische Lösung die irgendwie einen tollen Namen hat, oder die man irgendwie genauer beschreiben könnte als: Verwende einfach keinen globalen Zustand. Was doch in vielen Fällen auch überhaupt gar kein Problem ist. Wenn zwei Threads sich Zustand teilen, dann übergibt man den einfach beiden vor dem bzw. beim Start und schon hat man keinen globalen Zustand. Genau das habe ich doch zum Beispiel in dem anderen Thema mit dem „exit flag” gemacht.

Und das hat auch nichts mit Closures zu tun, denn auch bei Sprachen in denen *das* der bevorzugte Weg ist um Objekte zu erstellen, möchte man nach Möglichkeit keinen globalen Zustand damit modellieren, das heisst diese beiden Themen sind orthogonal.
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@BlackJack
OK, mit dem Hinweis auf das andere Thema meinst du sicher diese Zeilen #50 .. 53:

Code: Alles auswählen

    exit_event = Event()
    Thread(target=print_time, args=(exit_event, 'myTimer', 2)).start()
    serve(exit_event, 'myListener')
und als 'Zustand' ist hier "exit_event" gemeint.
Soweit verstanden. Aber irgendwie sehe ich halt nicht den "Vorteil" Zustände (was ja nix anderes als Daten sind) rumzureichen. Das kann meiner Meinung nach auch recht unübersichtlich werden.

Mein Beispiel hier im Thread kapselt die als global benutzten Daten ja innerhalb 'def gParam():' und sind nur über 'gSet,gGet' ansprechbar. Mag sein, dass es nicht der reinen pythonischen Lehre entspricht, aber sollte besser sein als global Datennamen wie 'server'; dh. meine Lösung schützt diesen Wert durch gSet('server', '192.....') bzw gGet('server').
Und geht das nicht in Richtung pythonischer Wörterbuch Funktion .. wie ja auch vorgeschlagen?
BlackJack

@gNeandr: Wenn man einfach nachvollziehen kann welche Daten von einem Stück Code benutzt werden ohne dass man dazu das ganze Programm durchlesen muss, oder bei Daten einfach(er) sehen kann welcher Code die benutzt und verändern kann, ohne dass man das ganze Programm durchlesen muss, dann ist das IMHO immer ein Vorteil. Das Programm ist dadurch einfacher nachvollziehbar, testbar, wart- und erweiterbar. Aber das hatten wir doch alles schon.

Ob Du globale Daten nun kapselst, oder besser gesagt wie Du die kapselst, ist egal, es bleiben ja globale Daten, und die werden durch keine Art der Kapselung besser. Wie schon gesagt, die beiden Themen sind orthogonal. Ich sehe auch nicht wo Deine Lösung etwas ”schützt”, und wovor? Ob man nun ein Modul für die Konfiguration anlegt (``import config; config.server = '123...'; print config.server``), das in einem globalen Wörterbuch ablegt (``config['server'] = '123...'; print config['server']``), oder über ein Closure darauf zugreift (``g_set('server', '123...'); print g_get('server')``) ändert semantisch ja nichts daran, dass es globale Daten sind.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@gNeandr:
So war mein Hinweis oben auf Closures natürlich nicht gemeint. Selbst wenn Du Zustände in Dateien rausschreibst um sie später tief im Code vergraben wieder zu laden und zu manipulieren, bleibts beim globalen Charakter der Zustände und ist ein Hinweis auf einen schlechten Entwurf, der Dir irgendwann um die Ohren fliegen wird.

Mir scheint, dass Dir nicht klar ist, warum globale Zustände, egal in welcher Form sie daher kommen, zum Problem werden. Idee ist doch, die Ablaufsteuerung möglichst zentral zu orchestrieren. Diese zentrale "Anlaufstelle" ist zB. in C die main-Funktion. Python erzwingt diese nicht, dafür hat sich unter Python der `if __name__ == '__main__': Block etabliert. Von hier aus gibst Du Zuständigkeit an Funktionen oder Exemplare und deren Methoden ab (über Parameter). Innerhalb deren Implementation orchestrieren sie wiederum ihre Subaufrufe (sind eine Art lokales "main"). Das Scoping von Python (und der meisten anderen Sprachen ;) ) folgt im wesentlichen dieser Idee. Im Idealfall sind Exemplare und Funktionen vollständig gekapselt - egal wo und zu welcher Zeit Du ein Exemplar erstellst oder ein Ergebnis einer Funktion erhältst - sie verhalten sich gleich und sind damit vorhersagbar.

Globale/Semiglobale Zustände führen in dieses Modell das Problem der "Nebenabsprachen" ein. Übrigens sind auch Konstanten eine Art Nebenabsprache, weichen aber obiges Prinzip nicht auf, da sie konstant sind (erzwungen oder per Konvention). Auch gibt es mit den Objektattributen eine gewollte Abweichung von diesem Prinzip ("objektglobal" - sind objektweit manipulierbar). Dadurch wird es möglich, Objekte imperativ zum Leben zu erwecken.

Da Du von Javascript sprachst - auch in der ereignisbasierten Programmierung sollte man kapseln (gut geschriebene JQuery-Plugins zeigen das ganz gut). Man steigt dort idR nicht mit "main" ein, da die Rahmenwerke bzw. der Browser diese Funktion mit der Ereignisschleife besetzen.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

derdon hat geschrieben:@snafu: Du hast beim zweiten `import` nicht das Modul nochmal importiert; die Zeile bewirkte nichts, weil sich das Modul noch im Cache befand. Mit `reload` wird es wirklich neu geladen und es gibt ein anderes Verhalten als du es dargestellt hast:
Ich sprach schon ganz bewusst vom ``import``-Statement und dem dafür typischen Verhalten. Wenn ich das selbe Modul in mehreren Untermodulen verwende, dann mache ich dies nicht mit einem ``reload``, sondern eben mit ``import``-Statements.
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@jerch
in C die main-Funktion. Python erzwingt diese nicht, dafür hat sich unter Python der `if __name__ == '__main__': Block etabliert. Von hier aus gibst Du Zuständigkeit an Funktionen oder Exemplare und deren Methoden ab (über Parameter). Innerhalb deren Implementation orchestrieren sie wiederum ihre Subaufrufe (sind eine Art lokales "main"). Das Scoping von Python (und der meisten anderen Sprachen ;) ) folgt im wesentlichen dieser Idee. Im Idealfall sind Exemplare und Funktionen vollständig gekapselt - egal wo und zu welcher Zeit Du ein Exemplar erstellst oder ein Ergebnis einer Funktion erhältst - sie verhalten sich gleich und sind damit vorhersagbar.

Dieses Konstruktion würde ich mir gerne näher ansehen, kannst du mir ein Beispiel geben .. Link ..

:D Danke
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

snafu hat geschrieben:
Darii hat geschrieben:global ist der "last resort". Genau wie goto in C.
Dafür, dass ``goto`` ein "last resort" sein soll, wird es in echten Programmen - u.a. im C-Quelltext von Python und im Linux-Kernel - aber ganz schön oft verwendet. Wie passt das mit deiner Aussage zusammen? Sind das für dich allesamt schlechte Programmierer oder setzt du eine sehr spezielle Definition von "last resort" voraus?
Meine Definition "last resort = letzter Ausweg" (laut Wörterbuch). Dass dieser letzte Ausweg trotzdem oft genommen wird liegt einfach daran, dass C keine äquivalenten besseren Sprachmittel z.B. zur Fehlerbehandlung anbietet.

Sprich goto sollte man in C genau wie global in Python nur verwenden, wenn es gar nicht anders (oder nicht besser) geht. Kommt in C leider nunmal recht häufig vor…
BlackJack

@Darii: ``goto`` ist nicht letzter Ausweg, sondern das Mittel der Wahl um die Sprünge, die in anderen Sprachen per Ausnahmebehandlung geregelt werden, in C zumindest ansatzweise umzusetzen. Letzter Ausweg ist etwas was man nimmt wenn man verzweifelt ist. Es mag sicher Leute geben die beim C-Programmierung ständig am verzweifeln sind, aber das ist nicht der Masstab an dem man das festmachen sollte. ;-)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@gNeandr: Hier mal ein simples Beispiel, einmal mit globaler Variable und einmal mit Parameter. Wenn Du mir die -1 für a global erklären kannst, hast Du gut aufgepasst - dann ist aber interessant, was nötig war, um das nachzuvollziehen. In real world code würde ich sowas immer als Programmierfehler ansehen. Beachte bitte auch, wie sich der Generator für a als Parameter immer gleich verhält.

Code: Alles auswählen

def gen():
    global a
    while a > 0:
        yield a
        a -= 1

print 'a as global'
a = 10
for i in gen():
    print i, a, list(gen())
print a, list(gen())

#################################

def reentrant_gen(a):
    while a > 0:
        yield a
        a -= 1

print 'a as parameter'
a = 10
for i in reentrant_gen(a):
    print i, a, list(reentrant_gen(a))
print a, list(reentrant_gen(a))
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@jerch ... besonders spannend finde ich das jetzt nicht, sorry.
Vielleicht liegt es daran, dass ich bestimmte "Werte" sehe, die ich an verschiedenen Stellen im Programm benutzen muss,
a) feste "Werte" -- zB 'server', die IP die ich praktischerweise beim Start einlese und in mehreren Funktionen / Threads verwenden muss,
oder
b) "Werte" die ich nach Berechnung (von importierten Ausgangswerten) weiter 'global' verfügbar halte (zB. Geo-Koordinaten zur Berechnung von 'sunrise' und 'sunset')

In beiden Fällen werden die einmal "ermittelt" und warten dann nur noch verwendet zu werden. Also recht einfach ..'global'.

Sicher gibt's Fälle in denen das "rumreichen" von Zuständen Sinn macht. BlackJack hat ja dankenswerterweise meinen ersten Entwurf entsprechend verbessert. Und Jerchs Beispiel zeigt das ja auch.

Nix für Ungut, eure Hinweise helfen und eines Tages werde ich wohl pythonischer denken ... :wink:
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@gNeandr: Das hat doch nichts mit pythonisch/unpythonisch zu tun, in C, C++ oder Java würde man das auch so machen. Und spätenstens bei Threads handelst Du Dir mit globalen Variablen entweder sowas wie Schrödingers Katze oder ein segfault ein, wenn da zeitgleich dranrum manipuliert wird. :roll:
Antworten