Seite 1 von 1
Python2 - Variablename entscheidet über AttributeError - hä?
Verfasst: Dienstag 6. Dezember 2011, 21:18
von Astorek
Hi @ all,
nach längerer Python-Abstinenz wollte ich mal wieder ganz langsam anfangen und stehe hier vor einem Problem, bei dem ich das Gefühl habe, Python will mich verarschen - oder ich habe zufällig irgendeinen Bug gefunden. Anders kann ich mir das Verhalten von Python nicht erklären.
Ich habe hier folgenden Code rumliegen:
Code: Alles auswählen
class Karte:
anzahl = 0
def __init__(self):
Karte.anzahl += 1
def __del__(self):
Karte.anzahl -= 1
def __str__(self):
return str(Karte.anzahl)
card = Karte()
print card
Wenn ich diesen Code in eine Datei schreibe und ihn mit Python2 ausführe, erhalte ich die Fehlermeldung:
Code: Alles auswählen
Exception AttributeError: "'NoneType' object has no attribute 'anzahl'" in <bound method Karte.__del__ of <__main__.Karte instance at 0x7f75d282e5f0>> ignored
Wenn ich hingegen
die letzten 2 Zeilen ändere, und zwar stattdessen in
Gibt es garkeine(!) Fehlermeldung.
Was zum Teufel ist da los? Wie kann es sein, dass der Variablenname "card" diesen seltsamen Fehler wirft, nicht aber der Variablenname "test2"? Nur durch Zufall habe ich herausgefunden, dass "test2" keinen Fehler wirft - wenn die Variable stattdessen z.B. "bliblablubb1" nehme, bekomme ich wieder den AttributeError, aber bei "bliblablubb15" (eine 5 hintendran) hingegen wieder nicht...
Auch interessant: Wenn ich die Methode "__del__" aus der Klasse entferne, erhalte ich diesen Fehler nicht - unabhängig vom Variablennamen. Leider bin ich auf die "__del__"-Methode angewiesen, da ich jederzeit sehen muss, wie viele Variablen die Klasse bereits nutz(t)en...
Tjoar... Frage: Bekommt ihr zufällig denselben Fehler, wenn ihr diesen Schnipsel-Code ausführt? Falls ja: Woran könnte es liegen? Ich habe leider keinen Zweit-PC oder -Betriebssystem zur Hand, auf dem ich das gerade testen könnte...
Schonmal im Voraus Danke für jede Hilfe!

Verwendete Python-Version ist 2.7.2 unter Archlinux 64 bit; Python wurde direkt als Paket vom Distributor installiert...
EDIT: Es wäre auch super, wenn sich jemand melden würde, bei dem der Fehler NICHT auftritt. Dann weiß ich, dass mit meinem Computer irgendwas nicht stimmt^^.
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Dienstag 6. Dezember 2011, 21:39
von deets
Das Verhalten ist bei mir gleich, und ein Beleg dafuer, dass man __del__ nicht benutzen sollte.
Funktionieren tut es wenn du es so programmierst:
Code: Alles auswählen
class Karte:
anzahl = 0
def __init__(self):
self.__class__.anzahl += 1
def __del__(self):
self.__class__.anzahl -= 1
def __str__(self):
return str(self.__class__.anzahl)
card = Karte()
print card
Was passiert ist recht simpel - Python versucht, das __main__-Modul zu entfernen. Dabei setzt es dann alle darin bekannten Namen auf "None", um deren Objekte garbage collecten zu lassen. Und dein Namensgebung hat durch Zufall nun einen Fall erzeugt, wo einmal zuerst Karte, und danach card, und beim anderen mal zuerst test2, und dann Karte auf None gesetzt werden. Je nach dem was zuerst passiert, kommt es zu dem Fehler.
Die self.__class__-Referenz hingegen hat bestand.
Ich wuerde mal sagen, du kommst auch ohne __del__ aus, in all meinen Jahren als Python-Programmierer habe ich das eigentlich nie wirklich gebraucht. Was ist denn dein Anwendungsfall?
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Dienstag 6. Dezember 2011, 21:57
von DasIch
__del__ ist kein Destruktor. Es ist nicht definiert wann oder ob die Methode aufgerufen wird. Instanzen, von Klassen die __del__ definieren, werden vom Garbage Collector der CPython für zirkuläre Referenzen verwendet nicht aufgeräumt und können so zu Memory Leaks führen.
Das Problem in deinem Fall, ist dass bei dem Aufruf von __del__ das Attribut `anzahl` schon nicht mehr existiert, dies führt zu dem AttributeError.
Wie deets gezeigt hat lässt sich das beheben wenn man ein Attribut mit einem Unterstrich prefixt, in diesem Fall wird ein Attribut erst nach der Ausführung von __del__ - sofern sie überhaupt statt findet - aufgeräumt wird.
Unabhängig davon ist dein Design allerdings prinzipiell problematisch, eine Klasse sollte eine Instanz ausschliesslich beschreiben, mit Instanzen sollte sie nicht agieren und nach ihrer Definition sollte man Veränderungen prinzipiell vermeiden.
Wenn du wissen willst wieviele Exemplare einer Klasse existieren, verwende eine geeignete Datenstruktur um diese zu erfassen und zu zählen.
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Dienstag 6. Dezember 2011, 22:00
von Astorek
Danke für die Aufklärung deets

