Seite 1 von 1
lokale Variable in Funktion
Verfasst: Samstag 24. Dezember 2005, 12:06
von droptix
Hmmm, klingt eigentlich wie eine Newbie-Frage. Aber für mich aus der PHP-Welt ist das hier unverständlich:
Code: Alles auswählen
def aendere(eine_liste):
eine_liste[1] = 4
x = [1,2,3]
aendere(x)
print x # Druckt [1,4,3]
Code: Alles auswählen
def nichtaendern(x):
x = 0
y = 1
nichtaendern(y)
print y # Druckt 1
Ich finde es seltsam, dass man beim Übergeben eines Funktions-Parameters zwar nur eine "Kopie" des Parameter-Wertes übergibt, der sich aber trotzdem rückwirkend auswirkt (s. Code-Snippet 1).
Wenn der Wert also eine Liste ist und ich die nunmal in meiner Funktion bearbeiten muss, dann wird die Liste auch global geändert, was sehr schlecht ist. In PHP ist das nicht so.
Ich will eine Liste einer Verzeichnisstruktur zu einem Pfad verknüpfen. Da
os.path.join(path1, path2) leider keine Listen verketten kann, sondern nur Strings, habe ich mir folgende Funktion gebastelt:
Code: Alles auswählen
import os
class Foo:
def __init__(self):
lPath = ["a", "b", "c"]
sDir = "d"
x = self.joined_path(lPath)
print x
print lPath
def joined_path(self, lPath):
while len(lPath) > 1:
lPath[0] = os.path.join(lPath[0], lPath.pop(1))
return str(lPath.pop())
Foo()
Leider ist
lPath nach
joined_path(lPath) leer. Ich müsste aber damit weiter arbeiten.
Wieso wird die Liste nicht nur innerhalb (lokal) der Funktion geändert, sondern auch global? Wichtiger: Wie stelle ich es an, dass ich die Liste nicht global verändere?
Re: lokale Variable in Funktion
Verfasst: Samstag 24. Dezember 2005, 12:39
von joe
droptix hat geschrieben:
Ich finde es seltsam, dass man beim Übergeben eines Funktions-Parameters zwar nur eine "Kopie" des Parameter-Wertes übergibt,
Das ist falsch. Die übergabe ist immer by-reference. Für die kopie musst du selbst sorgen, was aber nur bei veränderlichen datentypen nötig ist. Beispiel:
Code: Alles auswählen
def blah(eine_liste):
eine_lokale_liste = eine_liste[:]
eine_lokale_liste[1] = 4
Python ist einfach, wenn man sich erstmal dran gewöhnt hat, ausschließlich in referenzen zu denken.
joe
Verfasst: Samstag 24. Dezember 2005, 12:40
von Leonidas
Ich hätte das ganze anders gelöst:
Code: Alles auswählen
import os.path
lpath = ["a", "b", "c"]
os.path.sep.join(lpath)
Verfasst: Samstag 24. Dezember 2005, 23:00
von BlackJack
Ich hätte es noch anders gelöst:
Code: Alles auswählen
In [86]: path = ["a", "b", "c"]
In [87]: os.path.join(*path)
Out[87]: 'a/b/c'
Zu der Sache mit der Parameterübergabe: Ich komme am besten mit dem Modell `Namen` und `Objekte` zurecht. Es gibt in Python Objekte die irgendwo rumliegen und man kann kleine gelbe Post-It-Zettelchen mit Namen draufkleben, damit man sie wiederfindet.
Code: Alles auswählen
spam = list()
def parrot(eggs):
eggs.append(42)
parrot(spam)
print spam
In dem Beispiel wird ein Listenobjekt angelegt und der Name `spam` draufgeklebt. Dann wird die Funktion definiert und auf das Funktionsobjekt der Name `parrot` geklebt.
Beim Aufruf wird erst das Objekt mit dem Namen `parrot` gesucht -- ach ja, das ist das Funktionsobjekt. Dann wird das Objekt mit dem `spam` Aufkleber gesucht und das Listenobjekt gefunden.
Für den Aufruf wird vor der Funktionsausführung nun der Name `eggs` zusätzlich auf das Listenobjekt geklebt weil das der Name des Parameters ist. Da kleben jetzt zwei Namensaufkleber auf dem selben Objekt.
In der Funktion geht jetzt das Spielchen wieder los: Wir suchen das Objekt mit dem `eggs` Aufkleber. Ist immer noch das Listenobjekt und da wird ein Integer-Objekt angehängt. Am Ende der Funktion wird dann der `eggs` Aufkleber wieder weggeworfen.
Das mit den Namensaufklebern ist ein ziemlich simples Konzept das Pythons Verhalten in dieser Hinsicht aber komplett erklärt.
Viele Wege führen nach Rom
Verfasst: Sonntag 25. Dezember 2005, 11:37
von droptix
Oh, das ist interessant. Was ist der Unterschied (was passiert jeweils) in diesen beiden Beispielen?
Bsp. 1:
Code: Alles auswählen
import os.path
lpath = ["a", "b", "c"]
print os.path.sep.join(lpath)
# result: a\b\c
os.path.sep ist dachte ich nur der Separator für die Verkettung von Pfaden, also unter Windows der Backslash und unter Unix der Slash. Letztlich wird ja doch join() ausgeführt, welches nur zwei Strings verknüpfen kann. Wieso geht das im Gegensatz zu print
os.path.join(lpath) ?
Bsp. 2:
Code: Alles auswählen
import os.path
lpath = ["a", "b", "c"]
print os.path.join(*lpath)
# result: a\b\c
Was bewirkt der Stern bei
*lpath? Eine Art interne Schleife, die das "äußere"
os.path.join() für jeden Wert der Liste in lpath durchlaufen lässt?
Nur ein Weg funzt richtig
Verfasst: Sonntag 25. Dezember 2005, 11:49
von droptix
Doch nur ein Weg funzt richtig! Mein Ziel ist es ja, Pfade zu verknüpfen. Dabei kann es passieren, dass in der Liste der zu verknüpfenden Pfade auch mal ein Slash zu Beginn oder am Anfang steht.
os.path.join() verknüpft ja "clever", prüft also genau sowas und gibt keine doppelten Slashes zurück. Hier nochmal der deutliche Unterschied (unter Windows):
Code: Alles auswählen
import os
dirlist = ["a", "b", "c%s" % os.path.sep, "d"]
folder = "e"
print os.path.join(os.path.join(*dirlist), folder)
# print: a\b\c\d\e
print os.path.join(os.path.sep.join(dirlist), folder)
# print: a\b\c\\d\e
Re: Nur ein Weg funzt richtig
Verfasst: Sonntag 25. Dezember 2005, 15:24
von Leonidas
droptix hat geschrieben:os.path.sep ist dachte ich nur der Separator für die Verkettung von Pfaden, also unter Windows der Backslash und unter Unix der Slash. Letztlich wird ja doch join() ausgeführt, welches nur zwei Strings verknüpfen kann. Wieso geht das im Gegensatz zu print os.path.join(lpath) ?
os.path.sep ist ein String-Objekt (oder um es mit BlackJacks sehr passender Erklärung auszudrücken: ein Zettelchen/Name der auf ein String-Objekt zeigt), welches wie jeder String die Funktion
join(liste) hat. Damit werden die Strings in der Liste mit dem String der in os.path.sep steht verklebt. DIese lösung ist nicht besonders schlau, das gebe ich zu.
droptix hat geschrieben:os.path.join() verknüpft ja "clever", prüft also genau sowas und gibt keine doppelten Slashes zurück. Hier nochmal der deutliche Unterschied (unter Windows):
Wobei ich denke, dass Windows doppelte Backslashes sowieso ignoriert (habs aber nur mit ZSH unter Windows getestet).
Re: Nur ein Weg funzt richtig
Verfasst: Sonntag 25. Dezember 2005, 22:11
von droptix
Leonidas hat geschrieben:Wobei ich denke, dass Windows doppelte Backslashes sowieso ignoriert (habs aber nur mit ZSH unter Windows getestet).
Ja das stimmt auch. Aber ich mag es nicht mehr "quick and dirty". Dieser Teil soll ja auf mehreren Systemen lauffähig sein, also auch auf Unix-Derivaten.
By the way: Was ist
ZSH?
Re: Nur ein Weg funzt richtig
Verfasst: Sonntag 25. Dezember 2005, 22:17
von Leonidas
droptix hat geschrieben:By the way: Was ist ZSH?
Das ist die
Z Shell.
Re: Viele Wege führen nach Rom
Verfasst: Sonntag 25. Dezember 2005, 22:23
von BlackJack
droptix hat geschrieben:os.path.sep ist dachte ich nur der Separator für die Verkettung von Pfaden, also unter Windows der Backslash und unter Unix der Slash. Letztlich wird ja doch join() ausgeführt, welches nur zwei Strings verknüpfen kann. Wieso geht das im Gegensatz zu print os.path.join(lpath) ?
Die `join()` Methode auf Zeichenketten kann nicht nur zwei Zeichenketten zusammenfügen. Als Parameter wird ein "iterable" erwartet, also etwas wo man mit ``for item in iterable`` drüberlaufen kann. Wobei die `item` Objekte dann Zeichenketten oder Unicode-Objekte sein müssen.
Code: Alles auswählen
import os.path
lpath = ["a", "b", "c"]
print os.path.join(*lpath)
# result: a\b\c
Was bewirkt der Stern bei
*lpath? Eine Art interne Schleife, die das "äußere"
os.path.join() für jeden Wert der Liste in lpath durchlaufen lässt?
`os.path.join()` nimmt zwei bis beliebig viele Parameter entgegen. Eine Liste wäre aber nur *ein* Parameter. Der '*' sorgt dafür, dass aus dem "iterable" danach mehrere Parameter werden. Im Beispiel ist der Aufruf dann äquivalent zu ``os.path.join("a", "b", "c")``.
Mir scheint Du benutzt "ungarische Notation" bei den Namen -- das ist in Python unüblich weil man leicht in Versuchung kommt falsche Annahmen über die Objekte zu machen. Wenn man dann mal den Typ ändert sind alle Namen "falsch". Man könnte aus der Pfadliste da oben zum Beispiel problemlos ein Tupel machen. Oder auch eine geöffnete Datei übergeben in der eine Pfadkomponente pro Zeile steht. Oder ein ganz anderes, selbsgeschriebenes Objekt das "iterable" ist.
Re: Viele Wege führen nach Rom
Verfasst: Montag 26. Dezember 2005, 21:12
von droptix
BlackJack hat geschrieben:Mir scheint Du benutzt "ungarische Notation" bei den Namen -- das ist in Python unüblich weil man leicht in Versuchung kommt falsche Annahmen über die Objekte zu machen.
Was meinst du mit "ungarische Notation"? Die NAmensvergebung für Variablen? Weil ich u.a. den Prefix l* für Listen, s* für Strings und f* für Filehandler benutze?
Angewohnheit... hilft mir ehrlich gesagt bei der Wahl meiner Namen, weil man häufig aus einem sPath durch split() einen lPath erzeugt oder umgekehrt. Der Inhalt ist ja "gleich", liegt nur in anderer Form vor.
Ich werd mal drüber nachdenken.
Re: Viele Wege führen nach Rom
Verfasst: Montag 26. Dezember 2005, 23:00
von Leonidas
droptix hat geschrieben:Was meinst du mit "ungarische Notation"? Die NAmensvergebung für Variablen? Weil ich u.a. den Prefix l* für Listen, s* für Strings und f* für Filehandler benutze?
Ganz genau, das ist in VB auch sehr verbreitet für Widgets: cmdIrgendwas, lblIrgendwasAnderes....
droptix hat geschrieben:Angewohnheit... hilft mir ehrlich gesagt bei der Wahl meiner Namen, weil man häufig aus einem sPath durch split() einen lPath erzeugt oder umgekehrt. Der Inhalt ist ja "gleich", liegt nur in anderer Form vor.
Wobei der sPath kann sowohl ein Sting sein, als auch ein Unicode Objekt oder etwas davon abgeleitetes, oder eben etwas ganz anderes. Denn in Python ist es so, dass die Typen eben keineswegs fest sind. In PyGTK Programmen habe ich es manchmal so, dass ich von mehreren Widgets auf ein Callback zeige, dieses Callback bekommt als Parameter das Widget dazugepackt, wobei nicht gesagt wird, was das für ein Widget ist. Deswegen nenne ich den Parameter einfach nur Widget, statt cmdWidget oder so.
Verfasst: Donnerstag 25. Oktober 2007, 13:55
von wivaxing
Wie verhält es sich mit globalen/lokalen Variablen in Funktionen?
Wenn ich sowas schreibe
Code: Alles auswählen
globoli = None
def foo()
globoli = 41.99
foo()
if globoli == None:
print "shame!\n"
And shame it is... Ich bin ziemlich neu in Python und habe fast einen Tag gebraucht um rauszufinden, daß er in der Funktion so wohl nur den lokalen Scope der Funktion sieht.
Wie greife ich jetzt auf den Scope außen zu? Ist zwar für Funktionen etwas unsauber, aber die Funktion macht eigentlich schon was anderes und initialisiert beim ersten Aufruf ein globales Objekt, welches alle brauchen. Zumindest soll sie das einmal...
danke
Verfasst: Donnerstag 25. Oktober 2007, 15:02
von BlackJack
Wenn irgendwo in der Funktion eine Zuweisung an einen Namen erfolgt, ist dieser Name lokal.
Sauberer wäre das hier:
Code: Alles auswählen
def foo():
return 41.99
globoli = foo()
if globoli is None:
print 'shame!'
Bzw. wenn sich wirklich mehrere Funktionen einen Zustand teilen, könnte das ein Zeichen sein, dass man eine Klasse oder ein "config"-Modul benutzen sollte.
Verfasst: Donnerstag 25. Oktober 2007, 15:22
von wivaxing
Danke, also kann man gar nicht nach außen gucken? Nur was über Parameter reinkommt? Das überrascht mich jetzt schon...
Verfasst: Donnerstag 25. Oktober 2007, 15:27
von BlackJack
Nach aussen *gucken* kannst Du immer, sonst könnte man ja gar keine anderen Funktionen aus einer Funktion aufrufen ohne sie als Argument zu übergeben. Namen ausserhalb an andere Objekte binden ist das "Problem". Das geht auch, aber ich verrate Dir nicht wie.

