🧱 18. 面向对象编程

本章概述

学习 Python 面向对象编程:类、对象、继承、多态等。 预计学习时间:80 分钟


18.1 什么是面向对象

面向对象编程(OOP)是一种编程思想,把数据和操作数据的方法封装在一起,形成「对象」。

类和对象

  • 类(class):模板,描述一类事物的共同特征和行为
  • 对象(object):根据类创建的具体实例

示例

# 定义类
class Person:
    def __init__(self, name, age):
        self.name = name  # 属性
        self.age = age    # 属性
 
    def say_hello(self):  # 方法
        print(f"你好,我是{self.name},今年{self.age}岁")
 
# 创建对象
p1 = Person("张三", 18)
p2 = Person("李四", 20)
 
# 调用方法
p1.say_hello()  # 你好,我是张三,今年18岁
p2.say_hello()  # 你好,我是李四,今年20岁
 
# 访问属性
print(p1.name)  # 张三
print(p2.age)   # 20

类命名规范

  • 大驼峰命名法(每个单词首字母大写)
  • 例如:Person, Student, MyClass

18.2 init 构造方法

创建对象时自动调用的方法,用来初始化属性。

self 参数

self 代表当前对象本身,类似其他语言的 this

class Person:
    def __init__(self, name, age):
        self.name = name  # self.name 是对象的属性
        self.age = age
 
# 创建对象时,参数传给 __init__
p = Person("Tom", 18)

注意

  • 方法的第一个参数必须是 self
  • 调用方法时不用传 self,Python 自动传
  • self 名字可以改,但约定俗成用 self

18.3 属性和方法

实例属性

每个对象都有自己的属性,互不影响:

p1 = Person("张三", 18)
p2 = Person("李四", 20)
 
print(p1.name)  # 张三
print(p2.name)  # 李四(各自的属性)

类属性

所有对象共享的属性,定义在类里面、方法外面:

class Person:
    count = 0  # 类属性,统计创建了多少个对象
 
    def __init__(self, name):
        self.name = name
        Person.count += 1  # 访问类属性
 
p1 = Person("张三")
p2 = Person("李四")
print(Person.count)  # 2

实例方法

最常见的方法,第一个参数是 self,可以访问实例属性。

class Person:
    def __init__(self, name):
        self.name = name
 
    def say_hello(self):  # 实例方法
        print(f"你好,我是{self.name}")

类方法

@classmethod 装饰器,第一个参数是 cls(类本身)。

class Person:
    count = 0
 
    def __init__(self, name):
        self.name = name
        Person.count += 1
 
    @classmethod
    def get_count(cls):  # 类方法
        return cls.count
 
print(Person.get_count())  # 0

静态方法

@staticmethod 装饰器,不需要 selfcls 参数。

class MathUtils:
    @staticmethod
    def add(a, b):  # 静态方法
        return a + b
 
print(MathUtils.add(3, 5))  # 8

三种方法的区别

  • 实例方法:需要访问实例属性时用
  • 类方法:需要访问类属性或创建对象时用
  • 静态方法:和类相关但不需要访问类/实例属性时用

18.4 访问控制

Python 没有真正的私有属性,靠命名约定。

公有属性/方法

正常命名,外部可以直接访问:

class Person:
    def __init__(self, name):
        self.name = name  # 公有属性
 
p = Person("Tom")
print(p.name)  # 可以直接访问

受保护的 _xxx

单下划线开头,表示「受保护的」,建议不要外部访问,但还是能访问到:

class Person:
    def __init__(self, name):
        self._name = name  # 受保护的
 
p = Person("Tom")
print(p._name)  # 能访问,但不建议

私有的 __xxx

双下划线开头,Python 会改名(名称修饰),外部不能直接访问:

class Person:
    def __init__(self, name):
        self.__name = name  # 私有的
 
p = Person("Tom")
# print(p.__name)  # ❌ 报错,访问不到
 
# 其实可以通过改名后的名字访问(不推荐)
print(p._Person__name)  # Tom

Python 的哲学

Python 信奉「我们都是成年人」,靠约定而不是强制。 单下划线是「请勿打扰」,双下划线是「请勿擅入」,但都不是绝对的。


18.5 属性装饰器 @property

把方法变成属性一样调用。

基本用法

class Person:
    def __init__(self, name):
        self._name = name
 
    @property
    def name(self):  # getter
        return self._name
 
    @name.setter
    def name(self, value):  # setter
        if len(value) >= 2:
            self._name = value
        else:
            raise ValueError("名字太短")
 
p = Person("Tom")
print(p.name)    # 像属性一样访问
p.name = "Jerry" # 像属性一样修改

为什么用 property?

  • 可以加验证逻辑
  • 可以做计算属性
  • 接口兼容(原来是属性,后来改成方法不影响调用方)

只读属性

只写 getter,不写 setter,就是只读的:

class Circle:
    def __init__(self, radius):
        self.radius = radius
 
    @property
    def area(self):  # 只读属性
        return 3.14 * self.radius ** 2
 
