问题
I have to save various properties of custom GTK elements to a file for future use and decided to use JSON because of the simple format and nesting of dicts.
Many properties are GTK enums, like gtk.PAGE_ORIENTATION_PORTRAIT
, gtk.ANCHOR_CENTER
and pango.ALIGN_LEFT
. They have a unique name which can be retrieved with obj.value_name
to get a valid JSON type.
Currently I have 2 methods for each of my elements: to_str()
to get the value_name and from_str()
which maps the str to the enum again. I would like to automate this so I don't forget to call these and clean up the code a bit. The JSONEncoder and JSONDecodr are doing exactly this, or so I thought...
This is the example given in the Python docs and it works as expected.
import json
class ComplexEncoder(json.JSONEncoder):
def default(self, obj):
print "default method called for:", obj
if isinstance(obj, complex):
return [obj.real, obj.imag]
return json.JSONEncoder.default(self, obj)
print json.dumps(2 + 1j, cls=ComplexEncoder)
Based on this example I've added the GTK enums:
import json
import gtk
ENUMS = [gtk.PAGE_ORIENTATION_PORTRAIT, gtk.PAGE_ORIENTATION_LANDSCAPE]
class GtkEncoder(json.JSONEncoder):
def default(self, obj):
print "default method called for:", obj
if obj in ENUMS:
return obj.value_name
return json.JSONEncoder.default(self, obj)
print json.dumps(gtk.PAGE_ORIENTATION_LANDSCAPE, cls=GtkEncoder)
Notice the added print statement in the default
method. In the original example, this method is called without problems, but not in the GTK example. The default
method is never called and it returns <enum GTK_PAGE_ORIENTATION_LANDSCAPE of type GtkPageOrientation>
which isn't valid JSON ofcourse.
So, is there a way to automatically encode/decode these enums or am I stuck with the current manual approach? Note that my data structure to dump is not a single value, but a dict or dicts.
回答1:
The observed behavior occurs because the value gtk.PAGE_ORIENTATION_LANDSCAPE
is an instance of class 'gtk._gtk.PageOrientation'
which inherits type 'gobject.GEnum'
which in turn inherits type 'int'
.
So your GTK-enums are ints, and the json code assumes it can handle ints and thus does not call the default
method of your encoder.
Unfortunately the current json implementation isn't that helpful in encoding subclassed types like this :-/ No inheriting and overriding makes this possible (at least I cannot find any solution for this). Just too many hardcoded places where the value is checked for isinstance(value, (int, long))
.
But you can of course patch the source of the json encoder to achieve your goal without having to implement the whole json functionality anew. For this copy the file encoder.py
from the json library (for me this is /usr/lib/python2.7/json/encoder.py
) to your working directory and patch it.
In the functions _iterencode_list()
and _iterencode_dict()
(they are local to function _make_iterencode()
) you can find checks for being of type int
or long
; if so, the current implementation just calls str(value)
. Change that to encodeInt(value)
(in three places!) and implement your own encodeInt()
function in encoder.py
:
def encodeInt(value):
try:
return value.value_name
except:
return str(value)
Then, in your original code, you will have to import directly that patched file:
import encoder
and you will have to make sure the C implementation isn't used anymore but instead the patched code. (You see, normally, the (faster) C implementation is used, and that Python code in which we patched something is not.) To achieve this, just add after the import:
encoder.c_make_encoder = None
Now your patched encoder can be used:
print encoder.JSONEncoder().encode({
gtk.PAGE_ORIENTATION_PORTRAIT: [
gtk.PAGE_ORIENTATION_LANDSCAPE
],
gtk.PAGE_ORIENTATION_LANDSCAPE: gtk.PAGE_ORIENTATION_PORTRAIT })
prints:
{"GTK_PAGE_ORIENTATION_PORTRAIT": [GTK_PAGE_ORIENTATION_LANDSCAPE], "GTK_PAGE_ORIENTATION_LANDSCAPE": GTK_PAGE_ORIENTATION_PORTRAIT}
Note that Json dict keys always must be strings. That's why your values get double quotes when used as keys. But that's a fate even normal int
s — when used as keys — share. They also get stringified.
You can have a look at http://pastebin.com/2HAtN9E8 to see all the sources.
回答2:
I found a solution, although this may not work for gtk, it does however work for enums in general.
If you can use IntEnum instead of Enum, then you can override str in your Enum class to return a string of whatever you want, name or value most likely.
import json
from enum import IntEnum
class TrekCaptains(IntEnum):
Kirk = 0
Picard = 1
def __str__(self):
return '{0}'.format(self.value)
s = {TrekCaptains.Kirk:'Original Series',
TrekCaptains.Picard:'Next Generation'}
j = json.dumps(s)
print j
#result
#{"0": "Original Series", "1": "Next Generation"}
来源:https://stackoverflow.com/questions/21606049/json-encode-decode-gtk-enums