JSON编码/解码GTK枚举

3
我必须将自定义GTK元素的各种属性保存到文件中以供将来使用,决定使用JSON格式,因为它具有简单的格式和字典嵌套。许多属性都是GTK enums,例如`gtk.PAGE_ORIENTATION_PORTRAIT`,`gtk.ANCHOR_CENTER`和`pango.ALIGN_LEFT`。可以使用`obj.value_name`检索它们的唯一名称,以获取有效的JSON类型。
目前,我对我的每个元素有两种方法:`to_str()`获取`value_name`和`from_str()`将`str`映射回枚举。我想自动化这个过程,这样我就不会忘记调用它们并清理代码。JSONEncoder和JSONDecoder正是这样做的,或者我认为是这样...这是Python文档中给出的示例,并且按预期工作。
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)

根据这个例子,我添加了GTK枚举:

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)

请注意default方法中添加的打印语句。在原始示例中,该方法可以正常调用,但在GTK示例中无法调用。 default方法从未被调用,并返回不是有效JSON的<enum GTK_PAGE_ORIENTATION_LANDSCAPE of type GtkPageOrientation>

那么,是否有自动编码/解码这些枚举的方法,还是我必须使用当前手动方法?请注意,要转储的数据结构不是单个值,而是一个字典或多个字典。

2个回答

2
观察到的行为是因为值gtk.PAGE_ORIENTATION_LANDSCAPEclass 'gtk._gtk.PageOrientation'类的一个实例,该类继承了type 'gobject.GEnum',后者又继承了type 'int'
因此,您的GTK枚举是整数,json代码假定它可以处理整数,因此不调用编码器的default方法。
不幸的是,当前的json实现在编码这种子类类型时并不那么有帮助。没有继承和覆盖使这成为可能(至少我找不到任何解决方案)。太多硬编码的地方检查值是否为isinstance(value, (int, long))
但是,您当然可以修补json编码器的源代码,以实现您的目标,而无需重新实现整个json功能。为此,请将json库中的encoder.py文件(对我来说,这是/usr/lib/python2.7/json/encoder.py)复制到您的工作目录中并进行修补。
在函数_make_iterencode()的本地函数_iterencode_list()_iterencode_dict()中,您可以找到检查是否为intlong类型的代码;如果是,则当前实现只调用str(value)。将其更改为encodeInt(value)(三个位置!),并在encoder.py中实现自己的encodeInt()函数:
def encodeInt(value):
  try:
    return value.value_name
  except:
    return str(value)

然后,在您的原始代码中,您将不得不直接导入那个修补过的文件:
import encoder

你需要确保不再使用 C 实现,而是使用已打补丁的代码。(通常情况下,会使用更快的 C 实现,而我们打了补丁的 Python 代码则不会被使用。)为了实现这一点,在导入后添加以下内容即可:
encoder.c_make_encoder = None

现在你的修补编码器可以使用了:
print encoder.JSONEncoder().encode({
  gtk.PAGE_ORIENTATION_PORTRAIT: [
    gtk.PAGE_ORIENTATION_LANDSCAPE
  ],
  gtk.PAGE_ORIENTATION_LANDSCAPE: gtk.PAGE_ORIENTATION_PORTRAIT })

打印:
{"GTK_PAGE_ORIENTATION_PORTRAIT": [GTK_PAGE_ORIENTATION_LANDSCAPE], "GTK_PAGE_ORIENTATION_LANDSCAPE": GTK_PAGE_ORIENTATION_PORTRAIT}

请注意,JSON字典键始终必须是字符串。这就是为什么当用作键时,您的值会带有双引号的原因。但这甚至也适用于普通的int类型——当它们被用作键时,它们也会被转换为字符串。您可以查看链接http://pastebin.com/2HAtN9E8以查看所有来源。

谢谢你提供继承的提示,我其实也觉得会是那样。不过很遗憾,仅当枚举被编码时才有效。如果将枚举放入列表或字典中,则encodevalue参数为整个对象,因此会失败。 - Timo
哎呀,我以为“encode”例程是递归的。我会看一下,看看能否修复我的解决方案。 - Alfe
糟糕。那个JSON内部代码不容易实现。我正在处理,但在接下来的20小时内不会有任何结果。 - Alfe
谢谢您的关注。我花了很多时间在这上面,但是找不到内置的解决方案。 - Timo
我已经更新了我的回答。不幸的是,消息并不好。我所看到的唯一解决方案是修补json库。请参见答案以获取详细信息。 - Alfe
我对json的实现并不印象深刻。需要有一种方法来覆盖basestring、float、int、long、null、True和False的默认值。修补代码可以解决问题,但这并不是最好的方法。新的Enum库已经回溯到2.x,这使得这个问题变得更加重要,因为Enum是可哈希的,并且可以作为字典键的一个很好的选择。 - srock

0

我找到了一个解决方案,虽然这可能对gtk无效,但是对于枚举一般而言仍然有效。

如果你可以使用IntEnum而不是Enum,那么你可以在你的Enum类中重写str方法,返回任何你想要的字符串,最可能是名称或值。

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"}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接