当前位置:首页>python>Python语言基础:27_ 面向对象编程(OOP)

Python语言基础:27_ 面向对象编程(OOP)

  • 2026-10-03 12:52:17
Python语言基础:27_ 面向对象编程(OOP)

面向对象编程(Object-Oriented Programming,简称 OOP)是 Python 中最重要的编程范式之一。学会它,你的代码会更有条理、更易复用、更易维护。


一、什么是面向对象?(先建立直觉)

1. 两种编程思维对比

过程式编程(你之前可能写过的):

# 用变量和函数来描述dog1_name = "旺财"dog1_age = 3dog2_name = "来福"dog2_age = 5def dog_bark(name):    print(f"{name} 在汪汪叫!")def dog_run(name):    print(f"{name} 在跑!")dog_bark(dog1_name)dog_run(dog2_name)

面向对象编程:

# 把"狗"当作一个整体来设计class Dog:    def __init__(self, name, age):        self.name = name        self.age = age    def bark(self):        print(f"{self.name} 在汪汪叫!")    def run(self):        print(f"{self.name} 在跑!")dog1 = Dog("旺财", 3)dog2 = Dog("来福", 5)dog1.bark()dog2.run()

💡 核心思想:把现实世界中的事物抽象成"类"(模板),然后用"类"创建出具体的"对象"(实例)。数据(属性)和行为(方法)绑定在一起。


二、类和对象:最基本的概念

概念

类比

代码中

类(Class)

设计图纸

class Dog:

对象(Object)

根据图纸造出的实物

dog1 = Dog(...)

属性(Attribute)

对象的特征(数据)

name, age

方法(Method)

对象的行为(函数)

bark(), run()


三、定义类和创建对象

1. 最简单的类

class Dog:    """这是一个狗的类"""    pass  # 暂时什么都不做,占位# 创建对象(实例化)dog1 = Dog()dog2 = Dog()print(dog1)  # <__main__.Dog object at 0x...>print(dog2)  # <__main__.Dog object at 0x...># 判断类型print(type(dog1))  # <class '__main__.Dog'>print(isinstance(dog1, Dog))  # True

📌 pass 是空语句,表示"这里先占个位置,以后填代码"。


2. 给对象添加属性(不推荐的方式)

class Dog:    passdog1 = Dog()dog1.name = "旺财"   # 直接给对象绑定属性dog1.age = 3dog2 = Dog()dog2.name = "来福"print(dog1.name)  # 旺财print(dog2.name)  # 来福

⚠️ 这种方式太随意了,不同对象的属性可能不一致。正规做法是用 __init__ 方法。


四、__init__ 构造方法(非常重要)

__init__ 是一个特殊方法,在创建对象时自动调用,用于初始化对象的属性。

class Dog:    def __init__(self, name, age):        """        self: 代表当前正在创建的对象自己        name, age: 创建对象时传入的参数        """        self.name = name   # 把传入的 name 存到对象的 name 属性中        self.age = age     # 把传入的 age 存到对象的 age 属性中# 创建对象时,参数传给 __init__dog1 = Dog("旺财", 3)dog2 = Dog("来福", 5)print(f"{dog1.name} 今年 {dog1.age} 岁")  # 旺财 今年 3 岁print(f"{dog2.name} 今年 {dog2.age} 岁")  # 来福 今年 5 岁

self 到底是什么?

class Dog:    def __init__(self, name):        print(f"self 是:{self}")   # 打印 self 的内存地址        self.name = namedog1 = Dog("旺财")print(f"dog1 是:{dog1}")          # 你会发现地址和 self 一样!

结论:self 就是对象自己。Python 会自动把调用方法的对象作为第一个参数传给 self。

💡 你也可以把 self 改成别的名字(如 me),但全世界都用 self,别特立独行。


五、实例方法

在类中定义的函数,第一个参数是 self,就是实例方法。只能通过对象来调用。

