rätselhaftes Verhalten

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
kasimon
User
Beiträge: 18
Registriert: Freitag 21. März 2003, 20:54
Wohnort: Kiel

Hallo...

Entweder habe ich gerade eine Denkblockade, oder ich verstehe Python doch noch nicht so richtig... Kann mir vielleicht jemand von euch klarmachen, warum folgender Code nicht funktioniert (2.1/2.2):

Code: Alles auswählen

f=0
def fun():
  f=f+1
fun()
Was funktioniert ist

Code: Alles auswählen

f=0
def fun():
  global f
  f=f+1
fun()
aber ich wüsste schon gern, warum ersteres nicht läuft.

schonmal danke im vorraus,
K.
lbuega
User
Beiträge: 75
Registriert: Dienstag 15. April 2003, 08:51
Wohnort: Weissach

Ja, das ist so richtig. Hängt mit dem Namensraum (^=Geltungsbereich) zusammen. Die Funktion kennt die Variable nicht da sie ausserhalb dieser deklariert wurde - es sei denn Du sagst ihr durch "global", dass sie auch ausserhalb dannach schauen soll.

Eine andere Möglichkeite wie sie das "f" kennen würde ist durch Übergabe als Parameter:

Code: Alles auswählen

f=0
def fun(arg):
    arg+=1
fun(f)
Aber ohne ein "return" in der "fun"-Funktion geht dir der geänderte Wert auch wieder verloren, daher:

Code: Alles auswählen

f=0
def fun(arg):
    arg+=1
    return arg
f=fun(f)
Kann mit entsprechendem "print f"-Befehl vor und nach dem Ausführen der Funktion auch schön nachvollzogen werden.
Voges
User
Beiträge: 564
Registriert: Dienstag 6. August 2002, 14:52
Wohnort: Region Hannover

lbuega hat geschrieben:Ja, das ist so richtig. Hängt mit dem Namensraum (^=Geltungsbereich) zusammen. Die Funktion kennt die Variable nicht da sie ausserhalb dieser deklariert wurde - es sei denn Du sagst ihr durch "global", dass sie auch ausserhalb dannach schauen soll.
Das trifft es nicht ganz. Folgendes funktioniert ja auch.

Code: Alles auswählen

x=99
def foo():
    print x
foo()
Da kein lokales x gefunden wird, wird einfach das globale x genommen. Die Sache ist etwas subtiler.
Ich hab' noch ein Beispiel

Code: Alles auswählen

x=99
def foo():
    print x
    x = 88
foo()
Folge:UnboundLocalError: local variable 'x' referenced before assignment
Es gibt die Regel, dass Zuweisungen immer lokale Namen erzeugen, aber - Achtung, jetzt kommt es! - diese Erzeugung des lokalen Namens geschieht nicht erst beim Aufruf der Funktion, sondern schon vorab beim Kompilieren. Das heißt, wenn Du foo() aufrufst, existiert bereits die Information, dass es ein lokales x gibt und print x erzeugt einen Fehler, da dem x noch kein Wert zugewiesen wurde.
Jan
joerg
User
Beiträge: 188
Registriert: Samstag 17. August 2002, 17:48
Wohnort: Berlin
Kontaktdaten:

Hallo Jan,
Voges hat geschrieben: Es gibt die Regel, dass Zuweisungen immer lokale Namen erzeugen, aber - Achtung, jetzt kommt es! - diese Erzeugung des lokalen Namens geschieht nicht erst beim Aufruf der Funktion, sondern schon vorab beim Kompilieren. Das heißt, wenn Du foo() aufrufst, existiert bereits die Information, dass es ein lokales x gibt und print x erzeugt einen Fehler, da dem x noch kein Wert zugewiesen wurde.
Jan
Danke für die Beschreibung, das war mir bisher gar nicht so klar. Ich habe noch ein bißchen gespielt: Wenn man die Zuweisung hinter einer Abfrage versteckt, wird das Verhalten noch undurchschaubarer:

Code: Alles auswählen

x = 99
def foo():
    print x
    if 0:
        x = 88
foo()
Das funktioniert, weil der Compiler offensichtlich die Zuweisung wegoptimiert. Ersetze ich aber die Null durch ein None, gibt es den oben beschriebenen Fehler. Der Zweig wird nicht wegoptimiert, weil ich den Namen "None" dann noch überschreiben dürfte, nehme ich jedenfalls an.

