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

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

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
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

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

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

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
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

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

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?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

droptix hat geschrieben:By the way: Was ist ZSH?
Das ist die Z Shell.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
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.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

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.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

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

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

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

Danke, also kann man gar nicht nach außen gucken? Nur was über Parameter reinkommt? Das überrascht mich jetzt schon...
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. :P
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

BlackJack hat geschrieben:Das geht auch, aber ich verrate Dir nicht wie. :P
Cool 8)

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

:evil: :wink:
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...
Benutzeravatar
mkesper
User
Beiträge: 919
Registriert: Montag 20. November 2006, 15:48
Wohnort: formerly known as mkallas
Kontaktdaten:

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...
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

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...
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
Antworten