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.
lunar

@mutetella: Welcher Einwand? Ob wirklich alle builtins übersprungen werden müssen?

Nein, genau genommen ist das sogar falsch, denn "json" versteht gar nicht alle builtins ("set" beispielsweise nicht). Eigentlich darf man nur die Typen überspringen, die "json" laut Dokumentation von Haus aus versteht. Dazu war ich allerdings zu faul :)

Ich habe das Beispiel aktualisiert. Ganz korrekt ist es immer noch nicht, da beispielsweise "set" nicht richtig serialisiert wird, aber das überlasse ich Dir zur Übung :)
BlackJack

Wenn ich mal Korinthen kacken darf: Natürlich kann es auch „built in”-Module geben:

Code: Alles auswählen

In [227]: sys.builtin_module_names
Out[227]: 
('__builtin__',
 '__main__',
 '_ast',
 '_bisect',
 '_codecs',
 '_collections',
 '_functools',
 '_hashlib',
 '_locale',
 '_random',
 '_socket',
 '_sre',
 '_ssl',
 '_struct',
 '_symtable',
 '_warnings',
 '_weakref',
 'array',
 'binascii',
 'cPickle',
 'cStringIO',
 'errno',
 'exceptions',
 'fcntl',
 'gc',
 'grp',
 'imp',
 'itertools',
 'marshal',
 'math',
 'operator',
 'posix',
 'pwd',
 'select',
 'signal',
 'spwd',
 'strop',
 'sys',
 'syslog',
 'thread',
 'time',
 'unicodedata',
 'xxsubtype',
 'zipimport',
 'zlib')

In [228]: sys
Out[228]: <module 'sys' (built-in)>
Die Module sind fest in den Interpreter einkompiliert. Auswahl und Anzahl kann von der Python-Version und den Optionen beim Übersetzen des Interpreters abhängen.
lunar

@BlackJack: Ich würde das als Implementierungsdetail bezeichnen. Für den Programmierer verhalten sich diese Module – einige Ausnahmen beiseite gelassen – wie normale Module auch, vor allem muss man sie importieren, bevor man sie nutzen kann.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Ok, hab' mich mal herangewagt...:

Code: Alles auswählen

try:
    #Python < 3.0
    BUILTINS = __builtin__
except NameError:
    #Python >= 3.0
    BUILTINS = builtins

# Supported Json types as by
# http://docs.python.org/library/json.html#json.JSONEncoder
JSON_STANDARD_TYPES = [
    dict,
    list, tuple,
    str, unicode,
    int, long, float,
    bool,
    type(None)
]

JSON_NONSTANDARD_TYPES = [
    set
]

TO_JSON = {
    'set': list
}

def deserialize_object(o):
    o = dict(o)
    if '__type' in o:
        typename = o.pop('__type')
        modulename, classname = typename.rsplit('.', 1)
        #TODO: load module dynamically depending on python-version
        #module = importlib.import_module(modulename)
        module = __import__(modulename, fromlist=[classname])
        cls = getattr(module, classname)
        obj = cls.__new__(cls)
        if hasattr(obj, '__setstate__'):
            obj.__setstate__(o)
        else:
            for attr, value in o.iteritems():
                _type, value = value
                obj.__dict__[attr] = getattr(BUILTINS, _type)(value)
        return obj
    else:
        return o

def convert2json(obj):
    objname = type(obj).__name__
    return TO_JSON[objname](obj)

def default(obj):
    objtype = type(obj)
    if objtype in JSON_STANDARD_TYPES:
        # skip standard JSON types
        return objtype.__name__, obj
    elif objtype in JSON_NONSTANDARD_TYPES:
        return objtype.__name__, convert2json(obj)
    else:
        typename = objtype.__module__ + '.' + objtype.__name__
        state = {'__type': typename}
        if hasattr(obj, '__getstate__'):
            attrs = obj.__getstate__()
        else:
            attrs = dict(obj.__dict__)
        for attr, value in attrs.iteritems():
            state[attr] = default(value)
        return state

Was haltet ihr davon?

mutetella

P. S.: Den 'importlib'-Quellcode hab' ich jetzt der Einfachheit halber noch nicht übernommen...
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Mein erster Versuch ist nix gudd!! Das mit 'convert2json()' und 'TO_JSON' war zwar gut gemeint, gaukelt aber eigentlich nur vor, dass sich hier "einfach so" weitere typen zufügen lassen. Geht aber nicht "einfach so". Und das ist doof!

Ich will das noch ändern, muss jetzt aber erst mal früüühhhhstücken!

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Puhhh.... hier mein zweiter Entwurf:

Code: Alles auswählen

JSON_STANDARD_TYPES = [
    dict,
    list, tuple,
    str, unicode,
    int, long, float,
    bool,
    type(None)
]

def deserialize_object(o):
    if o is not None and '__type' in o:
        typename = o.pop('__type')
        value = o.get('__value', o.iteritems())
        modulename, classname = typename.rsplit('.', 1)
        #TODO: load module dynamically depending on python-version
        #module = importlib.import_module(modulename)
        module = __import__(modulename, fromlist=[classname])
        cls = getattr(module, classname, None)
        if cls in JSON_STANDARD_TYPES or cls == set:
            return cls(value)
        elif classname == 'datetime':
            return cls.strptime(value, '%Y%m%d%H%M')
        elif classname == 'date':
            return cls(*value)
        elif cls is None:
            return
        else:
            cls = cls.__new__(cls)
            if hasattr(cls, '__setstate__'):
                cls.__setstate__(o)
            else:
                for attr, value in value:
                    cls.__dict__[attr] = deserialize_object(value)
            return cls
    else:
        return o

