Klassen mit 'is' oder '==' vergleichen?

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.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Hallo,

per Definition vergleicht man mit 'is' die Identität zweier Objekte, mit '==' die Gleichheit zweier Werte... hmm...

Wenn ich nun prüfen möchte, ob eine Instanz von einer bestimmten Klasse stammt, dann vergleiche ich doch eher die Identität, oder?

Konkret geht es darum, dass ich vor dem Speichern eines Termins unter anderem den Typ einer Wiederholung ermitteln möchte ([115]):

Code: Alles auswählen

In [113]: RECURRENCES = {'D': recurrence.DailyRecurrence,
   .....:  'M': recurrence.MonthlyRecurrence,
   .....:  'W': recurrence.WeeklyRecurrence,
   .....:  'Y': recurrence.YearlyRecurrence}

In [114]: daily = RECURRENCES['D']('20120717', '20120717')

In [115]: [typ for typ, cls in RECURRENCES.iteritems() if
   .....: cls is daily.__class__][0]
Out[115]: 'D'
Also 'is' oder '=='??

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
deets

Warum so kompliziert, und nicht einfach isinstance(daily, cls)?
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Na das liegt doch auf der Hand: Weil ich doof bin! :mrgreen:
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Wobei es allerdings wichtig ist, dass ``isinstance`` auch True ergibt, falls du eine Kindklasse gegen die Elternklasse testest. In dem Fall musst du natürlich wissen, ob dies dem gewünschten Verhalten entspricht oder nicht.
Das Leben ist wie ein Tennisball.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@EyDu:
Meinst Du das?

Code: Alles auswählen

In [126]: isinstance(daily, recurrence.DailyRecurrence)
Out[126]: True

In [127]: isinstance(daily, recurrence.Recurrence)
Out[127]: True

In [128]: daily.__class__ is recurrence.DailyRecurrence
Out[128]: True

In [129]: daily.__class__ is recurrence.Recurrence
Out[129]: False
Dann ergäbe meine Frage ja doch noch Sinn... :wink: : 'is' oder '=='?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
lunar

@mutetella: Idealerweise solltest Du gar nicht gegen einen bestimmten Typen prüfen müssen. Ich würde sagen, Dein Entwurf ist fehlerhaft, wenn irgendein Teil X Deines Programms den Typ der Wiederholung wissen muss, sprich X enthält vom Wiederholungstypen abhängige Logik. Die gehört dann aber nicht in den Teil X, sondern in die jeweilige Wiederholungsklasse.

Wozu brauchst Du den genauen Typen der Wiederholung?
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@lunar:
Eine DailyRecurrence hat weniger Attribute als z. B. eine MonthlyRecurrence. Die jeweiligen Recurrence-Attribute speichere ich zusammen mit einem Vermerk über ihren Ursprung (daily, monthly etc...) ab, um beim Einlesen daraus wieder die korrekte Recurrence-Klasse zu erstellen.
Alternativ könnte ich auch jeder Recurrence-Klasse die gleichen Attribute geben und je nach Typ halt dann nicht alle verwenden. Gefällt mir aber gar nicht, quasi 'Dummy'-Attribute zu setzen, nur um ein immer gleiches '__init__()' zu haben.

Der Irrsinn, über eine LC aus einem dictionary anhand der Werte den Schlüssel abzufragen, um damit den Typ der Wiederholung zu ermitteln... na ja, das war halt mal wieder so ein typischer erster Versuch von mir, das Problem zu lösen... *grins*

Ok, inzwischen mach' ich es so:

Code: Alles auswählen

In [136]: daily = RECURRENCES['D']('20120717', '20120717')
In [137]: daily.typ = 'D'
Oder sollte ich den Typ gleich in der Klasse vermerken? Ich finde es halt seltsam, einer Klasse, die sich 'DailyRecurrence' nennt ein Attribut zu verpassen, das darauf hinweist, dass es sich um 'Daily..' handelt...

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
lunar

