Python 类与面向对象

程序越来越大,光用函数会变得混乱。这时候需要"类"——把数据和操作数据的函数绑在一起,形成"对象"。这一章讲清 Python 面向对象的核心:类、对象、继承、魔术方法,以及现代 Python 的 dataclass

1. 什么是类与对象?

类(class)是创建对象的模板(蓝图),对象(object)是按这个模板造出来的具体实例。比如"Dog 类"是狗的模板,而"旺财"是一只具体的狗对象。类把数据(属性)行为(方法)封装在一起。

# 定义类:class 类名:
class Dog:
    """一只狗"""    # 类的 docstring

    # 类属性:所有实例共享
    species = "Canis lupus familiaris"

    # 构造方法:创建对象时自动调用
    def __init__(self, name, age):
        # self 代表"当前对象自己",类似 JavaScript 的 this
        self.name = name       # 实例属性
        self.age = age

    # 实例方法:第一个参数永远是 self
    def bark(self):
        return f"{self.name} 汪汪叫!"

    def describe(self):
        return f"{self.name}, {self.age} 岁"


# 创建对象(实例化)
d1 = Dog("旺财", 3)
d2 = Dog("小白", 5)

print(d1.bark())        # 旺财 汪汪叫!
print(d2.describe())    # 小白, 5 岁

# 访问属性
print(d1.name)          # 旺财
print(d1.species)       # Canis lupus familiaris (类属性,所有实例共享)

几个要点:

2. self 的本质

# self 是什么?
# self 就是"当前对象自己"的引用
# 调用 d.bark() 时,Python 自动把 d 作为 self 传进去

class Cat:
    def __init__(self, name):
        self.name = name

    def meow(self):
        # 这里的 self.name 就是当前对象的 name
        return f"{self.name}: 喵~"

c = Cat("Tom")
# 下面两种调用等价:
print(c.meow())           # 自动传 self
print(Cat.meow(c))        # 手动传 self(很少这样写,理解原理用)

# ⚠ 忘记 self 是经典错误
class Bad:
    def hello():          # ❌ 忘了 self
        print("hi")
# Bad().hello()           # TypeError: hello() takes 0 positional arguments but 1 was given

d.bark() 等价于 Dog.bark(d)——Python 自动把对象 d 作为第一个参数(即 self)传进去。理解了这点,就理解了 Python 面向对象的底层。

3. 继承

继承让子类复用父类的代码,这是面向对象的核心价值之一。Python 支持单继承和多继承,这里先看单继承。

# 继承:子类继承父类的属性和方法
class Animal:
    def __init__(self, name):
        self.name = name

    def eat(self):
        return f"{self.name} 在吃东西"

    def sleep(self):
        return f"{self.name} 在睡觉"


# 继承语法:class 子类(父类):
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)    # 调用父类的 __init__
        self.breed = breed

    def bark(self):
        return f"{self.name} 汪汪叫"

    # 重写父类方法(覆盖)
    def eat(self):
        return f"{self.name} 在啃骨头"


class Cat(Animal):
    def meow(self):
        return f"{self.name} 喵喵叫"


# 子类有父类的所有方法
d = Dog("旺财", "金毛")
print(d.eat())       # 旺财 在啃骨头 (重写后的版本)
print(d.sleep())     # 旺财 在睡觉  (继承自 Animal)
print(d.bark())      # 旺财 汪汪叫

c = Cat("汤姆")
print(c.sleep())     # 汤姆 在睡觉  (继承自 Animal)

关键点:

4. 魔术方法(dunder methods)

Python 不用 toString()equals() 这种命名,而是用双下划线方法(__xxx__),叫"魔术方法"或"dunder"(double underscore)。它们定义了对象在特定场景的行为。

# 魔术方法(dunder methods):以双下划线开头和结尾
# 它们定义了对象在特定场景下的行为

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

    # str:print(对象) 时显示什么
    def __str__(self):
        return f"《{self.title}》 by {self.author}"

    # repr:开发者调试时看到的表示
    def __repr__(self):
        return f"Book({self.title!r}, {self.author!r})"

    # len:支持 len(对象)
    def __len__(self):
        return self.pages

    # eq:支持 ==
    def __eq__(self, other):
        return self.title == other.title and self.author == other.author

    # lt:支持 < 和 sort
    def __lt__(self, other):
        return self.pages < other.pages

    # add:支持 +
    def __add__(self, other):
        return Book(f"{self.title} & {other.title}", "合集", self.pages + other.pages)