class Dog:    def __init__(self, name, age):        self.name = name        self.age = age    def bark(self):        """实例方法:狗叫"""        print(f"{self.name}:汪汪汪!")    def introduce(self):        """实例方法:自我介绍"""        print(f"大家好,我叫{self.name},今年{self.age}岁。")    def have_birthday(self):        """实例方法:过生日,年龄加 1"""        self.age += 1        print(f"{self.name} 过生日了!现在 {self.age} 岁了。")# 使用dog1 = Dog("旺财", 3)dog1.bark()         # 旺财:汪汪汪!dog1.introduce()    # 大家好,我叫旺财,今年3岁。dog1.have_birthday()  # 旺财 过生日了!现在 4 岁了。dog1.have_birthday()  # 旺财 过生日了!现在 5 岁了。

六、类属性 vs 实例属性

1. 实例属性(每个对象独有的)

class Dog:    def __init__(self, name):        self.name = name  # 实例属性dog1 = Dog("旺财")dog2 = Dog("来福")print(dog1.name)  # 旺财print(dog2.name)  # 来福

2. 类属性(所有对象共享的)

class Dog:    species = "犬科"      # 类属性:所有狗都属于犬科    count = 0             # 类属性:记录创建了多少只狗    def __init__(self, name):        self.name = name  # 实例属性        Dog.count += 1    # 每创建一只狗,计数加 1dog1 = Dog("旺财")dog2 = Dog("来福")dog3 = Dog("大黄")print(dog1.species)   # 犬科(通过对象访问)print(Dog.species)    # 犬科(通过类访问)print(Dog.count)      # 3(共创建了 3 只狗)

📌 区别:

  • 实例属性
    :每个对象自己有一份,互不影响。
  • 类属性
    :属于类本身,所有对象共享同一份。

3. 注意陷阱:不要通过对象修改类属性

class Dog:    species = "犬科"dog1 = Dog()dog2 = Dog()dog1.species = "猫科"   # ⚠️ 这不是修改类属性!而是给 dog1 新建了一个实例属性!print(dog1.species)     # 猫科(dog1 自己的实例属性)print(dog2.species)     # 犬科(类属性没变)print(Dog.species)      # 犬科(类属性没变)

正确修改类属性的方式:

Dog.species = "哺乳纲"   # 通过类名修改print(dog2.species)      # 哺乳纲

七、类方法和静态方法

1. 类方法:@classmethod

类方法的第一个参数是 cls(代表类本身),而不是 self(代表对象)。通常用于创建替代构造器或操作类属性。

class Dog:    count = 0    def __init__(self, name, age):        self.name = name        self.age = age        Dog.count += 1    @classmethod    def get_count(cls):        """类方法:获取当前创建了多少只狗"""        return cls.count    @classmethod    def from_birth_year(cls, name, birth_year):        """        类方法:替代构造器        传入出生年份,自动计算年龄        """        age = 2026 - birth_year        return cls(name, age)   # cls 就是 Dog 类# 使用类方法dog1 = Dog("旺财", 3)dog2 = Dog("来福", 5)print(Dog.get_count())   # 2# 使用替代构造器dog3 = Dog.from_birth_year("大黄", 2020)print(dog3.name)   # 大黄print(dog3.age)    # 6

2. 静态方法:@staticmethod

静态方法不接收 self 也不接收 cls,就是一个普通函数,只是逻辑上属于这个类。

class MathUtils:    @staticmethod    def add(a, b):        return a + b    @staticmethod    def is_even(n):        return n % 2 == 0# 调用方式 1:通过类名(推荐)print(MathUtils.add(3, 5))       # 8print(MathUtils.is_even(4))      # True# 调用方式 2:通过对象(也可以,但不常用)util = MathUtils()print(util.add(10, 20))          # 30

📌 什么时候用静态方法?当某个函数逻辑上属于这个类,但又不需要访问类或对象的任何属性/方法时。


八、继承(Inheritance)

继承是 OOP 的核心特性之一:子类可以继承父类的属性和方法,还可以添加或修改自己的。

1. 最基本的继承