@mutetella: Wenn es um Serialisierung geht: Implementiere in den Recurrence-Klassen das Pickle-Protokoll, um die Attribute der Klassen in Form eines Wörterbuchs zu erhalten, und speichere den qualifizierten Pfad der Recurrence-Klassen. Dann kannst Du dynamisch Exemplare der jeweiligen Klasse erzeugen, und mittels des pickle-Protokolls mit den Werten befüllen, ohne dass Du irgendwo Typen vergleichen musst.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@lunar:
Ähnlich gehe ich bereits vor, allerdings verwende ich das json-Format, da ich die Termindaten in einer lesbaren Form speichern möchte.
Meine Appointment-Klassen besitzen eine 'get_raw_attributes()'-Methode, über die ich dann die Attribute der Appointment-Klasse und gegebenenfalls der Recurrence-Klasse erhalte. Diese Attribute speichere ich im json-Format und erstelle umgekehrt daraus dann wieder die Klassen.
Pickle hätte den Vorteil, dass ich mir die 'get_raw_attributes()'-Methode sparen könnte. Aber ich möchte eben eine lesbare Form.

Könnte aber auch sein, dass ich nicht wirklich verstanden habe, was Du meinst:
lunar hat geschrieben:... um die Attribute der Klassen in Form eines Wörterbuchs zu erhalten, und speichere den qualifizierten Pfad der Recurrence-Klassen.
Pickle liefert mir doch keine Attribute in Form eines Wörterbuchs. Und was meinst Du mit qualifiziertem Pfad der Recurrence-Klassen? Dass die gepickelten Daten nur in der herkömmlichen Umgebung (Modul mit den dazugehörigen Klassen) wieder hergestellt werden können oder was anderes?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
lunar

@mutetella: "pickle" selbst habe ich gar nicht erwähnt, sondern nur das "pickle"-Protokoll. Damit erhältst Du die Attribute eines Objekts als Wörterbuch. Der „qualifizierte Pfad“ (sollte eigentlich „qualifizierter Name“ heißen) einer Klasse ist der Name dieser Klasse inklusive ihres Moduls. Damit kannst Du Klassen eindeutig identifizieren. Darauf aufbauend kannst Du generische Serialisierung von und nach JSON implementieren (ungetestet), die im Wesentlichen genauso arbeitet wie pickle.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@lunar:
Ich muss das jetzt erstmal Zeile für Zeile durchkauen, Doku lesen und ausprobieren, um auch wirklich alles zu verstehen.
Und weil das wahrscheinlich ein kleines Weilchen dauern wird, wollte ich jetzt auf die Schnelle 'Vielen Dank!' für Deine Hilfe sagen...
Mein Kalender wird dadurch zwar auch nicht schneller fertig :lol: aber dafür wieder ein bischen besser!

mutetella


P.S. Eigentlich wollte ich ja noch fragen, welche Herangehensweise zum Vergleich von Klassen denn wohl performanter wäre... Aber das hat sich ja zum Glück erledigt! :P
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
lunar

@mutetella: Ich glaube nicht, dass es überhaupt einen Unterschied gibt, da die „Klassenklasse“ ;), sprich "type", den Gleichheitsoperator wahrscheinlich nicht überlädt, so dass "==" bei Exemplaren von "type" (= Klassen) ebenfalls auf Objektidentität prüft, und damit dasselbe tut wie "is". Und selbst wenn es einen Unterschied gäbe, wäre der mit Sicherheit zu gering als das er messbare wäre, geschweige denn für den Benutzer spürbar :)
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Ok, ich bin auf einem guten Weg... :mrgreen:

Hier bräuchte ich noch Hilfe:

default()
1. a) Wann verfügt eine Klasse denn über '__getstate__()'? Doch nur, wenn ich sie implementiere, oder?
b) Und wenn ich das mache doch nur, weil ich darüber bestimmen möchte, welche Attribute serialisiert werden sollen? Der Rückgabewert muss dabei ein Attribute-dict sein. Ist das so richtig?
c) Die Methode wird z. B. von 'pickle.dump()' aufgerufen, falls vorhanden. Andernfalls wird '__dict__' verwendet. Korrekt?
d) Wenn ich Deine 'default()'-Funktion ausschließlich zum Serialisieren meiner eigenen Klassen verwende, kann ich auf '__getstate__()'-Prüfung verzichten und gleich '__dict__' verwenden?
2. 'dict(obj.__dict__)' statt 'obj.__dict__', damit keine Seiteneffekte auftreten, oder?

deserialize_object()
1. Falls vorhanden, geschieht in der '__setstate__()'-Methode eigentlich nichts weiter, als anhand des dict 'o' der Klasseninstanz Attribute zu verpassen, richtig?
2. Dass eine Instanz nicht über die '__init__()'-Methode sondern über '__new__()' und dem 'manuellen' setzen der Attribute wiederhergestellt wird liegt daran, dass dadurch auch Attribute erfasst werden, die nicht explizit in der '__init__()'-Methode berücksichtigt sind. Stimmt das so?
3. 'importlib.import_module()' ab 2.7 ist equivalent zu '__import__()' aus <2.7? Wenn ja, weshalb existiert dann '__import__()' noch bis Py3 hinein?


