如何在Python中像Rust的derive(Debug)一样调试打印自定义类对象?
Python 实现类似 Rust #[derive(Debug)] 的调试打印方案
我同时用Python和Rust有段时间了,一直对Python自定义对象默认的打印信息不满——默认打印只会输出<module.Class object at 0x7f6415259210>这种完全没用的内容,不像Rust可以通过#[derive(Debug)]轻松得到可读性极强的调试输出:
#[derive(Debug, Default)] struct Hello { name: String, first: usize, second: usize, } fn main() { let hello = Hello::default(); dbg!(hello); }
输出的调试信息清晰直观:
[src/main.rs:10] hello = Hello { name: "", first: 0, second: 0, }
我知道Python的dataclass有类似功能:
from dataclasses import dataclass @dataclass class Hello: name: str first: int second: int print(Hello("", 0, 0)) >>> Hello(name='', first=0, second=0)
但这个方案只适用于dataclass,我需要能适配普通自定义类的通用方案。我试过手动写__repr__或者逐个打印变量,但太麻烦。目前我会复制粘贴一个Debug基类来实现:
class Debug: """模仿Rust的#[derive(Debug)] 继承此类可让对象在调试打印时更直观 """ if __debug__: def __repr__(self) -> str: names = [f"{attr}: {value}" for attr, value in self.__dict__.items()] if hasattr(self, "__slots__"): names += [f"{attr}: {getattr(self, attr)}" for attr, in getattr(self, "__slots__")] return f"{type(self).__name__}: [{', '.join(names)}]"
使用方式:
class Hello(Debug): def __init__(self, name: str = "", first: int = 0, second: int = 0) -> None: self.name = name self.first = first self.second = second def some_method(self): # 模拟非dataclass的复杂逻辑 self.set = 5 # 这个属性也会被打印出来 return "something" hello = Hello() print(hello) >>> Hello: [name: , first: 0, second: 0]
这个方案有不少问题(比如空字符串的显示、继承时的属性处理),但能凑合用。我知道可以通过添加判断逻辑、改用类装饰器甚至做成工具包来优化,但我更想知道有没有现成的成熟方案。请问实现这种通用调试打印的正确做法是什么?
可行的解决方案
1. 使用 attrs 库(最成熟的现成方案)
attrs 是Python生态中专门用于简化类定义的库,支持为普通类自动生成调试用的__repr__,比dataclass更灵活,兼容各种类结构。
安装:
pip install attrs
使用示例:
import attr @attr.define class Hello: name: str = "" first: int = 0 second: int = 0 def some_method(self): self.set = 5 return "something" hello = Hello() print(hello) >>> Hello(name='', first=0, second=0) # 如果想包含动态添加的属性,可以用attr.asdict()辅助打印所有属性 print(attr.asdict(hello)) >>> {'name': '', 'first': 0, 'second': 0, 'set': 5}
如果不想用装饰器,也可以为已有类补全__repr__:
import attr class Hello: def __init__(self, name: str = "", first: int = 0, second: int = 0): self.name = name self.first = first self.second = second # 为已有类生成调试用的repr attr.s(Hello, auto_attribs=False) hello = Hello() print(hello) >>> Hello(name='', first=0, second=0)
2. 用类装饰器实现通用调试打印
相比继承基类,类装饰器更灵活,不会影响类的继承结构:
def debug_repr(cls): if __debug__: def __repr__(self): # 收集所有实例属性(包括__dict__和__slots__) attrs = {} attrs.update(self.__dict__) if hasattr(self, "__slots__"): for slot in self.__slots__: if slot not in attrs: attrs[slot] = getattr(self, slot) # 用repr()保证属性值的类型显示正确(比如空字符串显示为'') attr_str = ", ".join(f"{k}={repr(v)}" for k, v in attrs.items()) return f"{cls.__name__}({attr_str})" cls.__repr__ = __repr__ return cls
使用方式:
@debug_repr class Hello: def __init__(self, name: str = "", first: int = 0, second: int = 0): self.name = name self.first = first self.second = second def some_method(self): self.set = 5 return "something" hello = Hello() print(hello) >>> Hello(name='', first=0, second=0) hello.some_method() print(hello) >>> Hello(name='', first=0, second=0, set=5)
3. 优化你的继承式Debug类
如果坚持用继承方案,可以优化属性处理逻辑,解决空字符串、继承属性的问题:
class Debug: """优化后的Rust风格Debug基类""" if __debug__: def __repr__(self) -> str: attrs = {} # 收集所有父类的slot属性 for cls in reversed(type(self).__mro__): if hasattr(cls, "__slots__"): for slot in cls.__slots__: if slot not in attrs: try: attrs[slot] = getattr(self, slot) except AttributeError: pass # 收集实例的动态属性 attrs.update(self.__dict__) # 格式化输出 attr_str = ", ".join(f"{k}={repr(v)}" for k, v in attrs.items()) return f"{type(self).__name__}({attr_str})"
使用效果:
class Hello(Debug): def __init__(self, name: str = "", first: int = 0, second: int = 0): self.name = name self.first = first self.second = second hello = Hello() print(hello) >>> Hello(name='', first=0, second=0)
4. 调试时临时打印属性
如果只是临时调试,不想修改类定义,可以用pprint模块打印实例的属性集合:
from pprint import pprint class Hello: def __init__(self, name: str = "", first: int = 0, second: int = 0): self.name = name self.first = first self.second = second hello = Hello() pprint(hello.__dict__) >>> {'first': 0, 'name': '', 'second': 0}
内容的提问来源于stack exchange,提问作者Egeau
相关产品推荐
相关产品推荐

