【问题标题】:JSON encode/decode GTK enumsJSON 编码/解码 GTK 枚举
【发布时间】:2014-02-06 14:47:17
【问题描述】:

我必须将自定义 GTK 元素的各种属性保存到文件中以备将来使用,并决定使用 JSON,因为 dicts 的格式和嵌套简单。

许多属性都是 GTK 枚举,例如 gtk.PAGE_ORIENTATION_PORTRAITgtk.ANCHOR_CENTERpango.ALIGN_LEFT。它们有一个唯一的名称,可以使用 obj.value_name 检索以获取有效的 JSON 类型。

目前我的每个元素都有 2 种方法:to_str() 获取 value_name 和 from_str() 再次将 str 映射到枚举。我想自动化这个,所以我不会忘记调用这些并清理一下代码。 JSONEncoder 和 JSONDecodr 正是这样做的,或者我认为......

这是 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 方法永远不会被调用,它会返回 <enum GTK_PAGE_ORIENTATION_LANDSCAPE of type GtkPageOrientation> 这当然不是有效的 JSON。

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

【问题讨论】:

    标签: python json gtk pygtk


    【解决方案1】:

    观察到的行为发生是因为值gtk.PAGE_ORIENTATION_LANDSCAPEclass 'gtk._gtk.PageOrientation' 的一个实例,它继承了type 'gobject.GEnum',而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)复制到您的工作目录并对其进行修补。

    在函数_iterencode_list()_iterencode_dict()(它们是函数_make_iterencode() 的本地函数)中,您可以找到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 dict 键始终必须是字符串。这就是为什么您的值在用作键时得到双引号的原因。但这是一种命运,即使是普通的ints——当用作钥匙时——分享。它们也会被字符串化。

    您可以查看http://pastebin.com/2HAtN9E8 以查看所有来源。

    【讨论】:

    • 感谢您的继承提示,我实际上认为它会是这样的。不幸的是,这仅在仅对枚举进行编码时才有效。将枚举放入列表或字典时,encodevalue 参数是整个对象,所以它失败了。
    • Urgs。好的,我认为encode 例程是递归的。我会看看我是否可以修改我的解决方案。
    • 无赖。那个 json 内部代码并不容易实现这一点。我正在努力,但在接下来的 20 小时内我不会想出任何东西。
    • 感谢您的调查。我为此花了很多时间,但找不到内置解决方案。
    • 我已经更新了我的答案。不幸的是,这个消息并不好。我看到的唯一解决方案是修补 json 库。详情见答案。
    【解决方案2】:

    我找到了一个解决方案,虽然这可能不适用于 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"}
    

    【讨论】:

      猜你喜欢
      • 2019-01-06
      • 2021-09-11
      • 2017-10-06
      • 2021-08-11
      • 1970-01-01
      • 2014-12-13
      • 2015-06-24
      • 1970-01-01
      • 2011-03-05
      相关资源
      最近更新 更多