Python repr() 函数:让对象“自我表达”的秘密武器
在 Python 的众多内置函数中,repr() 可能不是最常被提及的,但它却是调试、日志记录和对象理解中的隐形英雄。如果你曾经在调试时看到过一堆看不懂的 <object at 0x...>,那很可能就是因为没有正确使用 repr()。今天我们就来彻底搞懂这个函数——它不只是“打印对象”,更是让程序自我表达的桥梁。
想象一下,你是一个程序员侦探,正在调查一个神秘的变量。你看到它打印出来是 "<__main__.Person object at 0x7f8c1a2b3c40>",这就像在看一张模糊的照片,根本无法判断它到底是谁。但如果你用 repr(),它会告诉你:“我是一个 Person 类的实例,名字叫 张三,年龄 25。”——这就是 repr() 的价值:提供对象的“可读性表示”。
repr() 函数的基本用法与返回值
repr() 是 Python 内置函数,它的作用是返回一个对象的“官方”字符串表示形式。和 str() 不同,repr() 更强调准确性和可重现性,适合用于调试和开发阶段。
number = 42
print(repr(number)) # 输出: 42
print(str(number)) # 输出: 42
text = "Hello, World!"
print(repr(text)) # 输出: 'Hello, World!'
print(str(text)) # 输出: Hello, World!
注意:
repr()会给字符串加上引号,而str()不加。这是因为repr()的目标是“让代码能还原对象”,所以必须保留原始格式。
再看一个例子:
data = [1, 2, 3, "abc"]
print(repr(data)) # 输出: [1, 2, 3, 'abc']
print(str(data)) # 输出: [1, 2, 3, abc]
info = {"name": "李四", "age": 30}
print(repr(info)) # 输出: {'name': '李四', 'age': 30}
print(str(info)) # 输出: {'name': '李四', 'age': 30}
在这里你会发现,
repr()保留了字符串的引号,而str()则直接输出内容。这种差异在调试时非常关键——如果你看到repr()输出的'abc',就知道这是一个字符串,而不是一个变量名。
repr() 与 str() 的核心区别
这是初学者最容易混淆的地方。我们用一张表格来清晰对比两者:
| 特性 | repr() |
str() |
|---|---|---|
| 目标 | 提供“可重现的”表示,用于调试 | 提供“用户友好”的表示,用于显示 |
| 字符串引号 | 保留引号(如 'abc') |
不保留引号(如 abc) |
| 数字类型 | 保持原始格式(如 42) |
保持原始格式(如 42) |
| 对象表示 | 通常包含类名和内存地址(如 <Person object at 0x...>) |
通常更简洁(如 Person(name='张三')) |
| 使用场景 | 调试、日志、开发环境 | 用户界面、打印输出 |
形象比喻:
str() 像是一位导游,只告诉你“这里有个湖”,简洁明了;
而 repr() 像是一位科学家,会说“这是位于北纬 30.123° 的人工湖,编号 H-2024-001,水深 2.5 米”——细节丰富,便于还原。
自定义类中的 repr() 方法
当我们在定义自己的类时,repr() 的作用更加突出。Python 允许我们通过重写 __repr__ 方法来控制对象的字符串表示。
class Student:
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
def __repr__(self):
return f"Student(name='{self.name}', age={self.age}, grade={self.grade})"
s = Student("王五", 18, "高三")
print(repr(s)) # 输出: Student(name='王五', age=18, grade=高三)
print(s) # 输出: Student(name='王五', age=18, grade=高三)
关键点:当使用
print()或repr()时,Python 会自动调用对象的__repr__方法。如果没有定义,它会使用默认的<类名 object at 0x...>格式。
我们再来看一个更复杂的例子,模拟一个数据库记录:
class Record:
def __init__(self, id, title, created_at):
self.id = id
self.title = title
self.created_at = created_at
def __repr__(self):
return f"Record(id={self.id}, title='{self.title}', created_at='{self.created_at}')"
record = Record(101, "用户注册", "2024-05-20 10:30:00")
print(repr(record)) # 输出: Record(id=101, title='用户注册', created_at='2024-05-20 10:30:00')
这个输出非常有用,因为你可以直接复制这段字符串,粘贴到代码中,重新创建一个完全相同的对象——这就是 repr() 的“可重现”特性。
repr() 在调试与日志中的实际应用
在真实项目中,repr() 的价值体现在日志和异常处理中。
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
def process_data(data):
logger.debug(f"接收到的数据: {repr(data)}") # 使用 repr() 保留原始结构
if not isinstance(data, list):
raise ValueError(f"期望的是列表,但收到: {repr(data)}")
process_data([1, 2, 3])
process_data("not a list") # 会触发异常,日志中显示: 接收到的数据: 'not a list'
在日志中使用
repr(),可以清晰地看到传入的参数类型和值,避免因为字符串缺失引号而误解。
再看一个常见场景:处理 JSON 数据时,repr() 可以帮你检查嵌套结构。
import json
raw_data = '{"name": "赵六", "scores": [85, 92, 78]}'
parsed = json.loads(raw_data)
print(repr(parsed)) # 输出: {'name': '赵六', 'scores': [85, 92, 78]}
即使嵌套结构复杂,repr() 也能保持清晰的层级关系,帮助你快速定位问题。
常见误区与最佳实践
误区一:认为 repr() 只用于字符串
很多人以为 repr() 只对字符串有用,其实它对所有对象都适用。即使是自定义类、函数、模块,repr() 都能提供有意义的表示。
def my_func():
pass
print(repr(my_func)) # 输出: <function my_func at 0x...>
print(repr(len)) # 输出: <built-in function len>
误区二:忽略 repr() 与 str() 的区别
在 print() 中,你通常用 str(),但在 repr() 中,它会调用 __repr__。如果只重写了 __str__,而没有重写 __repr__,那么 repr() 会退化为默认格式。
class Person:
def __init__(self, name):
self.name = name
def __str__(self):
return f"人类: {self.name}"
p = Person("钱七")
print(str(p)) # 输出: 人类: 钱七
print(repr(p)) # 输出: <__main__.Person object at 0x...>
最佳实践:在自定义类中,建议同时重写
__str__和__repr__。__repr__应该尽可能详细,适合调试。
总结:为什么你应该掌握 Python repr() 函数
repr() 函数虽然低调,却是 Python 开发者工具箱中不可或缺的一环。它不仅能让你的调试过程更高效,还能在日志、异常处理、对象序列化等场景中发挥关键作用。
记住:
repr()提供的是“可重现的”字符串表示- 它在调试中比
str()更有用 - 自定义类时,一定要考虑
__repr__的实现 - 它能帮你“看清”对象的真相,而不是只看到表象
当你下次在控制台看到一堆 <object at 0x...> 时,别慌,用 repr() 一查,真相就浮出水面。Python repr() 函数,就是你与代码对话的“显微镜”。