You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 02:52:55