. Leuchtet alles ein, was du gesagt hast... Mir war nicht bewusst, dass die __del__-Methode solche zufällige Konstellationen erzeugen konnte. Das werde ich mir merken^^.
deets hat geschrieben:Ich wuerde mal sagen, du kommst auch ohne __del__ aus, in all meinen Jahren als Python-Programmierer habe ich das eigentlich nie wirklich gebraucht. Was ist denn dein Anwendungsfall?
Offen gesagt, war das nur mehr oder weniger eine Spielerei, die ich aus dem Tutorial
A Byte of Python aufgeschnappt habe. Ich wollte eine Art "Universelle Kartenspiel-Engine" schreiben, bei der es auch die Möglichkeit gibt, Karten direkt "verschwinden" zu lassen - und das wollte ich mir eigentlich ganz einfach mit der __del__-Methode machen^^. Ich finde da schon was, wie ich das anders lösen werde
EDIT:
@DasIch: Danke für die Aufklärung... Ich denke, ich werde mir noch anderweitig ein paar Tutorials ansehen, da ich, was Klassen angeht, noch relativ unerfahren bin^^...
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Dienstag 6. Dezember 2011, 22:28
von deets
DasIch hat geschrieben:
Wie deets gezeigt hat lässt sich das beheben wenn man ein Attribut mit einem Unterstrich prefixt, in diesem Fall wird ein Attribut erst nach der Ausführung von __del__ - sofern sie überhaupt statt findet - aufgeräumt wird.
Habe ich? Was hat denn ein Unterstrich vor einem Attribut damit zu tun? Was ich gezeigt habe ist, dass eine Instanz immer eine Referenz auf ihre Klasse hat. Das hat nix mit dem Attribut zu tun. Was veraendert wurde ist der globale Namensraum, auf den sich "Karte" bezieht.
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Dienstag 6. Dezember 2011, 22:29
von BlackJack
@Astorek: Was meinst Du mit „verschwinden lassen“? Die `__del__()`-Methode lässt nichts verschwinden, sondern wird (vielleicht) aufgerufen wenn das Exemplar von selbst „verschwindet“, also wenn das Objekt vom Programm aus nicht mehr erreichbar ist. Zu dem Zeitpunkt ist das Objekt für das Programm also schon weg — da ist kein heran kommen mehr. Und das ist unabhängig davon ob die Methode aufgerufen wird oder nicht.
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Dienstag 6. Dezember 2011, 23:53
von Leonidas
@BlackJack: ich gehe davon aus, dass der OP eine Art Kartenspiel programmiert und in der __del__ Methode die Karte aus der UI rausnehmen will. Und das will er dann mit ``del karte`` erreichen, so dass die Karte dann aus der GUI verschwindet.
Ist aber keine gute Lösung, eigentlich nichtmal eine "Lösung", weil es nicht funktioniert.
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Mittwoch 7. Dezember 2011, 01:03
von Astorek
Leonidas hat recht damit, was ich mit der __del__Methode ursprünglich erreichen wollte. Der Code, den ich hier reingestellt habe, habe ich zuvor bis auf die wichtigsten Stellen gekürzt.
---
Bei der Gelegenheit würde ich ehrlichgesagt gerne ein paar Meinungen über "meine Methode" beim Erstellen eines Kartenspieles einholen^^. Wie gesagt, ich habe noch wenig praktische Erfahrung beim Programmieren in Klassen und bin mir nicht sicher, ob die Verwendung von Klassen so gedacht ist, wie ich sie mir vorgestellt hätte.
Bis jetzt hätte ich mir mein Kartenspiel so vorgestellt:
- Eine Klasse "Karte" nimmt sämtliche Werte auf, die eine einzelne(!) Karte hat, z.B. Farbe und Wertigkeit.
- Die ganzen Karten werden innerhalb der main() in einer List gespeichert, nach dem Motto:
Code: Alles auswählen
i = 0
for j in "yellow", "magenta", "green", "orange", "yellow":
for k in range(12):
cards[i] = Karte(color = j, number = k + 1)
i += 1
Daneben gäbe es noch die Klassen "Kartendeck" (symbolisiert quasi die Karten, die man während des Spiels abheben kann), "Ablage" (die Karten, die man ausgespielt hat) und "Spieler". Der Transfer von Karten würde immer innerhalb der main() und mit Verwendung der List "cards" stattfinden... Ich hätte mir dabei gedacht, dass ich mir dabei den Transfer "realistisch" vorstellen kann: Karten werden vom Kartendeck genommen, bekommt Spieler XY oder landet in der Ablage etc. , Bei Spielende legen alle Spieler ihre Karten wieder zum Kartendeck dazu etc...
Ist das eine sinnvolle Verwendung von Klassen, bzw. gibts in der Art des Gedankengangs Optimierungen? Oder habe ich den Nutzen von Klassen ganz falsch verstanden?^^
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Mittwoch 7. Dezember 2011, 01:07
von Hyperion
Du solltest Dir mal ``enumerate`` angucken. Indizes für Iterables erzeugt man am besten damit. Könntest Du zweimal einsetzen, so weit ich das auf die schnelle gesehen habe.
Edit: Das erste kannst Du Dir auch schenken. `list.append` existiert ja.
So, hier mal die ganz elegante Variante:
Code: Alles auswählen
from itertools import product
class Card:
def __init__(self, color, number):
self.color, self.number = color, number
cards = [Card(*item) for item in product(("yellow", "magenta", "green"), range(1, 13))]
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Mittwoch 7. Dezember 2011, 07:56
von snafu
DasIch hat geschrieben:Wie deets gezeigt hat lässt sich das beheben wenn man ein Attribut mit einem Unterstrich prefixt, in diesem Fall wird ein Attribut erst nach der Ausführung von __del__ - sofern sie überhaupt statt findet - aufgeräumt wird.
Ist das dokumentiert oder geraten? Höre das nämlich in Zusammenhang mit `__del__` zum ersten Mal. Aber man lernt ja nie aus...
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Mittwoch 7. Dezember 2011, 08:39
von deets
@snafu
Ich habe das nicht gezeigt... und ich halte es auch fuer falsch. Python hat ja gar kein Attribut entfernt/auf None gesetzt. Sondern modul-globale Namen entfernt.
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Mittwoch 7. Dezember 2011, 15:59
von snafu
@deets: Das hatte ich schon verstanden. Du hattest dich ja danach auch von der dir zugeschriebenen Aussage distanziert. Die Verallgemeinerung hatte ja DasIch gebracht. Es mag ja sein, dass (C)Python eine bestimmte Reihenfolge bei der Abarbeitung bzw insbesondere beim Abräumen der verschiedenen Attribut"arten" geht. Ich wüsste aber halt nicht, dass diese Vorgehensweise "offiziell" wäre (sofern sie denn überhaupt existiert). Ich mein, im Prinzip kann man das ja ganz einfach testen. Ich fragte mich halt nur, wie DasIch zu seiner Feststellung gekommen ist.
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Donnerstag 8. Dezember 2011, 01:50
von derdon
Hyperion hat geschrieben:So, hier mal die ganz elegante Variante:
Code: Alles auswählen
from itertools import product
class Card:
def __init__(self, color, number):
self.color, self.number = color, number
cards = [Card(*item) for item in product(("yellow", "magenta", "green"), range(1, 13))]
Ganz elegant heißt mit collections.namedtuple

Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Donnerstag 8. Dezember 2011, 07:27
von Darii
snafu hat geschrieben:@deets: Das hatte ich schon verstanden. Du hattest dich ja danach auch von der dir zugeschriebenen Aussage distanziert. Die Verallgemeinerung hatte ja DasIch gebracht. Es mag ja sein, dass (C)Python eine bestimmte Reihenfolge bei der Abarbeitung bzw insbesondere beim Abräumen der verschiedenen Attribut"arten" geht.
Nein das ist unsinn das tut Python nicht, deswegen steht das auch nicht in der Doku. Zwar wird Python sicherlich Referenzen in der Reihenfolge in der sie im betreffenden dict stehen löschen, deswegen klappt das Umbenennen im Beispiel auch. Aber Umbennen von Variablen ist keine richtige Lösung weil man sich darauf nicht verlassen darf. Die Lösung ist keine Referenzen außer self in __del__ zu verwenden.
Und die eigentlich Lösung ist __del__ nur zu verwenden, wenn man nicht von Python verwaltete Ressourcen freigeben muss. Was aber auf bedeutet, dass man ggf. alle Funktionen die man dafür braucht, in der Klasse zwischenspeichern muss.
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Donnerstag 8. Dezember 2011, 15:06
von Hyperion
derdon hat geschrieben:
Ganz elegant heißt mit collections.namedtuple

