Tim Tepaße: Python, Tupel, Listen und Referenzen

Beitrag lesen

Hallo Kurt,

Bin mittlerweile schlauer, diese Sorte Arrays in Python müssen mit "Tupeln" [] und nicht mit "Listen" () realisiert werden, um Rückbezüge zu ermöglichen:

Du hast da was falsch verstanden: Tupel konstruiert man mit normalen Klammern - (1, 2) - Listen mit eckigen Klammern - [1, 2, 3] - anders als von Dir gesagt. Du nutzt also Listen.

Wo ist der Unterschied? Listen sind nun mal Listen, lang und veränderbar. Tupel dagegen sind fix, man kann sie nicht verändern. Idealerweise stellst Du Dir Tupel nicht als „nicht veränderbare Listen“ vor, sondern gehst mehr der mathematischen Sichtweise aus: Tupel sind dort aufzählbare Strukturen von Objekten, die aus Einzeldaten bestehen. Denk zum Beispiel an Positionen in einem zweidimensionalen Koordinatensystem (X- und Y-Wert) oder allgemeiner an Vektoren. Oder eben an alle möglichen Datensätze, ein Geburtsdatum wäre z.B. als ein Tupel aus Jahr, Monat und Tag (die Einzelwerte sind alles Zahlen) interpretierbar.

D.h. Tupel sind eher ein struktureller Datentyp für all die Fälle, für die man nicht unbedingt ein extra Objekt mit extra Klasse und Methoden und allem Pipapo erschaffen will, um das rumzureichen, sondern für all die kleinen Fälle. Und weil das so praktisch ist, ist das recht gut in die Sprache integriert. Links von der Zuweisung kann man z.B. Tuple Unpacking betreiben:

~~~python-shell

position = (5, 3)

>>> position
  (5, 3)
  >>> x, y = position
  >>> x
  5
  >>> y
  3

  
D.h. eigentlich wird das Tupel syntaktisch durch die Kommata bestimmt, nur zur syntaktischen Klarheit werden die Klammern drum gemacht:  
  
  ~~~python-shell

>>> x, y  

  (5, 3)

Genau das ist auch der Grund, weswegen das Rückgeben mehrerer Einzel-Werte aus Funktionen klappt. Die Funktion divmod() aus den Builtins von Python ist ja so definiert:

~~~python def divmod(x, y):
  return x // y, x % y

  
Effektiv wird da ein Tupel zurückgegeben; das Paar aus Divisor und Rest:  
  
  ~~~python-shell

>>> divisor, remainder = divmod(11, 3)  

  >>> print divisor, "Rest:", remainder  
  3 Rest: 2  
  >>> t = divmod(11, 3)  
  >>> type(t)  
  <type 'tuple'>  
  >>> print t  
  (3, 2)

Tupel haben noch andere Vorteile – die Unveränderbarkeit hat z.B. den praktischen Effekt, dass man sie als Key in einem Dictionary nehmen kann – aber in der Suche auf Deine Ringstrukturen bist Du da auf dem Holzweg.

...

Denn die sind wunderbar mit Listen möglich, wie Du es ja auch vorgeführt hast. Nur hier hast Du einen Fehler begangen, der Dich da auf den Weg brachte, es sei nicht möglich.

~~~python-shell

A = [1, 2]

>>> B = [0, A]
  >>> B
  [0, [1, 2]]

  
Anhand der ID – ganz einfach der Speicheradresse - kann man überprüfen, dass die Variable A und das zweite Element von B wirklich auf das gleiche Objekt zeigen; es referieren:  
  
  ~~~python-shell

>>> id(A)  

  420800  
  >>> id(B[1])  
  420800B  
  >>> A is B[1]  
  True  
  >>> A in B  
  True

Und jetzt kommt das, was Du in Deinem Posting von Donnerstag falsch gemacht hast:

>>> A = [4, 5]

Du änderst das Objekt, auf das A zeigt hier nämlich NICHT. Du erstellst ein NEUES Listen-Objekt – und das ist logischerweise ein anderes. Das vorherige Objekt wurde aber noch nicht von der Garbage Collection gefressen; schließlich existiert eine Referenz an zweiter Stelle in der Liste B darauf:

~~~python-shell

id(A)

419120
  >>> id(B[1])
  420800
  >>> A is B[1]
  False

  
Also schnell wieder rückgängig machen und A wieder die passende Referenz auf das richtige Objekt verpassen:  
  
  ~~~python-shell

>>> A = B[1]  

  >>> id(A)  
  420800

Die Ringstruktur kriegst Du hin, indem Du logischerweise das von A referenzierte Objekt selber änderst; mit den zugehörigen Methoden und Zuweisungen:

~~~python-shell

A[1] = B

>>> id(A)
  420800
  >>> id(B)
  420760
  >>> id(A[1])
  420760
  >>> id(B[1])
  420800

  
Also nix weiter als ein kleiner Verständnisfehler.  
  
  
Tim