Unvorhersehbare namedtuple-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
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Warum verhält sich namedtuple je nach Zusammenhang so verschieden?

Im folgenden Programmstück funktioniert alles wie vorgeschrieben und auch erwartet: der Abruf über Namen als auch über Indices, die wiederholte Wertzuweisung hingegen ist verboten:

Code: Alles auswählen

from collections import namedtuple

class Kl():
   def __init__(self):
      self.pkt = namedtuple('Punkt','x y')
      self.pkt = self.pkt(10,20)

##########################################
kl = Kl()

print(kl.pkt)
print(kl.pkt.y)
print(kl.pkt[1])

kl.pkt.y = 30  #AttributeError: can't set attribute
Hier aber funktioniert erstaunlicherweise (und angenehmerweise) sogar die wiederholte Wertzuweisung (für Tupel eigentlich verboten), aber dafür funktioniert der Abruf über Indices nicht mehr:

Code: Alles auswählen

from collections import namedtuple

class Kl():
   def __init__(self):
      self.pkt = namedtuple('Punkt','x y')
      (self.pkt.x, self.pkt.y) = (10,20)

##########################################
kl = Kl()

print(kl.pkt)
print(kl.pkt.y)
kl.pkt.y = 30
print(kl.pkt.y)

print(kl.pkt[1])  #TypeError: 'type' object is not subscriptable
Mit dieser zweiten Alternative wäre ich ja eigentlich ganz zufrieden; was mich aber trotzdem verärgert ist, dass sich Python so unvorhersehbar verhält. Gibt es dafür eine Erklärung?
BlackJack

@Goswin: Was hier sehr verwirrend ist, ist dass Du den gleichen Namen beziehungsweise das gleiche Attribut an sehr verschiedene Dinge bindest, nämlich sowohl an den *Datentyp* der mit `namedtuple()` erstellt wird, als auch an ein Exemplar. Und an den *Datentyp* kann man beliebige Attribute binden, aber natürlich nicht per Index zugreifen. Das ist irgendwie nicht weiter verwunderlich.

Warum bindest Du den *Datentyp* überhaupt an das `Kl`-Exemplar? Warum erstellst Du da jedes mal wenn ein `Kl`-Exemplar erstellt wird auch immer einen neuen Datentyp — der aber immer die gleichen Eigenschaften hat? Das macht keinen Sinn.

Der Quelltext sollte eigentlich so aussehen:

Code: Alles auswählen

Punkt = namedtuple('Punkt', 'x y')

class Klasse(object):
   def __init__(self):
      self.punkt = Punkt(10, 20)
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

BlackJack hat geschrieben:Warum erstellst Du da jedes mal wenn ein `Kl`-Exemplar erstellt wird auch immer einen neuen Datentyp — der aber immer die gleichen Eigenschaften hat?
Ich habe das Beispiel aus seinem Zusammenhang gelöst, um unerwünschte Nebeneffekte zu vermeiden. Die Exemplare der Klasse Kl welche ich benutze (heißt nicht wirklich Kl) haben noch viele andere Attribute und mehrere Methoden - und zusätzlich eben auch noch Attribute vom Typ 'Punkt'.


Meinst du vielleicht, dass die Anweisung

Code: Alles auswählen

Punkt = namedtuple('Punkt', 'x y')
außerhalb jeglicher Klassendefinition "mutterseelenallein" im Modul stehen soll? Ist das die vorgesehene Anwendungsweise? Vielleicht habe ich nur die angelernte (und möglicherweise "suboptimale") Gewohnheit, solches in objektorientierter Programmierung zu vermeiden.
BlackJack

@Goswin: Typen definiert man in der Regel auf Modulebene. Du schreibst doch normalerweise ``class``-Anweisungen auch nicht *in* Methoden sondern auf Modulebene. Und ein `namedtupel()`-Aufruf macht das selbe wie eine ``class``-Anweisung: Eine Klasse erstellen die eine Unterklasse von `tuple` ist. Du bringst hier Klassen, also Datentypen, und Exemplare von diesen Typen durcheinander. Beides sind in Python Werte, also Objekte, die man benennen, umbenennen, als Argumente übergeben, oder halt auch als Rückgabewert von einem Aufruf bekommen kann.

Code: Alles auswählen

In [7]: Punkt = collections.namedtuple('Punkt', 'x y')

In [8]: Punkt
Out[8]: __main__.Punkt

In [9]: punkt = Punkt(42, 23)

In [10]: punkt
Out[10]: Punkt(x=42, y=23)

In [11]: type(Punkt)
Out[11]: type

In [12]: type(punkt)
Out[12]: __main__.Punkt

In [13]: issubclass(Punkt, tuple)
Out[13]: True

In [14]: issubclass(punkt, tuple)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-e1050bd6d9a5> in <module>()
----> 1 issubclass(punkt, tuple)

TypeError: issubclass() arg 1 must be a class
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

BlackJack hat geschrieben:Typen definiert man in der Regel auf Modulebene. [...] Und ein `namedtupel()`-Aufruf macht das selbe wie eine ``class``-Anweisung.
Tja, das sehe ich jetzt auch so. Bisher hatte ich immer (da namedtupel eine Fuktion ist) das namedtuple wie eine Methode und nicht wie eine Klassendefinition behandelt. Alles klar, danke!
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

``namedtuple`` würde ich als eine Fabrikfunktion zum Erstellen einer von ``tuple`` abgeleiteten Klasse ansehen. Wichtig ist ja nur das Ergebnis (also der Rückgabewert), denn nur damit wird im weiteren Verlauf gearbeitet. Wie dies zustande kam, ist so gesehen zweitrangig.
Antworten