Nee nee, "ganz" ist schon meine... "super" elegant wäre Deine Lösung mit ``namedtuple``, die Du uns ja gleich zeigen wirst

Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Donnerstag 8. Dezember 2011, 17:49
von lunar
@snafu: CPython garantiert, dass globale Namen, welche mit einem Unterstrich beginnen,
vor globalen Namen gelöscht werden, die keinen führenden Unterstrich haben. Aus der
Dokumentation:
Warning: […]Starting with version 1.5, Python guarantees that globals whose name begins with a single underscore are deleted from their module before other globals are deleted; […]
In diesem Punkt hat der Unterstrich im Bezug auf "__del__()" also tatsächlich eine besondere Bedeutung. Ich glaube, DasIch hat da einfach was verwechselt, oder falsch in Erinnerung behalten.
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Freitag 9. Dezember 2011, 01:07
von derdon
Hyperion hat geschrieben:derdon hat geschrieben:
Ganz elegant heißt mit collections.namedtuple

Nee nee, "ganz" ist schon meine... "super" elegant wäre Deine Lösung mit ``namedtuple``, die Du uns ja gleich zeigen wirst

Einfach nur die Definition von Card anpassen:
Code: Alles auswählen
>>> from collections import namedtuple
>>> Card = namedtuple('Card', 'color number')
Re: Python2 - Variablename entscheidet über AttributeError -
Verfasst: Freitag 9. Dezember 2011, 01:18
von Hyperion
derdon hat geschrieben:
Einfach nur die Definition von Card anpassen:
Code: Alles auswählen
>>> from collections import namedtuple
>>> Card = namedtuple('Card', 'color number')
Ja, ganz nett. Aber darum ging es mir ja nicht - wie er seinen Datentypen letztlich aufbaut ist Sache des OP. Je nach Anwendungsfall mag eine eigene Klasse auch sinnvoller sein als Deine Idee.