b1 = Book("Python 入门", "Guido", 300)
b2 = Book("Python 入门", "Guido", 300)
b3 = Book("算法", "CLRS", 800)

print(b1)              # 《Python 入门》 by Guido (__str__)
print(len(b1))         # 300  (__len__)
print(b1 == b2)        # True (__eq__)
print(b1 < b3)         # True (__lt__)
print(b1 + b3)         # 《Python 入门 & 算法》 by 合集 (__add__)

常用魔术方法:

5. 实例方法 / 类方法 / 静态方法

class MyClass:
    count = 0      # 类属性

    def __init__(self):
        MyClass.count += 1

    # 实例方法:第一个参数是 self,操作实例
    def instance_method(self):
        return f"实例方法,访问 {self}"

    # 类方法:第一个参数是类本身(用 cls),操作类
    @classmethod
    def how_many(cls):
        return f"已创建 {cls.count} 个实例"

    # 静态方法:不接收 self 也不接收 cls,像普通函数
    @staticmethod
    def helper():
        return "我是辅助函数,不需要实例或类"


# 创建几个实例
a = MyClass()
b = MyClass()
c = MyClass()

print(MyClass.how_many())       # 已创建 3 个实例
print(a.instance_method())      # 实例方法,访问 <__main__.MyClass object>
print(MyClass.helper())         # 我是辅助函数,不需要实例或类

# 类方法常用作"工厂方法"(替代构造函数)
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def from_birth_year(cls, name, birth_year):
        import datetime
        age = datetime.datetime.now().year - birth_year
        return cls(name, age)    # 用 cls 创建实例

p = Person.from_birth_year("小明", 2000)
print(f"{p.name}, {p.age} 岁")

6. 属性 @property

把方法变成"属性"访问(像字段一样),还能在赋值时校验。这是 Python 的优雅封装方式。

class Circle:
    def __init__(self, radius):
        self._radius = radius     # _ 前缀表示"内部使用"(约定,不强制)

    # @property:把方法变成"属性"访问
    @property
    def radius(self):
        return self._radius

    # @属性名.setter:允许赋值
    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("半径必须大于 0")
        self._radius = value

    # 只读属性:只定义 @property,不定义 setter
    @property
    def area(self):
        import math
        return math.pi * self._radius ** 2


c = Circle(5)
print(c.radius)        # 5     (像属性一样访问,不加括号)
print(c.area)          # 78.54 (计算属性)

c.radius = 10          # 走 setter,会校验
print(c.area)          # 314.16

# c.radius = -1         # ❌ ValueError: 半径必须大于 0
# c.area = 100          # ❌ AttributeError: 只读属性不能赋值

7. dataclass(Python 3.7+):数据类神器

写"主要用来存数据"的类时,dataclass 能自动生成 __init____repr____eq__,省去一堆样板代码。

# dataclass(Python 3.7+):自动生成 __init__/__repr__/__eq__
# 写"数据类"的神器,省去一堆样板代码

from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int
    score: float = 0.0    # 默认值


# 自动有了 __init__
s1 = Student("小明", 20, 85.5)
s2 = Student("小红", 22)

print(s1)                  # Student(name='小明', age=20, score=85.5)  自动 __repr__
print(s2.score)            # 0.0  用默认值

# 自动 __eq__
s3 = Student("小明", 20, 85.5)
print(s1 == s3)            # True

# 对比:不用 dataclass 要写一大堆
class StudentOld:
    def __init__(self, name, age, score=0.0):
        self.name = name
        self.age = age
        self.score = score
    def __repr__(self):
        return f"StudentOld({self.name}, ...)"
    def __eq__(self, other):
        return self.name == other.name and self.age == other.age and self.score == other.score

dataclass 是现代 Python 写数据类的首选,绝大多数场景都该用它替代手写样板。

小结

面向对象是组织大规模代码的核心。重点掌握:类与对象__init__ 与 self继承与 super重写常用魔术方法(__str__/__repr__/__eq__/__len__)、类方法/静态方法@propertydataclass。下一篇看文件操作——读写本地数据。

← 上一篇 Python 模块与包

下一篇 Python 文件操作

✈️💬