Zugriff auf globale Variable - aber nur lesend klappts?!

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.
Antworten
Miranda
User
Beiträge: 12
Registriert: Sonntag 23. September 2018, 21:45

Montag 10. Juni 2019, 23:04

Hallo,

wieder einmal eine Frage. Das hier funktioniert:

Code: Alles auswählen

def spam():
    print(eggs)

eggs=1
while eggs<10:
    eggs = eggs +1
    spam() 
Ich kann lesend auf die Variable eggs zugreifen obwohl sie nicht in der Funktion definiert ist. Klar.
Sobald ich aber mit der Variablen arbeiten will - rummst es.

Code: Alles auswählen

def spam():
    print(eggs)
    eggs = eggs +1 #Hier knallt es - lesen geht, nur schreiben nicht. 
 
eggs=1
while eggs<10:
    eggs = eggs +1
    spam() 
Warum kann ich die Variable in der Funktion lesen, aber erst mir ihr rechnen, wenn ich sie (beispielsweise) mit eggs = 1 innerhalb der Funktion initialisiert habe.
Ich finde das irgendwie schwer zu durchschauen, das ist doch nichts halbes und nichts ganzes?

Grüße, Miranda
__deets__
User
Beiträge: 6419
Registriert: Mittwoch 14. Oktober 2015, 14:29

Montag 10. Juni 2019, 23:23

Das ist eine Festlegung die man in Python gemacht hat, weil es keine explizite Deklaration von Variablen gibt. Dabei wird in einer Funktion einfach geschaut, ob eine Zuweisung an einen Namen stattfinden.

<name> = <ausdruck>

Es ist dabei völlig unerheblich WAS in <ausdruck> steht.

Und wenn sowas gefunden wird, dann wird <name> Zur lokalen Variable in der Funktion gemacht.

Andere Sprachen machen das anders. ZB JavaScript, da musst du beim ersten Mal der Verwendung var davor schreiben. Aber Python macht’s halt so. Kann man doof finden. Aber nicht ändern.
Benutzeravatar
ThomasL
User
Beiträge: 775
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Dienstag 11. Juni 2019, 06:20

Miranda hat geschrieben:
Montag 10. Juni 2019, 23:04
Ich finde das irgendwie schwer zu durchschauen, das ist doch nichts halbes und nichts ganzes?
Hallo Miranda, das ist sogar ganz einfach zu durchschauen.
Einfach weil so wie in deinem Beispiel macht man es NICHT.
Man macht es so:

Code: Alles auswählen

def spam(eggs):
    print(eggs)
    eggs = eggs + 1
    return eggs
 
eggs = 1
while eggs < 10:
    eggs = eggs + 1
    eggs = spam(eggs) 
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
sparrow
User
Beiträge: 1362
Registriert: Freitag 17. April 2009, 10:28

Dienstag 11. Juni 2019, 07:25

Miranda hat geschrieben:
Montag 10. Juni 2019, 23:04
Ich finde das irgendwie schwer zu durchschauen, das ist doch nichts halbes und nichts ganzes?
Manchmal ist es ganz gut, Dinge nicht nur anhand eines einzigen Beispiels zu betrachten.
Was ThomasL geschrieben hat, solltest du beherzigen. In 99,99% der Fälle ist das Benutzen von globalen Variablen falsch. Es gilt: Eine Funktion bekommt die Variablen, die sie braucht, als Parameter übergeben und liefert das Ergebnis mit return zurück. Genau wie von Thomas beschrieben.

Warum kann man dann überhaupt auf Variablen des äußeren Namensraums zugreifen? Weil es für manche Variablen, sogenannte Konstanten, sinnvoll ist. Konstanten, wie der Name schon sagt, sind konstant und ändern sich nach der einmaligen Zuweisung nicht mehr. In anderen Programmiersprachen muss man die auch entsprechend deklarieren und kann sie hinterher gar nicht mehr ändern. Das wird bei Python, wie so vieles, durch Konvention gelöst. Man weiß, dass man Konstanten nicht ändert und tut das auch nicht . Es sei denn, man weiß was man tut und braucht das unbedingt.
In Python gibt es die Konvention Konstanten IN_GROSSBUCHSTABEN zu schreibe, damit jeder weiß: Alles klar, fester Wert.
Ein Beispiel wären Konstanten in der Mathematik (etwa Pi) oder so etwas wie ein fester Pfad oder die Benennung von Werten für ein leichteres Arbeiten (RED = 16, BLUE = 12 falls irgendwo Integerwerte als Farben verwendet werden). Es macht also durchaus Sinn, die "äußere" Variable in der Funktion lesen zu können. Sie ändern zu wollen, zeugt von schlechtem bis falschem Programmierstil.
Miranda hat geschrieben:
Montag 10. Juni 2019, 23:04
Warum kann ich die Variable in der Funktion lesen, aber erst mir ihr rechnen, wenn ich sie (beispielsweise) mit eggs = 1 innerhalb der Funktion initialisiert habe.
Wenn du innerhalb der Funktion "eggs = 1" schreibst, ist das nicht mehr die selbe Variable wie in der "äußeren" Umwelt. Die Funktion hat ihren eigenen Namensraum - und das ist auch gut so.

