如何递归打印变量的内容,包括数据和对象属性?

11 浏览
0 Comments

如何递归打印变量的内容,包括数据和对象属性?

在Python中,可以使用str()repr()来打印变量的内容。但是变量的内容可能会很复杂。报告为php var_dump()等效的pprint库可以很好地以易于阅读的格式显示数据:Python中有没有类似于PHP的var_dump()的函数?Python中是否有一个函数可以打印对象的所有当前属性和值?\n然而,如果数据中有对象([编辑]没有实现__str____repr__),str()repr()pprint只会给出它们的名称。我想要一个能够递归遍历对象属性的方法,以正确地提供变量的完整表示。\n某些函数(如内置函数)不应该被打印,因为这没有用处。该方法还应该能够处理getattr引发的异常等问题。或许自定义可迭代对象也可以像列表一样处理。\n


\n我尝试了下面的方法。虽然它能工作,但我确定还有一些未考虑到的边界情况,以及可能从输出中遗漏的一些信息(例如,区分元组/列表)。我的意思是,请分享其他可选方案 🙂

0
0 Comments

这是一个我以前创建的递归漂亮打印程序,用于检查我的项目中的数据结构。它能够处理任意对象,为常见的数据类型提供有用的输出,并通过参数进行一些自定义。注释掉的行表示可能要使用的替代方法。代码也可以在Gist中找到:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 递归生成任意对象的漂亮打印输出
def generate_pprint(obj, level_indent="  ", max_depth=None, verbose_output=True,
                    justify_output=True, prevent_loops=True, prevent_revisit=False,
                    explore_objects=True, excluded_ids=[], visited_ids=[],
                    path_ids=[], current_depth=0):
    """递归生成任意对象的漂亮打印输出。
    递归生成任意对象的内容的漂亮打印输出。内容以包含键值对的行表示。递归可以受到各种参数的影响(见下文)。
    参数:
        max_depth:       递归的最大深度。如果超过了深度,递归将停止。
        verbose_output:  产生详细的输出。为某些数据类型添加一些附加细节。
        justify_output:  对齐输出。以块状外观生成输出,键和值之间有相等的间距。
        prevent_loops:   通过在当前递归路径中跟踪已访问的对象来检测和防止递归循环。
        prevent_revisit: 检测和防止重新访问已访问的对象。尽管“prevent_loops”仅在一个递归路径中防止重新访问对象,但这可以在所有递归路径中防止重新访问对象。
        explore_objects: 探索(即递归进入)任意对象。如果启用,将探索不匹配基本类型的任意对象。如果禁用,只探索某些类型的对象(元组,列表,字典,集合/不可变集合)。注意,这不影响最初提供的对象(始终会进行探索)。
        excluded_ids:    要排除(即不进行递归)的对象ID列表。如果遇到具有匹配ID的对象,则停止递归。
        visited_ids,     用于控制递归流程,循环检测和重新访问检测的内部变量。不要提供或修改这些变量!
        current_depth:   当前递归的深度。
    返回:
        生成的漂亮打印输出作为行(字符串)的列表。
    引发:
        TypeError:       对象或值具有不受支持的类型(不应该发生)
        AssertionError:  断言失败,很可能暴露一个错误(不应该发生)
    """
    output = []
    indent = level_indent * current_depth
    # 检查对象是否已在当前递归路径中访问过。
    # 如果是,则遇到了一个循环,因此需要停止递归。如果
    # 否则,继续并将对象添加到当前递归路径中访问的对象列表中
    if (prevent_loops == True):
        if (id(obj) in path_ids):
            output.append(indent + "<recursion loop detected>")
            return output
        path_ids.append(id(obj))
    # 检查对象是否已访问过。如果是,则不再访问
    # 它,并停止递归。如果否,则继续并将当前对象
    # 添加到已访问对象的列表中
    if (prevent_revisit == True):
        if (id(obj) in visited_ids):
            output.append(indent + "<item already visited>")
            return output
        visited_ids.append(id(obj))
    # 检查是否超过了递归的最大允许深度。如果是,则中断
    # 递归
    if (max_depth != None and current_depth > max_depth):
        output.append(indent + "<recursion limit reached>")
        return output
    # 检查是否应该排除对象。如果是,则中断递归
    if (id(obj) in excluded_ids):
        output.append(indent + "<item is excluded>")
        return output
    # 确定键和关联的值
    if (isinstance(obj, dict)):
        keys = obj.keys()
        values = obj
    elif (isinstance(obj, tuple) or isinstance(obj, list)):
        keys = range(0, len(obj))
        values = obj
    elif (isinstance(obj, set) or isinstance(obj, frozenset)):
        keys = range(0, len(obj))
        values = [ item for item in obj ]
    elif (isinstance(obj, object)):
        keys = [ item for item in dir(obj) if (not item.startswith("_")) ]
        values = { key: getattr(obj, key) for key in keys }
    else: # 这不应该发生,因为Python中的一切都是'object',并且应该在上面捕获,但最好是安全的
        raise TypeError("unsupported object type: '%s'" % type(obj))
    # 定义键字符串模板。如果要对齐输出,则确定键字符串的最大长度,并相应调整模板
    kstmp1 = kstmp2 = "%s"
    if (justify_output == True):
        maxlen = 0
        for key in keys:
            klen = len(str(key))
            if (klen > maxlen):
                maxlen = klen
        kstmp1 = "%-" + str(maxlen+3) + "s" # maxlen+3: 将单引号和尾随冒号包围
        kstmp2 = "%-" + str(maxlen+1) + "s" # maxlen+1: 尾随冒号
    # 处理键和关联的值
    for key in keys:
        value = values[key]
        # 生成键字符串
        if (isinstance(obj, dict) and isinstance(key, str)):
            keystr = kstmp1 % ("'" + str(key) + "':")
        else:
            keystr = kstmp2 % (str(key) + ":")
        # 生成值字符串
        valstr = ""
        exp_obj = False
        if (isinstance(value, dict)):
            valstr = "<dict, %d items, class '%s'>" % (len(value), type(value).__name__) if (verbose_output == True) else "<dict, %d items>" % len(value)
        elif (isinstance(value, tuple)):
            valstr = "<tuple, %d items, class '%s'>" % (len(value), type(value).__name__) if (verbose_output == True) else "<tuple, %d items>" % len(value)
        elif (isinstance(value, list)):
            valstr = "<list, %d items, class '%s'>" % (len(value), type(value).__name__) if (verbose_output == True) else "<list, %d items>" % len(value)
        elif (isinstance(value, set)): # set和frozenset是不同的
            valstr = "<set, %d items, class '%s'>" % (len(value), type(value).__name__) if (verbose_output == True) else "<set, %d items>" % len(value)
        elif (isinstance(value, frozenset)): # set和frozenset是不同的
            valstr = "<frozenset, %d items, class '%s'>" % (len(value), type(value).__name__) if (verbose_output == True) else "<frozenset, %d items>" % len(value)
        elif (isinstance(value, range)):
            valstr = "<range, start %d, stop %d, step %d>" % (value.start, value.stop, value.step) if (verbose_output == True) else "<range(%d,%d,%d)>" % (value.start, value.stop, value.step)
        elif (isinstance(value, bytes)):
            valstr = "<bytes, %d bytes>" % len(value)
        elif (isinstance(value, bytearray)):
            valstr = "<bytearray, %d bytes>" % len(value)
        elif (isinstance(value, memoryview)):
            valstr = "<memoryview, %d bytes, object %s>" % (len(value), type(value.obj).__name__) if (verbose_output == True) else "<memoryview, %d bytes>" % len(value)
        elif (isinstance(value, bool)): # 需要放在int之前,因为'bool'也被视为'int'
            valstr = "%s" % value
        elif (isinstance(value, int)):
            valstr = "%d (0x%x)" % (value, value)
        elif (isinstance(value, float)):
            valstr = "%s" % str(value) # str(value)提供最佳表示;替代方案:'%e|%E|%f|%F|%g|%G' % value
        elif (isinstance(value, complex)):
            valstr = "%s" % str(value)
        elif (isinstance(value, str)):
            valstr = "'%s'" % repr(value)[1:-1] # 这似乎是始终获得单引号字符串的唯一方法
        elif (value == None):
            valstr = "None"
        elif isinstance(value, type): # 检查对象是否为“class”(https://stackoverflow.com/a/10123520/1976617);需要放在'callable'之前,因为'class'也被视为'callable'
            valstr = "<class '%s.%s'>" % (value.__module__, value.__name__) if (verbose_output == True) else "<class>"
        elif (callable(value)): # 捕获一切可调用的函数,方法,类(由于构造函数),等等
            valstr = "<callable, class '%s.%s'>" % (value.__class__.__module__, value.__class__.__name__) if (verbose_output == True) else "<callable>"
        elif (isinstance(value, object)): # 这必须是最后一个,因为*上面的一切都被视为'object'
            valstr = "<object, class '%s.%s'>" % (value.__class__.__module__, value.__class__.__name__) if (verbose_output == True) else "<object>"
            if (explore_objects == True):
                exp_obj = True # 这确保我们只探索不表示基本类型的对象(即上面列出的所有内容)
        else: # 这不应该发生,因为Python中的一切都是'object',并且应该在上面捕获,但最好是安全的
            raise TypeError("unsupported value type: '%s'" % type(value))
        # 从键/值字符串生成键值行并添加到输出中
        output.append(indent + keystr + " " + valstr)
        # 如果满足某些条件,则递归地探索值对象
        if (isinstance(value, dict) or isinstance(value, tuple) or isinstance(value, list) or isinstance(value, set) or
            isinstance(value, frozenset) or (isinstance(value, object) and exp_obj == True)):
            output += generate_pprint(value, level_indent=level_indent, max_depth=max_depth, verbose_output=verbose_output,
                                      justify_output=justify_output, prevent_loops=prevent_loops, prevent_revisit=prevent_revisit,
                                      explore_objects=explore_objects, excluded_ids=excluded_ids, visited_ids=visited_ids,
                                      path_ids=path_ids, current_depth=current_depth + 1)
    # 从当前递归路径的已访问对象列表中删除对象
    # (递归循环检测的一部分;这个操作“回滚”了已布置的路径)
    if (prevent_loops == True):
        assert len(path_ids) > 0 and path_ids[-1] == id(obj), "last item in list of path objects not existing or not matching object"
        path_ids.pop()
    # 返回生成的输出
    return output