c = Circle(5)
print(c.area)  # 78.5
# c.area = 100  # ❌ 报错,不能修改

18.6 继承

一个类继承另一个类,拥有父类的属性和方法。

基本用法

# 父类(基类)
class Animal:
    def __init__(self, name):
        self.name = name
 
    def eat(self):
        print(f"{self.name}在吃东西")
 
# 子类(派生类),继承 Animal
class Dog(Animal):
    def bark(self):
        print(f"{self.name}在汪汪叫")
 
dog = Dog("旺财")
dog.eat()   # 继承来的方法
dog.bark()  # 自己的方法

方法重写

子类可以重写父类的方法:

class Animal:
    def speak(self):
        print("动物在叫")
 
class Dog(Animal):
    def speak(self):  # 重写父类方法
        print("汪汪汪")
 
class Cat(Animal):
    def speak(self):  # 重写父类方法
        print("喵喵喵")
 
Dog().speak()  # 汪汪汪
Cat().speak()  # 喵喵喵

super() 调用父类方法

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
class Student(Person):
    def __init__(self, name, age, grade):
        super().__init__(name, age)  # 调用父类的__init__
        self.grade = grade
 
s = Student("张三", 18, 3)
print(s.name, s.age, s.grade)

多层继承

class A:
    pass
 
class B(A):  # B继承A
    pass
 
class C(B):  # C继承B,间接继承A
    pass

多重继承

一个类继承多个父类:

class A:
    def method_a(self):
        print("A的方法")
 
class B:
    def method_b(self):
        print("B的方法")
 
class C(A, B):  # 同时继承A和B
    pass
 
c = C()
c.method_a()  # A的方法
c.method_b()  # B的方法

多重继承注意

  • 多重继承会让代码变复杂,尽量少用
  • 方法名冲突时,按继承顺序查找(MRO)
  • 可以用 C.__mro__ 查看方法解析顺序

18.7 多态

同一个方法,不同对象调用有不同的表现。

class Animal:
    def speak(self):
        pass
 
class Dog(Animal):
    def speak(self):
        print("汪汪汪")
 
class Cat(Animal):
    def speak(self):
        print("喵喵喵")
 
def make_speak(animal):
    animal.speak()  # 同一个方法,不同表现
 
make_speak(Dog())  # 汪汪汪
make_speak(Cat())  # 喵喵喵

Python 的多态

Python 是动态语言,不要求继承关系,只要有同名方法就行(鸭子类型)。

class Dog:
    def speak(self):
        print("汪汪汪")
 
class Cat:
    def speak(self):
        print("喵喵喵")
 
# 不用继承,只要有speak方法就行

18.8 特殊方法(魔术方法)

双下划线开头和结尾的方法,有特殊用途。

str 字符串表示

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    def __str__(self):
        return f"Person(name={self.name}, age={self.age})"
 
p = Person("Tom", 18)
print(p)  # Person(name=Tom, age=18)

repr 官方表示

def __repr__(self):
    return f"Person('{self.name}', {self.age})"

str vs repr

  • __str__:给人看的,友好的字符串
  • __repr__:给程序看的,能还原对象的字符串
  • 没有 __str__ 时会用 __repr__

len 长度

class MyList:
    def __init__(self, data):
        self.data = data
 
    def __len__(self):
        return len(self.data)
 
lst = MyList([1, 2, 3])
print(len(lst))  # 3

运算符重载

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
    def __add__(self, other):  # + 运算符
        return Point(self.x + other.x, self.y + other.y)
 
    def __str__(self):
        return f"({self.x}, {self.y})"
 
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 + p2)  # (4, 6)

常用特殊方法

方法说明调用方式
__init__构造方法obj = Class()
__str__字符串表示str(obj), print(obj)
__repr__官方表示repr(obj)
__len__长度len(obj)
__add__加法a + b
__sub__减法a - b
__eq__等于a == b
__lt__小于a < b
__getitem__索引取值obj[key]
__setitem__索引赋值obj[key] = value
__call__调用对象obj()

18.9 常用概念

封装

把数据和操作数据的方法放在一起,隐藏内部实现,只暴露接口。

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # 私有,外部不能直接改
 
    def deposit(self, amount):   # 公开方法
        if amount > 0:
            self.__balance += amount
 
    def withdraw(self, amount):  # 公开方法
        if 0 < amount <= self.__balance:
            self.__balance -= amount
 
    def get_balance(self):       # 公开方法
        return self.__balance

继承

子类复用父类的代码,见 18.6 节。

多态

同一个接口,不同实现,见 18.7 节。

面向对象三大特性

封装、继承、多态


🔗 相关章节


📝 我的笔记

在这里记录你的理解、疑问和练习代码

# 你的练习代码
 

✅ 本章检查清单

  • 理解类和对象的概念
  • 会定义类和创建对象
  • 掌握 init 构造方法
  • 理解实例属性和类属性
  • 理解实例方法、类方法、静态方法
  • 了解访问控制(公有、受保护、私有)
  • 会使用 @property 装饰器
  • 掌握继承和方法重写
  • 理解多态
  • 了解常用特殊方法