解决Python的JSON模块不支持循环引用的问题

3

除了使用外部库(例如 jsonpickle,虽然我没有尝试过),是否有一种方法可以让 Python 的 json 模块将具有循环引用的字典(或列表等)进行转储(只是删除引用)?

我只想使用 json 更轻松地查看一些调试输出。


为什么你想要避免使用另一个库呢?你可以使用 YAML,我很确定它可以处理循环引用。 - Falmarri
在我的情况下,我正在运行在Google App Engine内部,如果没有提供很多价值的话,最好不要包含外部库。但我相信其他人有不同的理由。链接到其他库肯定是有帮助的,但不是主要问题。 - Pat
1个回答

3

好的,避免使用除标准模块以外的任何东西,这里有一个解决方案,它利用repr来处理循环引用。编辑:最新版本请参见多用途函数,可将任何Python对象以大部分可读的方式转储(也称为dump)

# MAGIC-NUMBER: max length is just some guess at a reasonable size, e.g. 80 cols by 100 lines
def dump(value, msg='DUMP', max_length=80 * 100, stdout=False, pick=None):
    """
    Write as verbose of a description of the value as possible to logging.DEBUG.

    See https://dev59.com/9obca4cB1Zd3GeqPZLN6

    :param value: The item of interest.
    :type value: object
    :param msg: Prefix for the logged item (default='DUMP')
    :type msg: basestring
    :param max_length: Longest allowed string length (set to None for unlimited)
    :type max_length: int
    :param stdout: If true, print instead of logging (default=False)
    :type stdout: bool
    :param pick: If specified, dump only values for these keys of the item
        (value must be a dict or allow __dict__ access).
        The name comes from http://underscorejs.org/#pick.
    :type pick: iterable of basestring
    :return: True if message dumped
    :rtype: bool
    """
    if not logging.getLogger().isEnabledFor(logging.DEBUG) and not stdout:
        return

    if pick:
        d = value if isinstance(value, dict) else value.__dict__
        filtered = {
            property_name: d[property_name]
            for property_name in pick
            if property_name in d
        }
        value = filtered

    kwargs = dict(indent=2, sort_keys=True)
    try:
        import json
        info = json.dumps(value, **kwargs)
    except:
        # JSON doesn't like circular references :/
        try:
            string_repr = repr(value)
            # Replace python primitives, single-quotes, unicode, etc
            string_repr = string_repr\
                .replace('None', 'null')\
                .replace('True', 'true')\
                .replace('False', 'false')\
                .replace("u'", "'")\
                .replace("'", '"')

            # Replace object and function repr's like <MyObject ...>
            string_repr = re.sub(r':(\s+)(<[^>]+>)', r':\1"\2"', string_repr)

            # Replace tuples with lists, very naively
            string_repr = string_repr.replace('(', '[').replace(')', ']')

            info = json.dumps(json.loads(string_repr), **kwargs)
        except:
            from pprint import pformat
            info = pformat(value, indent=2)

    def _out(formatted_string, *format_args):
        """Format the string and output it to the correct location."""
        if stdout:
            print(formatted_string % format_args)
        else:
            logging.debug(formatted_string, *format_args)

    if max_length is None or len(info) <= max_length:
        _out('%s: %s', msg, info)
        return True
    else:
        _out(
            'Did not dump "%s" due to length restriction. Increase max_length if desired.', msg
        )
    return False

不要只写 except:,那样会捕获系统异常。 - Falmarri
是的,我知道。我有点懒。不过,如果出现系统异常,最有可能在最后一个 except 子句中重新引发异常。这不是为了生产日志记录 ;) - Pat
哈!所以我被投下了一票,却没有任何评论。有时候我觉得人们对编程答案太过于个人化了! - Pat

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