Aber: Eine leere Liste "[]" statt der Null führt auch zum Fehler, und diese ist nun mal konstant "unwahr", wie eine Null, nur merkt der Compiler das hier wohl nicht.

Irgendwie gefällt mir das Verhalten von Python an der Stelle nicht!

Jörg
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hallo joerg und alle anderen,

naja, das ganze lässt sich durch sauberes Programmieren leicht umschiffen.

1. verändere keine globalen Variablen.
2. deklariere locale Variablen am Anfang der Prozedut/Methode..

Zu 1.: Dies lässt sich dadurch machen, daß ian die aufrufende Funktion der neue wert mittels return zurückgegeben wird. Die Änderung globaler Variablen kann leicht zu seltsamen Nebeneffekten führen.
Zu 2.: Weise localen Variablen gleich am Anfang der Prozedur entsprechende Werte zu.


Gruß

Dookie
Voges
User
Beiträge: 564
Registriert: Dienstag 6. August 2002, 14:52
Wohnort: Region Hannover

joerg hat geschrieben:Irgendwie gefällt mir das Verhalten von Python an der Stelle nicht!
Naja, den Code, den wir hier verzapft haben, würde ich auch als 'bösartig' einstufen ;-). Unbeabsichtigt wird man wohl kaum damit konfrontiert.
Die lokalen Variablen einer Funktion bekommt man übrigens (in unserem Falle) mit
print foo.func_code.co_varnames
Jan
kasimon
User
Beiträge: 18
Registriert: Freitag 21. März 2003, 20:54
Wohnort: Kiel

Huh, da hab ich ja was losgetreten :shock:

Ich kann ja mal kurz erzählen, was ich eigentlich vorhatte: Es geht darum,
zu zählen, wie oft eine rekursive Funktion aufgerufen wird. Und da ich den
Code dann abgeben muss und der korrigierende kein Python kann, wollte ich
den code so einfach wie möglich halten. Alternative wäre eine simple
klasse, die nur die Funktion und den Zähler enthält. Ist aber optisch etwas
verwirrender (für den uneingeweiten).

Naja, ich werd da nochmal etwas basteln. Jedenfalls danke an alle für einige
kleine "aha"-effekte :-)

K.
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hallo nochmal,

Code: Alles auswählen

>>> def funktion():
...     funktion.cnt += 1
... 
>>> funktion.cnt = 0
>>> funktion()
>>> funktion()
>>> funktion()
>>> print funktion.cnt
3

Gruß

Dookie
kasimon
User
Beiträge: 18
Registriert: Freitag 21. März 2003, 20:54
Wohnort: Kiel

Dookie hat geschrieben:

Code: Alles auswählen

>>> def funktion():
...     funktion.cnt += 1
... 
>>> funktion.cnt = 0
>>> funktion()
>>> funktion()
>>> funktion()
>>> print funktion.cnt
3
Dankeschön! Warum das funktioniert, ist mir aber auch nicht ganz klar...
Da werd ich wohl mal wieder einen Blick in die Bücher werfen müssen.

Gruss,
K.
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Functionen sind eben auch objekte, und objekten kann man attrbute, in dem Fall das attribut cnt hinzufügen. Das ist genz praktisch, wenn man in einer Funktion eine Variable benötigt, die auch beim nächsten Aufruf der Funktion ihren Wert behalten soll.

Dookie
joerg
User
Beiträge: 188
Registriert: Samstag 17. August 2002, 17:48
Wohnort: Berlin
Kontaktdaten:

Hallo,

@Jan: Stimmt schon, besonders bilderbuchmäßig ist unser Code nicht gewesen. Trotzdem finde ich, daß eine Sprache sich auch in Randbereichen ihrer Definition sinnvoll verhalten sollte. Aber es gibt Schlimmeres und vor Allem viel schlimmere Sprachen.;-)

@Dookie: Das geht erst mit neueren Python-Versionen, oder? Ich habe stattdessen immer eine Klasse mit __call__()-Methode gebaut, dann hatte ich auch sowas wie eine Funktion mit Attributen. An die neuen Möglichkeiten werde ich mich wohl noch gewöhnen müssen. BTW: Hat schon mal jemand hier Erfahrungen mit den New-Style-Classes auf C-Ebene gesammelt?

Jörg
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hi joerg,

hab mit grad python1.5 installiert, damit gehts nicht, mit den pythonversionen 2.1 und 22 gehts sicher.


Dookie
Antworten