如何递归打印变量的内容,包括数据和对象属性?
如何递归打印变量的内容,包括数据和对象属性?
在Python中,可以使用str()
和repr()
来打印变量的内容。但是变量的内容可能会很复杂。报告为php var_dump()
等效的pprint
库可以很好地以易于阅读的格式显示数据:Python中有没有类似于PHP的var_dump()的函数?,Python中是否有一个函数可以打印对象的所有当前属性和值?\n然而,如果数据中有对象([编辑]没有实现__str__
或__repr__
),str()
,repr()
和pprint
只会给出它们的名称。我想要一个能够递归遍历对象属性的方法,以正确地提供变量的完整表示。\n某些函数(如内置函数)不应该被打印,因为这没有用处。该方法还应该能够处理getattr
引发的异常等问题。或许自定义可迭代对象也可以像列表一样处理。\n
\n我尝试了下面的方法。虽然它能工作,但我确定还有一些未考虑到的边界情况,以及可能从输出中遗漏的一些信息(例如,区分元组/列表)。我的意思是,请分享其他可选方案 🙂
这是一个我以前创建的递归漂亮打印程序,用于检查我的项目中的数据结构。它能够处理任意对象,为常见的数据类型提供有用的输出,并通过参数进行一些自定义。注释掉的行表示可能要使用的替代方法。代码也可以在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)
解决方法:使用递归的方式,逐层打印变量的内容和对象属性。
这个问题的出现是因为作者原本使用的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函数,作者解决了无法递归打印变量的问题,并展示了该函数的使用方法和输出结果。这个解决方法可以帮助开发人员更全面地了解变量的结构和内容。
如何递归打印变量的内容,包括数据和对象属性?
问题出现的原因:
当我们需要查看一个变量的内容时,通常使用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格式,都可以根据个人偏好选择打印的格式。