lokale Variable in Funktion

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.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

lokale Variable in Funktion

Beitragvon droptix » Samstag 24. Dezember 2005, 12:06

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?
joe

Re: lokale Variable in Funktion

Beitragvon joe » Samstag 24. Dezember 2005, 12:39

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
Benutzeravatar
Leonidas
Administrator
Beiträge: 16023
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Beitragvon Leonidas » Samstag 24. Dezember 2005, 12:40

Ich hätte das ganze anders gelöst:

Code: Alles auswählen

import os.path
lpath = ["a", "b", "c"]
os.path.sep.join(lpath)
My god, it's full of CARs! | Leonidasvoice vs Modvoice
BlackJack

Beitragvon BlackJack » Samstag 24. Dezember 2005, 23:00

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.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Viele Wege führen nach Rom

Beitragvon droptix » Sonntag 25. Dezember 2005, 11:37

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?
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Nur ein Weg funzt richtig

Beitragvon droptix » Sonntag 25. Dezember 2005, 11:49

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
Benutzeravatar
Leonidas
Administrator
Beiträge: 16023
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Re: Nur ein Weg funzt richtig

Beitragvon Leonidas » Sonntag 25. Dezember 2005, 15:24

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).
My god, it's full of CARs! | Leonidasvoice vs Modvoice
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Re: Nur ein Weg funzt richtig

Beitragvon droptix » Sonntag 25. Dezember 2005, 22:11

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?
Benutzeravatar
Leonidas
Administrator
Beiträge: 16023
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Re: Nur ein Weg funzt richtig

Beitragvon Leonidas » Sonntag 25. Dezember 2005, 22:17

droptix hat geschrieben:By the way: Was ist ZSH?

Das ist die Z Shell.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
BlackJack

Re: Viele Wege führen nach Rom

Beitragvon BlackJack » Sonntag 25. Dezember 2005, 22:23

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.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Re: Viele Wege führen nach Rom

Beitragvon droptix » Montag 26. Dezember 2005, 21:12

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.
Benutzeravatar
Leonidas
Administrator
Beiträge: 16023
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Re: Viele Wege führen nach Rom

Beitragvon Leonidas » Montag 26. Dezember 2005, 23:00

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.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
wivaxing
User
Beiträge: 21
Registriert: Mittwoch 17. Oktober 2007, 14:16

Beitragvon wivaxing » Donnerstag 25. Oktober 2007, 13:55

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
BlackJack

Beitragvon BlackJack » Donnerstag 25. Oktober 2007, 15:02

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.
wivaxing
User
Beiträge: 21
Registriert: Mittwoch 17. Oktober 2007, 14:16

Beitragvon wivaxing » Donnerstag 25. Oktober 2007, 15:22

Danke, also kann man gar nicht nach außen gucken? Nur was über Parameter reinkommt? Das überrascht mich jetzt schon...

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder