Python 面向对象

Python 从设计之初就是一门面向对象的语言,创建一个类和对象非常容易。

一、核心概念

先搞清楚几个术语的含义:

  • 类(Class):用来描述具有相同属性和方法的对象的集合。比如"汽车"就是一个类
  • 对象(Object):类的具体实例。比如"我家那辆红色的 Model 3"就是一个对象
  • 属性(Attribute):对象的特征,比如汽车的颜色、品牌
  • 方法(Method):类中定义的函数,描述对象能做什么,比如汽车能"启动"、能"刹车"
  • 实例化(Instantiate):从类创建一个具体对象的过程

二、类的定义与实例化

class MyClass:
    """一个简单的类"""
    i = 12345          # 类变量(所有实例共享)

    def f(self):
        return 'hello world'

x = MyClass()          # 实例化
print(x.i)             # 12345  — 通过对象访问类变量
print(x.f())           # hello world — 调用方法

类对象支持两种操作:

  • 属性引用obj.name,访问类中定义的属性和方法
  • 实例化MyClass(),创建一个类的实例

三、init 构造方法

很多类都希望在创建对象时带上初始状态,所以会定义 __init__() 方法。实例化时会自动调用它

class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart

x = Complex(3.0, -4.5)
print(x.r, x.i)   # 3.0 -4.5

参数通过实例化操作传入:Complex(3.0, -4.5) 等价于调用 __init__(x, 3.0, -4.5),其中 x 是自动创建的实例。

__init__() 不能有返回值,返回非 None 值会报 TypeError。

四、self 参数

类的方法与普通函数的唯一区别:第一个参数必须是 self,代表类的实例本身。

class Test:
    def prt(self):
        print(self)            # <__main__.Test object at 0x...>
        print(self.__class__)  # <class '__main__.Test'>

t = Test()
t.prt()
  • self 代表当前对象的地址,不是类本身
  • self 不是关键字,换成别的名字也能跑,但约定用 self
class Test:
    def prt(runoob):       # 用 runoob 代替 self,也能正常执行
        print(runoob)
        print(runoob.__class__)

五、类变量 vs 实例变量

class people:
    name = ''       # 类变量 — 类中函数体之外定义,所有实例共享
    age = 0

    def __init__(self, n, a):
        self.name = n   # 实例变量 — 用 self.xxx 定义,每个实例独有
        self.age = a

p1 = people('Alice', 25)
p2 = people('Bob', 30)
print(p1.name)  # Alice
print(p2.name)  # Bob

关键区别: 类变量是"共享"的,实例变量是"各自独立"的。当实例变量和类变量同名时,实例变量会"遮蔽"类变量,但类变量本身不会被修改:

class Test:
    x = 10        # 类变量

t1 = Test()
t2 = Test()
t1.x = 20       # 这是给 t1 创建了一个实例变量 x,遮蔽了类变量
print(t1.x)     # 20(实例变量)
print(t2.x)     # 10(还是类变量,没被影响)
print(Test.x)   # 10

六、私有属性和私有方法

两个下划线 __ 开头的属性或方法为私有,外部不能直接访问。

6.1 私有属性

class people:
    def __init__(self, n, a, w):
        self.name = n        # 公有
        self.age = a         # 公有
        self.__weight = w    # 私有 — 外部无法直接访问

    def speak(self):
        # 类内部可以正常访问私有属性
        print(f"{self.name} 说: 我 {self.age} 岁,体重 {self.__weight} 斤")

p = people('Tom', 10, 30)
p.speak()        # Tom 说: 我 10 岁,体重 30 斤
# p.__weight     # ❌ AttributeError

6.2 私有方法

class Site:
    def __init__(self, name, url):
        self.name = name
        self.__url = url       # 私有属性

    def __foo(self):           # 私有方法
        print('这是私有方法')

    def foo(self):             # 公共方法 — 通过它间接调用私有方法
        print('这是公共方法')
        self.__foo()

x = Site('菜鸟教程', 'www.runoob.com')
x.foo()        # ✅ 正常输出
# x.__foo()   # ❌ AttributeError

6.3 名称修饰(Name Mangling)

Python 并不是真正禁止访问私有属性,只是做了一个名称重命名__weight 实际上被改名为 _类名__weight

class people:
    def __init__(self):
        self.__weight = 30

