Gültigkeitsbereiche und Namensräume in Python

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
uweddf
User
Beiträge: 12
Registriert: Freitag 30. August 2013, 10:28

Hallo,

zuerst einmal möchte ich mich bei allen bedanken, die in Ihrer Freizeit sich in diesem Forum engagieren.

Ich selber bin neu hier, habe das Python-Tutorial sowie Learn Python The Hard Way, 3rd Edition gelesen.
Nachdem ich jetzt die ersten Gehversuche mit Python unternommen habe, ergeben sich für mich einige Fragen
bezüglich des Gültigkeitsbereiches und der Namensräume in Python.

Obwohl ich die zwei nachstehend aufgeführten Python-Module selbst geschrieben habe, verstehe ich nicht wirklich,
was ich da codiert habe.

Ich habe Python 2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)] on win32 benutzt.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Name  :  Test1.py
# -----------------------------------------------------------------------------
#
def test(myDict, myKey):
    for i in xrange(10):
        myDict[myKey].append(i)



def main():
    myDict = {'a':[],'b':[],'c':[],'d':[]}
    Subroutine = Test2.Subroutinen(myDict)

    for i in xrange(10):
        myDict['a'].append(i)

    Subroutine.myListAppend('b')

    Subroutine.myListAppend2(myDict,'c')

    test(myDict, 'd')

    for i in ['a', 'b', 'c', 'd']:
        print '     %s : %s' %(i, myDict[i])



if __name__ == '__main__':
    import time
    import os
    import Test2

    myStartTime = time.clock()

    print '\n >>> Starte Programm : %s\n' %os.path.abspath(__file__)

    main()

    print( '\n >>> Das Programm wurden erfolgreich beendet. <<<')
    print('      - Elapsed Time : %1.6f Sekunden -\n' %(time.clock() - myStartTime))

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Name  :  Test2.py
# -----------------------------------------------------------------------------
#
class Subroutinen(object):
    def __init__(self, myDict):
        self.myDict = myDict


    def myListAppend(self, myKey):
        myDict = self.myDict

        for i in xrange(10):
            myDict[myKey].append(i)

        return


    def myListAppend2(self, a, b):
        for i in xrange(10):
            a[b].append(i)

        return
Nachstehend werde ich darlegen, was ich glaube codiert zu haben.

Im Python-Module Test1.py in der Funktion main() habe ich,

[1] Ein locales Dictnary definiert.

.... myDict = {'a':[],'b':[],'c':[],'d':[]}


[2] Das Objekt myDict wird an das Objekt Subroutine gebunden.

.... Subroutine = Test2.Subroutinen(myDict)

Es soll eine Referenz auf das Objekt myDict im Namensraum main() an die Klasse Subroutine übergeben werden.

.... class Subroutinen(Objekt):
.... .... def __init__(self, myDict):
.... .... .... self.myDict = myDict

Unter einer Referenz verstehe ich den Verweis auf ein bereits bestehendes Objekt, also keine Kopie von diesem
sondern ein Verweis auf das Original.

Mit dieser Vorgehensweise möchte ich vermeiden, das zwischen den Objekten der jeweilige Inhalt ständig hin und her kopiert wird.


[3] An das Objekt myDict werden Werte angehängt.

.... for i in xrange(10):
.... .... myDict['a'].append(i)


[4] Aufruf der Funktion Subroutine.myListAppend().

.... Subroutine.myListAppend('b')

.... ----------------------------

.... def myListAppend(self, myKey):
.... .... myDict = self.myDict

.... .... for i in xrange(10):
.... .... .... myDict[myKey].append(i)

.... .... return

Damit habe ich das hin und her kopieren des Inhaltes des Objektes myDict vermieden,
da die Funktion Subroutine.myListAppend() auf das Objekt myDict im Namensraum main() verweist.

Anmerkung : Das Konstrukt myDict = self.myDict scheint auf den ersten Blick überflüssig zu sein.
Ich habe einige Tests durchgeführt und bin zu dem Ergebnis gekommen, das Funktionen wesentlich
performanter ausgeführt werden, wenn ich self an lokale Variablen der jeweiligen Funktion übergebe
und nicht direkt mit zum Beispiel self.myDict arbeite.