# generate_pprint()的便捷包装器
def print_pprint(*args, **kwargs):
    output = generate_pprint(*args, **kwargs)
    for line in output:
        print(line)
# 演示(仅在直接运行时执行)
if (__name__ == "__main__"):
    import sys
    print_pprint(sys)

解决方法:使用递归的方式,逐层打印变量的内容和对象属性。

0
0 Comments

这个问题的出现是因为作者原本使用的pprint方法只能显示变量的一级数据,无法递归地打印出变量的内容和对象属性。为了解决这个问题,作者编写了一个名为var_dump的函数,该函数可以递归地打印变量的内容和对象属性。

解决方法如下所示:

作者首先导入了types模块,然后定义了一个名为var_dump的函数。这个函数可以接受三个参数:obj(要打印的变量),depth(递归的深度),l(用于缩进的字符串)。

在函数的实现中,作者使用了一些条件判断和循环来处理不同类型的变量。首先,作者通过判断depth的值来决定是否使用repr函数来返回obj的字符串表示。然后,作者判断obj是否是字典类型,如果是,则将obj赋值给objdict。如果不是字典类型,则判断obj是否是基本类型,如果是,则直接返回obj的字符串表示。如果不是基本类型,则尝试按照列表的方式来迭代obj,并递归调用var_dump函数来打印每个元素。如果obj无法迭代为列表,则将obj的属性和值存储在objdict中。