p = people()
# print(p.__weight)          # ❌ AttributeError
print(p._people__weight)     # ✅ 30(不推荐,但技术上可以访问)

所以 Python 的私有是"约定性"的,不是强制的。通过 dir() 可以看到重命名后的属性名。

七、方法重写(Override)

如果父类的方法不能满足需求,可以在子类中重新定义同名方法:

class Parent:
    def myMethod(self):
        print('调用父类方法')

class Child(Parent):
    def myMethod(self):
        print('调用子类方法')

c = Child()
c.myMethod()   # 调用子类方法

super() 可以在子类中调用父类被覆盖的方法:

class Child(Parent):
    def myMethod(self):
        print('调用子类方法')
        super(Child, self).myMethod()  # 调用父类方法

c = Child()
c.myMethod()
# 调用子类方法
# 调用父类方法

八、继承

继承是面向对象的核心特性之一,子类可以复用父类的属性和方法。

8.1 单继承

class people:
    def __init__(self, n, a, w):
        self.name = n
        self.age = a
        self.__weight = w

    def speak(self):
        print(f"{self.name} 说: 我 {self.age} 岁。")

class student(people):           # student 继承 people
    def __init__(self, n, a, w, g):
        people.__init__(self, n, a, w)   # 调用父类的构造方法
        self.grade = g

    def speak(self):             # 重写父类方法
        print(f"{self.name} 说: 我 {self.age} 岁了,我在读 {self.grade} 年级")

s = student('ken', 10, 60, 3)
s.speak()  # ken 说: 我 10 岁了,我在读 3 年级

除了 people.__init__(self, n, a, w) 这种写法,更推荐用 super()

class student(people):
    def __init__(self, n, a, w, g):
        super().__init__(n, a, w)   # 推荐写法,自动传递 self
        self.grade = g

8.2 多继承

Python 支持多继承,即一个子类可以同时继承多个父类:

class speaker():
    def __init__(self, n, t):
        self.name = n
        self.topic = t

    def speak(self):
        print(f"我叫 {self.name},我演讲的主题是 {self.topic}")

class sample(speaker, student):    # 同时继承 speaker 和 student
    def __init__(self, n, a, w, g, t):
        student.__init__(self, n, a, w, g)
        speaker.__init__(self, n, t)

test = sample("Tim", 25, 80, 4, "Python")
test.speak()
# 我叫 Tim,我演讲的主题是 Python

多继承的坑: 如果多个父类有同名方法,Python 从左到右搜索,找到第一个就用。上面 sample(speaker, student)speak 调用的是 speaker 的版本,因为 speaker 排在前面。所以父类的顺序很重要

8.3 从模块导入基类

基类可以定义在另一个模块中:

from module_name import BaseClassName

class DerivedClassName(BaseClassName):
    pass

九、类方法、静态方法、实例方法

这是面试常考的知识点,三种方法的核心区别在于参数调用方式

class Test:
    def instance_fun(self):          # 实例方法 — 第一个参数是 self
        print("实例方法", self)

    @classmethod
    def class_fun(cls):              # 类方法 — 第一个参数是 cls(类本身)
        print("类方法", cls)

    @staticmethod
    def static_fun():                # 静态方法 — 没有隐含参数
        print("静态方法")

t = Test()

# 实例方法:只能通过实例调用(通过类调用需要手动传实例)
t.instance_fun()          # ✅ 输出实例方法,打印对象地址
# Test.instance_fun()     # ❌ TypeError

# 类方法:可以通过类或实例调用
Test.class_fun()          # ✅ 输出 <class '__main__.Test'>
t.class_fun()             # ✅ 同样输出 <class '__main__.Test'>

# 静态方法:可以通过类或实例调用
Test.static_fun()         # ✅ 输出静态方法
t.static_fun()            # ✅ 同样输出静态方法
方法类型装饰器隐含参数能访问实例属性能访问类属性
实例方法self
类方法@classmethodcls
静态方法@staticmethod

类方法的典型用途:工厂方法,用来替代 __init__ 构造函数:

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

    @classmethod
    def from_string(cls, date_str):
        year, month, day = map(int, date_str.split('-'))
        return cls(year, month, day)     # 等同于 Date(year, month, day)

d = Date.from_string("2025-12-25")       # 不需要手动写 Date(2025, 12, 25)
print(d.year)  # 2025