class Animal:   # 父类(基类)    def __init__(self, name):        self.name = name    def eat(self):        print(f"{self.name} 在吃东西")    def sleep(self):        print(f"{self.name} 在睡觉")class Dog(Animal):   # 子类(派生类),继承自 Animal    def bark(self):        print(f"{self.name}:汪汪汪!")class Cat(Animal):   # 另一个子类    def meow(self):        print(f"{self.name}:喵喵喵!")# 使用dog = Dog("旺财")dog.eat()      # 旺财 在吃东西(继承自 Animal)dog.sleep()    # 旺财 在睡觉(继承自 Animal)dog.bark()     # 旺财:汪汪汪!(Dog 自己的)cat = Cat("咪咪")cat.eat()      # 咪咪 在吃东西cat.meow()     # 咪咪:喵喵喵!

💡 Dog(Animal) 表示 Dog 继承自 Animal。Dog 自动拥有 Animal 的所有方法。


2. 子类重写父类方法(Override)

子类可以重新定义父类的方法,覆盖父类的实现。

class Animal:    def __init__(self, name):        self.name = name    def speak(self):        print(f"{self.name} 发出声音")class Dog(Animal):    def speak(self):   # 重写父类的 speak 方法        print(f"{self.name}:汪汪汪!")class Cat(Animal):    def speak(self):   # 重写父类的 speak 方法        print(f"{self.name}:喵喵喵!")# 使用animals = [Dog("旺财"), Cat("咪咪"), Animal("某动物")]for animal in animals:    animal.speak()# 输出:# 旺财:汪汪汪!# 咪咪:喵喵喵!# 某动物 发出声音

3. super() —— 调用父类的方法

子类重写方法后,如果想保留父类的部分逻辑,用 super()。

class Animal:    def __init__(self, name):        print("Animal.__init__ 被调用")        self.name = name    def eat(self):        print(f"{self.name} 在吃东西")class Dog(Animal):    def __init__(self, name, breed):        # 调用父类的 __init__,让父类先初始化 name        super().__init__(name)        print("Dog.__init__ 被调用")        self.breed = breed  # 子类自己再加一个品种属性    def eat(self):        # 先执行父类的 eat        super().eat()        # 再添加子类自己的逻辑        print(f"{self.name} 吃得很开心,还摇尾巴!")dog = Dog("旺财", "金毛")# 输出:# Animal.__init__ 被调用# Dog.__init__ 被调用dog.eat()# 输出:# 旺财 在吃东西# 旺财 吃得很开心,还摇尾巴!

📌 super() 的完整写法是 super(Dog, self),但 Python 3 中可以简写为 super()。


4. 多继承(一个类继承多个父类)

Python 支持多继承,但新手建议先掌握单继承。

class Flyable:    def fly(self):        print("飞起来了!")class Swimmable:    def swim(self):        print("在游泳!")class Duck(Flyable, Swimmable):   # 鸭子既能飞又能游泳    def __init__(self, name):        self.name = namedonald = Duck("唐老鸭")donald.fly()    # 飞起来了!donald.swim()   # 在游泳!

⚠️ 多继承可能引发"菱形继承"问题(两个父类有同名方法)。Python 使用 MRO(方法解析顺序) 来决定调用哪个,可以通过 类名.__mro__ 查看顺序。


九、封装(Encapsulation)

封装就是把内部实现细节隐藏起来,只暴露必要的接口。

1. 私有属性:以双下划线开头 __name

class BankAccount:    def __init__(self, owner, balance):        self.owner = owner        self.__balance = balance   # 私有属性(双下划线开头)    def deposit(self, amount):        """存钱"""        if amount > 0:            self.__balance += amount            print(f"存入 {amount} 元,当前余额:{self.__balance}")        else:            print("存款金额必须大于 0")    def withdraw(self, amount):        """取钱"""        if 0 < amount <= self.__balance:            self.__balance -= amount            print(f"取出 {amount} 元,当前余额:{self.__balance}")        else:            print("余额不足或金额无效")    def get_balance(self):        """获取余额(对外提供的接口)"""        return self.__balance# 使用account = BankAccount("小明", 1000)# 通过正规接口操作account.deposit(500)      # 存入 500 元,当前余额:1500account.withdraw(200)     # 取出 200 元,当前余额:1300print(account.get_balance())  # 1300# 试图直接访问私有属性# print(account.__balance)   # AttributeError: 'BankAccount' object has no attribute '__balance'# ⚠️ 但其实 Python 的私有是"伪私有",可以通过特殊方式访问(不推荐):print(account._BankAccount__balance)  # 1300(名称改编 name mangling)