Wow... so ein Kalender ist echt 'ne heiße Sache... *grins*

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
lunar

default()
1a) Richtig.
b) Richtig.
c) Richtig.
d) Richtig. "__getstate__()" ist keine Notwendigkeit. Du könntest die Methode auch "gimme_my_supercool_attributes_dude()" nennen, oder eben direkt auf "__dict__" zurückgreifen. Im Beispiel verwende ich diese Methode nur zwecks Kompatibilität zu pickle.
2) Richtig. Insbesondere wird durch eine frühzeitige Kopie das versehentliche Verändern der Attribute des Objekts verhindert, was schwer zu findende Fehler zur Folge hätte.

deserialize_object()
1. Richtig.
2. Zwar richtig, ist aber nicht der Grund, warum ich "__new__()" direkt aufrufe. Schließlich könnte ich das Exemplar auch „konventionell“ erzeugen, und anschließend alle Attribute manuell setzen, und hätte somit auch alle Attribute entsprechend des deserialisierten Zustands gesetzt. Vielmehr rufe ich "__new__()" explizit auf, damit "__init__()" gar nicht erst aufgerufen wird. Auch das vordergründig, weil "pickle" sich auch so verhält. Der Grund dafür ist, dass eine Klasse so sicher sein kann, dass "__init__()" nur für neue Exemplare aufgerufen wird, nicht aber für deserialisierte Objekte. Das entspricht der Idee der Serialisierung, Objekte persistent zu speichern und anschließend dasselbe Objekt wieder laden zu können, und eben nicht lediglich ein neues Exemplar mit den alten Werten zu versehen.
3. Falsch. "importlib.import_module()" importieren zwar beide das angegebene Modul, haben aber unterschiedliche Rückgabewerte:

Code: Alles auswählen

>>> import importlib
>>> module = 'xml.etree.ElementTree'
>>> __import__(module)
<module 'xml' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xml/__init__.pyc'>
>>> importlib.import_module(module)
<module 'xml.etree.ElementTree' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xml/etree/ElementTree.pyc'>
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Dass unter <2.7 das 'importlib'-Modul nicht zu den builtins gehört, ist eine blöde Sache. Zuerst dachte ich, das spielt keine Rolle, ob

Code: Alles auswählen

getattr('entry', 'recurrences.DailyRecurrence')
oder aber

Code: Alles auswählen