最后,作者将obj的类名(如果有)或类型名存储在name变量中,并通过循环遍历objdict来打印对象的属性和值。在循环中,作者使用了repr函数来返回属性的字符串表示,并递归调用var_dump函数来打印属性的值。

在例子的输出中,作者定义了两个类B和A,并创建了一个字典变量var,其中包含两个键值对。通过调用var_dump函数来打印var的内容,可以看到var中的对象和属性都被递归地打印出来。这个例子展示了var_dump函数的使用方法和输出结果。

总结起来,这篇文章介绍了一个递归打印变量内容和对象属性的解决方法。通过编写var_dump函数,作者解决了无法递归打印变量的问题,并展示了该函数的使用方法和输出结果。这个解决方法可以帮助开发人员更全面地了解变量的结构和内容。

0
0 Comments

如何递归打印变量的内容,包括数据和对象属性?

问题出现的原因:

当我们需要查看一个变量的内容时,通常使用print语句来打印变量的值。然而,如果这个变量是一个对象,仅仅打印变量的值可能无法满足我们的需求。我们可能还想打印出对象的属性和其他相关信息,以便更好地理解对象的结构和内容。因此,出现了这个问题。

解决方法:

下面的代码可以以json或yaml格式递归打印出变量的所有内容,包括数据和对象属性:

import jsonpickle # pip install jsonpickle
import json
import yaml # pip install pyyaml
# 将对象序列化为字符串
serialized = jsonpickle.encode(obj)
# 打印json格式的内容
print json.dumps(json.loads(serialized), indent=4)
# 打印yaml格式的内容
print yaml.dump(yaml.load(serialized), indent=4)

通过使用`jsonpickle`库,我们可以将对象序列化为字符串。然后,我们使用`json`库将序列化后的字符串解析为json格式,并使用`json.dumps()`函数以缩进格式打印出来。同样地,我们也可以使用`yaml`库将序列化后的字符串解析为yaml格式,并使用`yaml.dump()`函数以缩进格式打印出来。

这样,我们就可以递归打印出变量的所有内容,包括数据和对象属性了。无论是使用json格式还是yaml格式,都可以根据个人偏好选择打印的格式。

0