💡 命名改编(Name Mangling):Python 会把 __balance 改成 _BankAccount__balance,目的是防止子类意外覆盖,而不是绝对安全。


2. 私有方法

class Car:    def __init__(self):        self.__speed = 0    def accelerate(self):        self.__speed += 10        self.__check_speed()   # 调用私有方法    def __check_speed(self):   # 私有方法        """内部检查,不对外暴露"""        if self.__speed > 120:            print("警告:超速了!")        else:            print(f"当前速度:{self.__speed} km/h")car = Car()car.accelerate()   # 当前速度:10 km/hcar.accelerate()   # 当前速度:20 km/h# car.__check_speed()  # 报错,无法访问

3. 单下划线 _name(约定俗成的"保护")

class Person:    def __init__(self, name):        self._name = name   # 单下划线:表示"内部使用,不建议外部直接访问"p = Person("小明")print(p._name)  # 可以访问,但 IDE 可能会警告你

📌 单下划线只是约定,Python 不会阻止你访问。双下划线 __name 会触发名称改编,有一定保护效果。


十、多态(Polymorphism)

多态的核心理念:不同的对象,对同一消息(方法调用)做出不同的响应。

class Animal:    def speak(self):        raise NotImplementedError("子类必须实现 speak 方法")class Dog(Animal):    def speak(self):        return "汪汪汪"class Cat(Animal):    def speak(self):        return "喵喵喵"class Duck(Animal):    def speak(self):        return "嘎嘎嘎"# 多态的体现:同一个函数,传入不同对象,表现不同def animal_concert(animal):    print(f"{type(animal).__name__} 说:{animal.speak()}")# 传入不同的对象animal_concert(Dog())    # Dog 说:汪汪汪animal_concert(Cat())    # Cat 说:喵喵喵animal_concert(Duck())   # Duck 说:嘎嘎嘎

💡 Python 是动态类型语言,多态非常自然。你不需要像 Java 那样声明接口,只要对象有 speak() 方法,就能传给 animal_concert()。


十一、魔术方法(Magic Methods / Dunder Methods)

以双下划线 __ 开头和结尾的方法,是 Python 内置的特殊方法,在特定场景下自动调用。

1. __str__ 和 __repr__:对象的字符串表示

class Dog:    def __init__(self, name, age):        self.name = name        self.age = age    def __str__(self):        """给用户看的,友好描述(print 时调用)"""        return f"狗狗:{self.name},{self.age}岁"    def __repr__(self):        """给开发者看的,精确描述(交互式环境直接显示对象时调用)"""        return f"Dog(name='{self.name}', age={self.age})"dog = Dog("旺财", 3)print(dog)           # 狗狗:旺财,3岁(调用 __str__)print(str(dog))      # 狗狗:旺财,3岁print(repr(dog))     # Dog(name='旺财', age=3)(调用 __repr__)

2. __eq__:判断两个对象是否相等

class Point:    def __init__(self, x, y):        self.x = x        self.y = y    def __eq__(self, other):        """判断两个 Point 是否相等"""        if not isinstance(other, Point):            return False        return self.x == other.x and self.y == other.yp1 = Point(1, 2)p2 = Point(1, 2)p3 = Point(3, 4)print(p1 == p2)   # True(因为 __eq__ 被重写了)print(p1 == p3)   # False

📌 如果不写 __eq__,默认用 is 比较(比较内存地址),p1 == p2 会是 False。