Warum das so ist, kann ich mit meinem heutigen Wissensstand leider nicht erklären.


[5] Aufruf der Funktion Subroutine.myListAppend2()

.... Subroutine.myListAppend2(myDict,'c')

.... ------------------------------------

.... def myListAppend2(self, myDict, myKey):
.... .... for i in xrange(10):
.... .... .... myDict[myKey].append(i)
.... .... return

Diese Funktion dürfte aus meiner Sicht, die locale Variable myDict innerhalb der Funktion main() gar nicht verändern.
Dennoch geschieht genau dieses und ich finde hierfür überhaupt keine plausible Erklärung warum dieses so ist.


[6] Aufruf der Funktion test()

.... test(myDict, 'd')

.... ------------------------------------

.... def test(myDict, myKey):
.... .... for i in xrange(10):
.... .... .... myDict[myKey].append(i)

Diese Funktion dürfte aus meiner Sicht, die locale Variable myDict innerhalb der Funktion main() gar nicht verändern.
Dennoch geschieht genau dieses und ich finde auch hierfür keine plausible Erklärung warum dieses so ist.


Könnte irgend jemand mir bitte ein klein wenig Hilfe zum besseren Verständnis der jeweiligen Gültigkeitsbereiche und der Namensräume geben.

Vielen Dank.
BlackJack

@uweddf: [2] ist falsch. Es wird erst der Ausdruck auf der rechten Seite der Zuweisung ausgewertet und das ist nicht das Wörterbuch sondern ein Exemplar der Klasse `Test2.Subroutine`. Wobei der Name für eine Klasse irgendwie ungünstig, weil sehr irreführend ist. Subroutine ist eine andere Bezeichnung für Funktion oder Prozedur. Solange die Klasse, also der Datentyp, nicht tatsächlich eine Subroutine repräsentiert ist der Name unpassend.

Referenzen gibt es in Python nicht wirklich, aber das ist natürlich das was hinter den Kulissen in der Implementierung passiert. Python kopiert bei Zuweisungen an Namen oder Argumenten bei Aufrufen keine Werte. Nie. Wenn man etwas kopiert haben möchte, dann muss man das explizit selber tun.

[3] Präziser: An die Liste zu dem Schlüssel 'a' des Wörterbuchs `myDict` werden Daten angehängt. Das Wörterbuch selbst verändert sich dadurch nicht.

[4] `Subroutine.myListAppend()` ist keine Funktion sondern eine (gebundene) Methode.

Wenn Du Dir um die Ablaufgeschwindigkeit bei auf diesem Niveau Gedanken machst, dann ist Python vielleicht nichts für Dich. Und wenn Du auf Grund dieser Erkenntnis jetzt anfängst immer alle Attribute erst an lokale Namen zu binden, dann ist Python ganz sicher nichts für Dich. Wenn man da optimieren möchte/muss, sollte man erst einmal effizienteren Code schreiben der den Quelltext selber nicht komplexer macht. Zum Beispiel:

Code: Alles auswählen

    def list_append(self, key):
        self.mapping[key].extend(xrange(10))
Der Geschwindigkeitsunterschied kommt dadurch zustande das man in einem Fall 10 mal einen lokalen Namen nimmt und von dem ein Attribut abfragt und im anderen Fall 1 mal das Attribut von einem lokalen Namen abfragt und dann 10 mal nur noch einen lokalen Namen verwendet. Ist halt weniger Arbeit die da verrichtet werden muss.

[5] Warum erwartest Du das bei diesem Aufruf plötzlich eine Kopie von `myDict` aus `main()` übergeben wird während Du beim Aufruf von `Test2.Subroutine()` von einer Referenz ausgegangen bist? Aufruf ist Aufruf. Da werden *nie* Kopien gemacht.

[6] Siehe [5]. Python kopiert keine Werte wenn man das nicht explizit sagt.
Antworten