Verfasst: Donnerstag 25. Oktober 2007, 20:23
von Leonidas
BlackJack hat geschrieben:Das geht auch, aber ich verrate Dir nicht wie.

Cool
Nein, im Ernst, BlackJack hat Recht: wenn du aus einer Funktion globale Variablen ändern musst, dann hast du etwas falsch gemacht und solltest eher überdenken, wie man es besser macht und nicht versuchen aus Funktionen globale Namen zu überschreiben.
Verfasst: Freitag 26. Oktober 2007, 11:40
von wivaxing
Ich habe eigentlich überhaupt nichts gemacht - ich habe nur einen riesigen sequentiellen Python Code erhalten - für mich versuche ich da Struktur reinzubringen ohne alles über den Haufen zu werfen
Mit jeder Änderung will ich möglichst wenig erstmal an der Ablauflogik ändern, da ich noch Null Erfahrung mit Debugging in Python habe und dieses Programm etwas eigenartig auf "Änderungen" reagiert, deswegen bin ich dabei erstmal Teile in Funktionen auszulagern nur aus Gründen der Strukturierung. Das wird sowie später komplett neu gemacht, aber erstmal muß ich finden was in dem Ding überhaupt vor sich geht...
Verfasst: Freitag 26. Oktober 2007, 14:06
von mkesper
wivaxing hat geschrieben:Danke, also kann man gar nicht nach außen gucken? Nur was über Parameter reinkommt? Das überrascht mich jetzt schon...
Doch, aber das will man nicht, weil es in Teufels Küche(TM) führt...
Verfasst: Freitag 26. Oktober 2007, 20:03
von birkenfeld
mkallas hat geschrieben:wivaxing hat geschrieben:Danke, also kann man gar nicht nach außen gucken? Nur was über Parameter reinkommt? Das überrascht mich jetzt schon...
Doch, aber das will man nicht, weil es in Teufels Küche(TM) führt...
So ist das aber falsch. Man will durchaus nach außen *gucken*, man will nur nicht außen was verändern.
Wozu hätten wir sonst nested scopes...