Code: Alles auswählen

>>> def test():
	i = 1
	i = i + 1
	print(f"i in in test: {i}")

	
>>> i = -5
>>> test()
i in in test: 2
>>> i
-5
>>> 
Miranda
User
Beiträge: 12
Registriert: Sonntag 23. September 2018, 21:45

Dienstag 11. Juni 2019, 07:32

Hallo,
__deets__ hat geschrieben:
Montag 10. Juni 2019, 23:23
...
Dabei wird in einer Funktion einfach geschaut, ob eine Zuweisung an einen Namen stattfinden.

<name> = <ausdruck>

Es ist dabei völlig unerheblich WAS in <ausdruck> steht.
...
Genau das wundert mich eben: Es ist ja eben nicht vollkommen unerheblich was in <ausdruck> steht. Das hätte ich ja erwartet.
Die Variable 'eggs' selbst darf dort im Ausdruck ja nicht stehen - sonst kommt es zu einem Fehler.
Und das wundert mich. Ich darf die Variable lesen - bekomme auch den Wert geliefert, der in dem Hauptprogramm steht. Ich darf die Variable in der Funktion auch überschreiben - nur darf sie nicht Teil des Ausdrucks sein.

Code: Alles auswählen

eggs = eggs + 5 # gibt also einen Fehler

Code: Alles auswählen

eggs = temp
eggs = temp + 5 
Ich versuche nur zu verstehen warum man das in Python so zulässt. Es ist für mich unlogisch den Wert
- lesen zu können,
- die Variable lokal überschreiben zu können.
Aber beides in Kombination geht nicht.

Da blättert mein Nagellack beim Lesen ab...
Benutzeravatar
sparrow
User
Beiträge: 1362
Registriert: Freitag 17. April 2009, 10:28

Dienstag 11. Juni 2019, 07:43

Ist doch ganz einfach. Du sagst "i =" damit existert im lokalen Namensraum eine nicht initialisierte Variable mit dem Namen "i". Und wenn du dann hinter dem Gleichheitszeichen anfängst, "i" zu verwenden, dann ist es halt nicht initialisiert.

Ich verstehe dein Problem nicht. Ich kenne keinen Praxisfall, in dem das relevant wäre. Dur regst dich völlig umsonst auf und verschwendest Lebenszeit.
Nochmal: Variablen als Parameter in die Funktion. Alles andere ist kaka.
Zuletzt geändert von sparrow am Dienstag 11. Juni 2019, 07:45, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 10596
Registriert: Sonntag 21. Oktober 2012, 17:20

Dienstag 11. Juni 2019, 07:44

Es gibt kein "Überschreiben". Eine Variable ist entweder im lokalen Namensraum definiert, oder außerhalb. Sobald eine Zuweisung stattfindet, nimmt Python an, die Variable wäre lokal. Und dann macht `eggs = eggs + 5` keinen Sinn mehr, weil der Wert von `eggs` nicht definiert ist, also eggs + 5 nicht berechnet werden kann.
Wie schon die anderen geschrieben haben, kommt der Fall, dass eine Variable einen globalen Wert hat und dann lokal geändert werden soll, nicht vor.
Es wäre doch sehr verwirrend, wenn ein globale Variable und eine lokale den selben Namen haben könnten.

Daher die Konvention, Konstanten komplett groß zu schreiben, dann ist bei Deinem Beispiel alles ok, falls `eggs` tatsächlich konstant wäre:

Code: Alles auswählen

EGGS = 1
def spam():
    eggs = EGGS + 1
    print(eggs)
Bei Variablem `eggs` hat ThomasL bereits die Lösung geschrieben:

Code: Alles auswählen

def spam(eggs):
    print(eggs)
    eggs = eggs + 1
    return eggs

def main():
    eggs = 1
    while eggs < 10:
        eggs = eggs + 1
        eggs = spam(eggs)

if __name__ == '__main__':
    main()
Dass Fälle, die nicht vorkommen dürfen, zu Fehlermeldungen führen, finde ich ganz ok. Stell Dir nur die Verwirrung vor, wenn so komische Dinge, wie Du sie versuchst, erlaubt wären und es nur zur Laufzeit zu entscheiden wäre, ob eine Variable nun an der Stelle lokal oder global ist.
Benutzeravatar
__blackjack__
User
Beiträge: 4243
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Dienstag 11. Juni 2019, 09:01