def default(obj):
    objtype = type(obj)
    objname = objtype.__name__
    objmodule = objtype.__module__
    typename = objmodule + '.' + objname
    state = {'__type': typename}
    if objtype in JSON_STANDARD_TYPES:
        state['__value'] = obj
    elif objtype == set:
        state['__value'] = list(obj)
    elif objname == 'date':
        state['__value'] = (obj.year, obj.month, obj.day)
    elif objname == 'datetime':
        state['__value'] = obj.strftime('%Y%m%d%H%M')
    else:
        if hasattr(obj, '__getstate__'):
            attrs = obj.__getstate__()
        else:
            attrs = dict(obj.__dict__)
        for attr, value in attrs.iteritems():
            state[attr] = default(value)
    return state

Nicht ganz so glücklich bin ich darüber, dass mit dieser Lösung manche Objekte 2 mal an 'deserialize_object()' geschickt werden:
Einmal, wenn sie tatsächlich deserialisiert werden und evtl. nochmal, wenn sie innerhalb der 'for'-Schleife einer Klasse als Attribut hinzugefügt werden. Nachdem ich unter Python < 2.7 keinen Einfluss darauf habe, in welcher Reihenfolge das an 'json.load()' dict abgearbeitet wird, wüsste ich nicht, wie sich das lösen lässt.
Ich könnte natürlich einfach alle JSON_STANDARD_TYPES in 'default()' unverändert durchwinken, dann allerdings würden non-JSON_STANDARD_TYPES die sich z. B. in einer Liste befinden nicht deserialisiert, da sie ja von 'json.load()' nie an 'deserialize_object()' übergeben werden.

Was meint ihr?

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

@mutetella: Ich verstehe Dein Problem nicht… Du musst doch ohnehin rekursiv (de-)serialisieren, da ein Objekt ja ein weiteres komplexes Objekt als Attribut haben kann.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@lunar:
Wenn ich eine Klasse mit einem str-Attribut habe, schaut damit ein json-Objekt so aus:

Code: Alles auswählen

'{"__type": "any.Class", "name": {"__value": "simply a string", "__type": "__builtin__.str"}}'
Könnte ich die Reihenfolge beim Deserialisieren beeinflussen, würde ich 'any.Class' erzeugen, dabei das str-Attribut deserialisieren und als Attribut hinzufügen.

In dieser Form ist es so, dass 'json.dump()' erstmal

Code: Alles auswählen

{"__value": "simply a string", "__type": "__builtin__.str"}
zu 'deserialize_object()' schickt und 'simply a string' zurück erhält. Danach wird

Code: Alles auswählen

{"__type": "any.Class", "name": "simply a string"}
an 'deserialize_object()' übergeben. Dort wird dann innerhalb der for-Schleife 'simply a string' nochmals an 'deserialze_object()' geschickt. 'simply a string' erzeugt demnach 2 Funktionsaufrufe. Ginge das nicht billiger?

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

@mutetella: Wieso serialisierst Du Zeichenketten nicht einfach direkt?! Mein ursprünglicher Code hat das getan…
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Ok, ich kann Dir das jetzt leider nicht mehr erklären... In einer ursprünglichen Version war es allerdings so, dass 'json.load()' nur dann "in die Tiefe" ging, wenn jedes Objekt aufgebröselt per dict vorlag. Es sah so aus, als würden andernfalls JSON_STANDARD_TYPES nicht an 'deserialize_object()' gesendet und demnach z. B. Listen- oder Tuple-Elemente nicht weiter bearbeitet werden.

Na ja, wie auch immer, Du hast Recht. So

Code: Alles auswählen

def default(obj):
    ...
    if objtype in JSON_STANDARD_TYPES:
        return obj
    ...
klappts und nix ist doppelt gemobbelt... Man könnte natürlich noch tuple aus den 'JSON_STANDARD_TYPES' herausnehmen, weil 'json' daraus Listen macht...

Ansonsten: Kann ich es so lassen oder sind grobe Schnitzer drin?

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

@mutetella: Ich sehe auf den ersten Blick keine kapitalen Fehler… natürlich liegt es an Dir, dafür jetzt Tests zu schreiben, um wirklich sicher zu sein, dass die Funktionen funktionieren :)
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@lunar:
Das mit der Testerei (also nicht nur ein paar gut gemeinte Funktionsaufrufe) ist 'ne Sache, mit der ich mich auch schon lange auseinandersetzen sollte.... ob mein Kalender jemals zum Einsatz kommen wird...?

Jedenfalls danke ich Dir sehr für Deine Hilfe!!!

mutetella
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

Wenn du bis jetzt keine Tests für deinen Kalender geschrieben hast, dann kannst du es dir eigentlich (für die vorhandenen Methoden) sparen oder mal hier und dort hinzufügen, wenn du an einer Methode etwas ändern willst. Das Testen muss eigentlich von Anfang systematisch geschenen, so dass möglichst der gesamte Code abgedeckt wird. Fängst du erst am Ende damit an, fehlt dir schon ein großer Teil des Nutzens. Mit Tests will man nicht nur sicher gehen, dass am Ende alles funktioniert, sondern dass Änderungen zwischendurch auch nichts zerstören. Vielleicht testest du das einfach mal bei Gelegenheit an einem neuen Modul.
Das Leben ist wie ein Tennisball.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Mit Urlaub, Faulheit, Hitze und so weiter sind jetzt zwar 4 Wochen vergangen... trotzdem möchte ich die paar Änderungen an der 'json_ser.py' hier noch einstellen.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Bin gerade über Twitter @getpy auf das folgende Modul aufmerksam geworden und dachte mir, das gehört eigentlich auch noch hier in diesen Thread... man weiß ja nie...

jsonpickle

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