十、@property 属性装饰器

@property 让你把方法调用变成属性访问,同时还能控制读写权限:

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):                  # getter — 读取时调用
        return self._radius

    @radius.setter
    def radius(self, value):           # setter — 赋值时调用
        if value < 0:
            raise ValueError("半径不能为负数")
        self._radius = value

    @property
    def area(self):                    # 只有 getter 没有 setter → 只读属性
        return 3.14159 * 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: 半径不能为负数
# c.area = 100     # ❌ AttributeError: can't set attribute

十一、运算符重载

Python 通过魔术方法(双下划线方法)实现运算符重载,让自定义对象支持 +-print() 等操作:

class Vector:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __str__(self):                   # print() 时调用
        return f'Vector ({self.a}, {self.b})'

    def __repr__(self):                  # 交互式环境 / repr() 调用
        return f'Vector({self.a}, {self.b})'

    def __add__(self, other):            # + 运算符
        return Vector(self.a + other.a, self.b + other.b)

    def __sub__(self, other):            # - 运算符
        return Vector(self.a - other.a, self.b - other.b)

    def __eq__(self, other):             # == 运算符
        return self.a == other.a and self.b == other.b

v1 = Vector(2, 10)
v2 = Vector(5, -2)
print(v1 + v2)   # Vector (7, 8)
print(v1 - v2)   # Vector (-3, 12)
print(v1 == v2)  # False

str vs repr 的区别

两个网站和社区笔记中都强调了这点:

  • __str__:给人看的,print(obj)str(obj) 时调用
  • __repr__:给开发者看的,交互式终端显示和 repr(obj) 时调用

如果只定义了其中一个,print() 会优先用 __str__,找不到才用 __repr__

常用魔术方法速查

方法触发方式说明
__init__MyClass()构造方法,不能有返回值
__del__对象销毁时析构方法
__str__print(obj) / str(obj)字符串表示(给人看)
__repr__repr(obj) / 交互式显示字符串表示(给开发者看)
__len__len(obj)返回长度
__getitem__obj[key]索引取值
__setitem__obj[key] = value索引赋值
__contains__item in objin 运算符
__call__obj()像函数一样调用对象
__add__obj + other加法
__sub__obj - other减法
__mul__obj * other乘法
__truediv__obj / other除法
__mod__obj % other取余
__pow__obj ** other幂运算
__eq__obj == other等于
__lt__obj < other小于
__bool__bool(obj)布尔判断

反向运算符

a + ba 没有定义 __add__ 时,Python 会尝试调用 b.__radd__(a)

class Vector:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __add__(self, other):
        if isinstance(other, int):
            return Vector(self.a + other, self.b)
        return Vector(self.a + other.a, self.b + other.b)

    def __radd__(self, other):          # 反向加法 — 处理 int + Vector 的情况
        if isinstance(other, int):
            return Vector(self.a + other, self.b)
        raise ValueError("值错误")

v = Vector(2, 10)
print(v + 5)    # Vector (7, 10)  — 调用 __add__
print(5 + v)    # Vector (7, 10)  — 调用 __radd__

十二、抽象类

当一个类不希望被直接实例化,而是作为"模板"让子类继承时,可以用 abc 模块定义抽象类:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):       # 子类必须实现这个方法
        pass

    @abstractmethod
    def perimeter(self):
        pass

# s = Shape()  # ❌ TypeError: Can't instantiate abstract class

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

    def perimeter(self):
        return 2 * 3.14159 * self.radius

c = Circle(5)
print(c.area())        # 78.54
print(c.perimeter())   # 31.42

如果子类没有实现所有抽象方法,实例化时会报错:

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def area(self):
        return self.w * self.h

    # 没有实现 perimeter

# r = Rectangle(3, 4)  # ❌ TypeError: Can't instantiate abstract class

十三、dataclass(Python 3.7+)

当类主要是用来存数据的时候,__init__ 写起来很啰嗦。@dataclass 装饰器可以自动生成 __init____repr____eq__ 等方法:

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

p1 = Point(1, 2)
p2 = Point(1, 2)

print(p1)              # Point(x=1, y=2)  — 自动生成了 __repr__
print(p1 == p2)        # True              — 自动生成了 __eq__

等价于手动写:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point(x={self.x}, y={self.y})'

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y