getattr('entry.recurrences', 'DailyRecurrence')
Falsch gedacht und ich ahne auch warum... :(
Folgendes würde funktionieren:

Code: Alles auswählen

PY_VERSION = sys.version_info
def get_cls(obj):
    typename = obj.pop('__type')
    modulename, classname = typename.rsplit('.', 1)
    if PY_VERSION < (2, 7):
        module = __import__(modulename, fromlist=[classname])
    else:
        import importlib
        module = importlib.import_module(modulename)
    return getattr(module, classname)

def deserialize_object(o):
    if '__type' in o:
        cls = get_cls(o)
        obj = cls.__new__(cls)
        if hasattr(obj, '__setstate__'):
            obj.__setstate__(o)
        else:
            obj.__dict__.update(o)
        return obj
    else:
        return o
Hab' ich was übersehen, gibt es einen einfacheren Weg, das ganze <2.7-kompatibel zu machen?

Als Beispiel 'importlib.import_module()' vs. '__import__()' hast Du das 'xml.etree.ElementTree'-Modul genannt.
Ich hab' zum Experimentieren die 'ElementTree'-Klasse aus dem Modul verwendet. Dabei ist mir aufgefallen, dass eine Instanz dieser Klasse in der 'default()'-Methode durchfallen würde, weil das 'xml.etree.ElementTree'-Modul als builtin deklariert ist. Dass builtins ausgesiebt werden (müssen) leuchtet mir ein, aber gilt das wirklich für alle?
Gibt es noch eine andere Möglichkeit, builtins vom Serialisieren auszunehmen, evtl. so:

Code: Alles auswählen

if type(obj) in [str, int, float, list, tuple, dict]:
    ...
Scheint mir aber auch doof zu sein... :roll:

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
lunar

@mutetella: Kopiere doch einfach den Quelltext von "importlib" und verwende diesen, wenn der Import von "importlib" aus der Standardbibliothek fehlschlägt.

Wie kommst Du darauf, dass "xml.etree.ElementTree" „builtin“ wäre?!
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

lunar hat geschrieben:Wie kommst Du darauf, dass "xml.etree.ElementTree" „builtin“ wäre?!

Code: Alles auswählen

>>> module = __import__('xml.etree.ElementTree')
>>> type(module).__module__
'__builtin__'

Code: Alles auswählen

>>> import importlib
>>> module = importlib.import_module('xml.etree.ElementTree')
>>> type(module).__module__
'builtins'
Darum. Wobei dann auch zwecks Kompatibilität die builtin-Prüfung so aussehen sollte:

Code: Alles auswählen

if 'builtin' in objtype.__module__:
    ...
Warum musste denn '__module__' geändert werden? Ist doch blöd, oder?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
deets

Dir ist schon klar das der *TYP* eines Moduls was anderes ist als das Modul selbst? Dass der Typ "module" aus builtins ist, ist ja nicht weiter verwunderlich.
lunar

@mutetella: Du verwechselst das Modul selbst mit dem Typen eines Moduls. "type(module)" gibt letzteres zurück, also quasi die „Modul-Klasse“ von Python:

Code: Alles auswählen

>>> import xml.etree.ElementTree
>>> xml.etree.ElementTree
<module 'xml.etree.ElementTree' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xml/etree/ElementTree.pyc'>
>>> type(xml.etree.ElementTree)
<type 'module'>
>>> import types
>>> type(xml.etree.ElementTree) is types.ModuleType
True
Jedes Modul hat diesen Typen, sowohl solche aus der Standardbibliothek als auch Drittmodule wie "docutils":

Code: Alles auswählen

>>> import docutils
>>> type(docutils)
<type 'module'>
>>> type(xml.etree.ElementTree) is type(docutils)
True
Jedes Mal, wenn ein neues Modul importiert wird, erzeugt Python intern ein Exemplar dieser „Modul-Klasse“, und führt – vereinfacht gesagt – den Quelltext des Moduls im Namensraum dieses Exemplars aus. Offensichtlich muss diese Modul-Klasse selbst "builtin" sein, denn sonst müsste Python ja beim Importieren eines Moduls erst das Modul mit der Modul-Klasse importieren, wozu aber wieder die Modul-Klasse nötig wäre, die aber wiederum importiert werden müssen… ein klassisches Henne-Ei-Problem.

Daraus folgt aber nicht, dass dieses neue "Modul-Exemplar" selbst "builtin" ist. Im Gegenteil, Module sind nie builtin, da ja gerade der Zweck von Modulen darin besteht, dass sie später nachgeladen werden können, und nicht automatisch im Interpreter vorhanden sind.

Ebenfalls folgt daraus nicht, dass die Attribute eines Moduls, also die darin enthaltenen Klassen, "builtin" sind:

Code: Alles auswählen

xml.etree.ElementTree.ElementTree.__module__
'xml.etree.ElementTree'
Lediglich der Typ der Klasse "xml.etree.ElementTree.ElementTree", also quasi die „Klassen-Klasse“, ist dann wieder "builtin":

Code: Alles auswählen

>>> type(xml.etree.ElementTree.ElementTree)
<type 'type'>
>>> type(xml.etree.ElementTree.ElementTree).__module__
'__builtin__'
Aber auch hier gilt wieder, dass jede Klasse in Python, auch solche, die Du beispielsweise selbst erzeugst, diesen Typ haben:

Code: Alles auswählen

>>> class Spam(object):
...     pass
... 
>>> type(Spam)
<type 'type'>
>>> type(xml.etree.ElementTree.ElementTree) is type(Spam)
True
Im Allgemeinen ist in Python eben alles ein Objekt, mithin auch Klassen und Module. Es gibt also eine Modul-Klasse und eine Klassen-Klasse, und man muss immer unterscheiden zwischen dem Objekt und seiner Klasse, also beispielsweise einem Modul und der Modul-Klasse und einer Klasse und der Klassen-Klasse.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Ach Du sch.... :oops: Da hab' ich mal wieder viel zu oberflächlich gedacht...

Bleibt also wohl nur noch mein Einwand bzgl. der 'builtin'-Prüfung übrig, oder?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Antworten