3. __len__:让对象支持 len()

class Classroom:    def __init__(self):        self.students = []    def add_student(self, name):        self.students.append(name)    def __len__(self):        return len(self.students)cls = Classroom()cls.add_student("小明")cls.add_student("小红")print(len(cls))   # 2(调用了 __len__)

4. __getitem__ / __setitem__:让对象像字典/列表一样用 []

class MyDict:    def __init__(self):        self._data = {}    def __setitem__(self, key, value):        print(f"设置 {key} = {value}")        self._data[key] = value    def __getitem__(self, key):        print(f"获取 {key}")        return self._data.get(key, "不存在")    def __delitem__(self, key):        print(f"删除 {key}")        if key in self._data:            del self._data[key]d = MyDict()d["name"] = "小明"      # 设置 name = 小明print(d["name"])        # 获取 name → 小明print(d["age"])         # 获取 age → 不存在del d["name"]           # 删除 name

5. 常用魔术方法速查

魔术方法

触发场景

示例

__init__

创建对象时

obj = Class()

__str__

print(obj) / str(obj)

print(dog)

__repr__

交互式显示 / repr(obj)

>>> dog

__eq__

obj1 == obj2

p1 == p2

__ne__

obj1 != obj2

p1 != p2

__lt__

obj1 < obj2

p1 < p2

__len__

len(obj)

len(classroom)

__getitem__

obj[key]

d["name"]

__setitem__

obj[key] = value

d["name"] = "x"

__delitem__

del obj[key]

del d["name"]

__iter__

for x in obj

for s in classroom

__contains__

item in obj

"x" in obj

__call__

obj()(把对象当函数调用)

obj()

__add__

obj1 + obj2

p1 + p2


十二、@property 装饰器:把方法变成属性

有时候你想像访问属性一样访问方法的结果,但又希望在获取/设置时做一些校验。

class Temperature:    def __init__(self, celsius):        self._celsius = celsius    @property    def celsius(self):        """获取摄氏度(像属性一样访问)"""        return self._celsius    @celsius.setter    def celsius(self, value):        """设置摄氏度(可以加入校验)"""        if value < -273.15:            raise ValueError("温度不能低于绝对零度!")        self._celsius = value    @property    def fahrenheit(self):        """华氏度(只读属性,没有 setter)"""        return self._celsius * 9/5 + 32# 使用t = Temperature(25)# 像访问属性一样读取print(t.celsius)       # 25print(t.fahrenheit)    # 77.0# 像设置属性一样设置t.celsius = 30print(t.celsius)       # 30# 尝试设置非法值# t.celsius = -300    # ValueError: 温度不能低于绝对零度!# 华氏度是只读的# t.fahrenheit = 100  # AttributeError: can't set attribute

💡 @property 的妙处:外部代码看起来是在访问属性,实际上是在调用方法。你可以随时在方法里添加逻辑,而不需要修改外部调用代码。


十三、抽象基类(ABC):强制子类实现方法

如果你希望父类只定义接口,强制所有子类必须实现某些方法,用 abc 模块。

from abc import ABC, abstractmethodclass Animal(ABC):   # 继承 ABC,成为抽象基类    @abstractmethod    def speak(self):        """抽象方法:子类必须实现"""        pass    @abstractmethod    def move(self):        """抽象方法:子类必须实现"""        pass    def sleep(self):        """普通方法:子类可以直接继承使用"""        print("睡觉中...")class Dog(Animal):    def speak(self):        print("汪汪汪")    def move(self):        print("四条腿跑")class Fish(Animal):    def speak(self):        print("咕噜咕噜")    def move(self):        print("在水里游")# 使用dog = Dog()dog.speak()   # 汪汪汪dog.sleep()   # 睡觉中...# animal = Animal()  # 报错!不能实例化抽象基类

📌 如果子类没有实现所有 @abstractmethod 装饰的方法,也不能实例化。


十四、综合实战:设计一个图书管理系统

把上面学的知识串起来:

