python的class

18 阅读3分钟

这是一个专门针对 __str____repr__ 进行了代码深挖,并整合了所有 Class 核心要点的完整总结。


一、 核心:__str____repr__ 深度解析

在 Python 中,如果你直接打印一个类实例,通常会看到类似 <__main__.Student object at 0x102...> 这样不直观的信息。为了让输出更有意义,我们需要这两个魔术方法。

1. 代码演示

class Book:
    def __init__(self, title, author, price):
        self.title = title
        self.author = author
        self.price = price

    # 【面向用户】让 print() 更好看
    def __str__(self):
        return f"《{self.title}》- {self.author} 著"

    # 【面向开发】让调试更精准,通常返回“创建该对象的代码模样”
    def __repr__(self):
        return f"Book(title='{self.title}', author='{self.author}', price={self.price})"

# --- 测试代码 ---
b = Book("Python入门", "Guido", 59.9)

# 场景 1:使用 print() 或 str()
print(b)          # 输出: 《Python入门》- Guido 著
print(str(b))     # 输出: 《Python入门》- Guido 著

# 场景 2:在交互式命令行直接输入变量名 (或者调试器查看)
# >>> b
# 输出: Book(title='Python入门', author='Guido', price=59.9)

# 场景 3:当对象存在于容器(如列表)中时
bookshelf = [b, Book("流浪地球", "刘慈欣", 45.0)]
print(bookshelf)  
# 输出: [Book(title='Python入门', author='Guido', price=59.9), Book(title='流浪地球', author='刘慈欣', price=45.0)]
# 注意:容器内部打印使用的是 __repr__,而不是 __str__!

2. 关键区别总结

方法触发时机目标受众编写建议
__str__print(), str()终端用户追求简单、易读,像一句话。
__repr__直接输入变量、repr()列表/字典内部显示开发者/调试追求无歧义,最好写成 类名(参数=值)

注:如果你只写 __repr__ 而不写 __str__,Python 在需要打印时会“借用” __repr__;但反过来不行。


二、 Python 类(Class)知识全图谱

1. 结构与生命周期

  • class ClassName: 使用大驼峰命名。
  • __init__(self, ...):初始化方法。不能返回任何值(除了 None)。
  • self:必须作为第一个参数,代表当前的实例。调用时由 Python 自动传入。

2. 两种属性(极其重要)

  • 实例属性:在 __init__ 里定义的 self.x。每个对象独有一份。
  • 类属性:在所有函数外定义的变量。所有对象共享一份。
    • 大坑: 永远不要把 listdict 作为类属性,除非你真的希望所有人在一个篮子里放东西。

3. 解决“多个 init”问题

Python 不支持函数重载(同名不同参),我们使用 @classmethod 提供额外的初始化路径:

class Date:
    def __init__(self, year, month, day):
        self.year, self.month, self.day = year, month, day

    @classmethod
    def from_string(cls, date_str): # 通过 "2023-10-01" 初始化
        y, m, d = map(int, date_str.split('-'))
        return cls(y, m, d) # cls 代表 Date 类本身

4. 三种方法对比

  • 实例方法:带 self,操作对象数据。
  • 类方法 (@classmethod):带 cls,通常用于工厂模式(创建对象)。
  • 静态方法 (@staticmethod):不带 selfcls,只是逻辑上属于这个类(类似普通函数)。

5. 面向对象三大特性

  • 继承class Child(Parent):。使用 super().__init__() 激活父类属性。
  • 封装:使用 __name(双下划线)定义私有属性,保护数据。
  • 多态:不同类实现同名方法(如 draw()),调用者无需关心对象具体是谁。

三、 避坑指南总结

  1. 漏写 self
    • 内部调用方法必须写 self.method()
    • 访问内部属性必须写 self.attr
  2. 默认参数陷阱
    • def __init__(self, items=[]) 是极其危险的!所有实例会共用同一个列表。应该用 items=None,然后在函数内赋值 self.items = []
  3. 继承被覆盖
    • 如果子类写了 __init__,父类的 __init__ 就不会自动执行,必须手动调 super()
  4. 混淆类属性与实例属性
    • 修改类属性会通过 ClassName.attr = val 改变所有人。
    • 通过 self.attr = val 修改,如果原本是类属性,会变成在该实例上新建一个同名实例属性,掩盖掉类属性。

四、 最后的建议

  • 什么时候写类? 当你发现有一堆变量(数据)和一堆函数(行为)总是形影不离时,就该把它们揉成一个类。
  • 好的类长什么样? 属性在 __init__ 里一目了然,有清晰的 __repr__ 方便调试,复杂的初始化交给 @classmethod