@Miranda: Deine Aussage „das ist doch nichts halbes und nichts ganzes“ stimmt halt nicht – ein Name ist entweder lokal in der Funktion, und zwar an *jeder* Stelle, oder er ist nicht lokal, an *jeder* Stelle in der Funktion. Wenn er mal lokal und mal nicht-lokal wäre – *das* wäre verwirrend und nichts halbes und nichts ganzes.
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
Benutzeravatar
kbr
User
Beiträge: 1069
Registriert: Mittwoch 15. Oktober 2008, 09:27

Dienstag 11. Juni 2019, 14:33

@Miranda: Stell Dir einmal vor, das ginge

Code: Alles auswählen

def function():
    eggs = eggs + 1
und vor anlegen und zuweisen an das lokale Label "eggs" würde ein nicht lokales Label "eggs" referenziert werden. Wie würde sich dann der folgende Code verhalten?

Code: Alles auswählen

def function():
    for _ in range(3):
        eggs = eggs + 1
Ab dem zweiten Durchlauf der Schleife hätte die dritte Zeile nun ein anderes Verhalten. Das wäre übel. Es ist daher alles richtig so, wie es ist.
Miranda
User
Beiträge: 12
Registriert: Sonntag 23. September 2018, 21:45

Mittwoch 12. Juni 2019, 22:55

__blackjack__ hat geschrieben:
Dienstag 11. Juni 2019, 09:01
@Miranda: Deine Aussage „das ist doch nichts halbes und nichts ganzes“ stimmt halt nicht – ein Name ist entweder lokal in der Funktion, und zwar an *jeder* Stelle, oder er ist nicht lokal, an *jeder* Stelle in der Funktion. Wenn er mal lokal und mal nicht-lokal wäre – *das* wäre verwirrend und nichts halbes und nichts ganzes.
Hallo,
genau das war es was mir gefehlt hat: Ich hatte nicht gesehen, dass es eine exklusive Sache ist. Also ist es entweder eine globale Variable, die nur gelesen wird oder es wird mit der Zuweisung zu einer rein lokalen Variablen.
_DAS_ ist für mich dann doch eingängig.

Vielen lieben Dank!!!
Benutzeravatar
DeaD_EyE
User
Beiträge: 324
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Sonntag 16. Juni 2019, 15:20

Wieso nicht so:

Code: Alles auswählen

def spam(eggs):
    """
    Hier nichts ausgeben, nur rechnen und den Wert dann zurück liefern.
    Das ist was Funktionen eigentlich machen sollten.
    Lieferst du einen Wert zurück und gibst gleichzeitig etwas in der Konsole aus
    ist keine pure Funktion mehr. In anderen Sprachen unterscheidet man Funktionen
    von Prozeduren, bei Python gibt es das nicht. Dennoch kann man sich daran orientieren.
    Prozeduren machen irgendwas mit der Welt in der sie leben. Sie verändern sie in dem sie z.B. Text irgendwo ausgeben.
    Funktionen rechnen nur und beeinflussen nicht die Welt.
    
    Wenn man text ausgeben möchte, kann man dafür eine extra Funktion erstellen.
    Die andere Funktion soll nichts weiter machen, als rechnen. Das hat den Vorteil, dass
    du diese Funktion auch in anderen Teilen deines Programm nutzen kannst, ohne das etwas
    in der Konsole an Text ausgeben wird.
    
    Ich rege mich jedes mal darüber auf, wenn man wieder irgendein Framework mein Terminal zumüllt.
    Da hat man  das Problem, dass man das nicht so einfach abschalten kann, es seiden man verändert den Code des Framworks.
    """
    return eggs +1 
 

eggs=1
while eggs<10:
    eggs = spam(eggs)
    print(eggs)
oder Pythonic:

Code: Alles auswählen

for i in range(1, 11):
    print(i)
Globale Variablen braucht man nur selten.
Es kommt z.B. bei dem MicroWebFramework Flask vor. Dort lebt app auf Modulebene.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
DasIch
User
Beiträge: 2519
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Montag 17. Juni 2019, 11:50

DeaD_EyE hat geschrieben:
Sonntag 16. Juni 2019, 15:20
Globale Variablen braucht man nur selten.
Es kommt z.B. bei dem MicroWebFramework Flask vor. Dort lebt app auf Modulebene.
Auch bei Flask braucht man es nicht. Man es kann über das Application Factory Pattern umgehen. Es ist bei nicht-trivialen Anwendungen auch definitiv zu empfehlen.
Benutzeravatar
DeaD_EyE
User
Beiträge: 324
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Freitag 21. Juni 2019, 16:32

Und trotzdem wird es in jedem Tutorial falsch gelehrt. Ist ja auch bequem, bis man irgendwann über seinen eigenen Code stolpert.
Mit app auf Modulebene hatte ich bisher keine Probleme, aber wie du schon bereits geschrieben hast, spielt das erst bei komplexen Anwendungen eine Rolle.

Das eigentliche Problem ist, dass sich schlechte Angewohnheiten nicht so einfach abgewöhnen lassen. Gerade für Anfänger ist das ein Problem, wenn sie
es falsch lernen.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Antworten