from abc import ABC, abstractmethodfrom datetime import datetime# ========== 抽象基类 ==========class LibraryItem(ABC):    def __init__(self, item_id, title):        self.item_id = item_id        self.title = title        self.is_borrowed = False        self.borrower = None        self.borrow_date = None    @abstractmethod    def get_item_type(self):        pass    def borrow(self, user):        if self.is_borrowed:            print(f"《{self.title}》已被借出,无法借阅")            return False        self.is_borrowed = True        self.borrower = user        self.borrow_date = datetime.now()        print(f"{user} 成功借阅《{self.title}》")        return True    def return_item(self):        if not self.is_borrowed:            print(f"《{self.title}》未被借出")            return False        days = (datetime.now() - self.borrow_date).days        self.is_borrowed = False        self.borrower = None        self.borrow_date = None        print(f"《{self.title}》已归还,借阅时长:{days} 天")        return True    def __str__(self):        status = "已借出" if self.is_borrowed else "在馆"        return f"[{self.get_item_type()}] {self.item_id}: 《{self.title}》({status})"# ========== 具体子类 ==========class Book(LibraryItem):    def __init__(self, item_id, title, author, pages):        super().__init__(item_id, title)        self.author = author        self.pages = pages    def get_item_type(self):        return "图书"    def __str__(self):        base = super().__str__()        return f"{base} | 作者:{self.author} | {self.pages}页"class Magazine(LibraryItem):    def __init__(self, item_id, title, issue_number):        super().__init__(item_id, title)        self.issue_number = issue_number    def get_item_type(self):        return "杂志"    def __str__(self):        base = super().__str__()        return f"{base} | 第{self.issue_number}期"# ========== 图书馆类 ==========class Library:    def __init__(self, name):        self.name = name        self.__items = {}      # 私有:所有馆藏        self.__borrow_history = []  # 私有:借阅历史    def add_item(self, item):        if item.item_id in self.__items:            print(f"ID {item.item_id} 已存在")            return        self.__items[item.item_id] = item        print(f"入库成功:{item.title}")    def find_item(self, item_id):        return self.__items.get(item_id)    def list_items(self):        print(f"\n===== {self.name} 馆藏列表 =====")        for item in self.__items.values():            print(item)    def borrow(self, item_id, user):        item = self.find_item(item_id)        if item and item.borrow(user):            self.__borrow_history.append({                'user': user,                'item': item.title,                'action': '借阅',                'time': datetime.now()            })    def return_item(self, item_id):        item = self.find_item(item_id)        if item:            item.return_item()    @property    def total_items(self):        return len(self.__items)    @property    def borrowed_count(self):        return sum(1 for item in self.__items.values() if item.is_borrowed)# ========== 运行演示 ==========lib = Library("市立图书馆")# 添加馆藏lib.add_item(Book("B001", "Python编程", "张三", 350))lib.add_item(Book("B002", "数据结构", "李四", 280))lib.add_item(Magazine("M001", "科学世界", 202407))# 查看馆藏lib.list_items()# [图书] B001: 《Python编程》(在馆) | 作者:张三 | 350页# [图书] B002: 《数据结构》(在馆) | 作者:李四 | 280页# [杂志] M001: 《科学世界》(在馆) | 第202407期# 借阅lib.borrow("B001", "小明")lib.borrow("M001", "小红")# 再次查看lib.list_items()# B001 显示"已借出",M001 显示"已借出"# 查看统计print(f"\n总馆藏:{lib.total_items},已借出:{lib.borrowed_count}")# 归还lib.return_item("B001")# 尝试直接操作私有属性(被封装保护)# lib.__items    # 报错

十五、面向对象设计原则(新手进阶方向)

  1. DRY
    (Don't Repeat Yourself):不要重复代码,用继承或组合复用。
  2. 组合优于继承
    :如果"有一个"关系,用组合;如果"是一个"关系,用继承。
  3. 单一职责
    :一个类只做一件事。
  4. 开闭原则
    :对扩展开放,对修改关闭。

最新文章

随机文章