第7章 面向对象编程

面向对象编程(Object-Oriented Programming,OOP)是一种程序设计范式,它将数据和操作数据的方法组织在一起,形成对象。Python作为一门多范式编程语言,完全支持面向对象编程,并且提供了丰富的面向对象特性。本章将详细介绍Python中面向对象编程的核心概念和实践技巧。

本系列文章所使用到的示例源码:Python从入门到精通示例代码

7.1 类与对象的概念

面向对象编程简介

面向对象编程是一种编程范式,它使用”对象”来设计应用程序和计算机程序。与面向过程编程不同,面向对象编程将数据和处理数据的方法封装在一起,形成一个个独立的对象。

面向对象 vs 面向过程

面向过程编程关注的是”做什么”,程序是一系列函数的调用:

# 面向过程的方式
def calculate_area(length, width):
    return length * width

def calculate_perimeter(length, width):
    return 2 * (length + width)

length = 5
width = 3
area = calculate_area(length, width)
perimeter = calculate_perimeter(length, width)

面向对象编程关注的是”谁来做”,程序是一系列对象的交互:

# 面向对象的方式
class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def calculate_area(self):
        return self.length * self.width

    def calculate_perimeter(self):
        return 2 * (self.length + self.width)

rect = Rectangle(5, 3)
area = rect.calculate_area()
perimeter = rect.calculate_perimeter()

OOP的核心思想

面向对象编程有四个核心特性:
1. 封装(Encapsulation):将数据和方法包装在一起,隐藏内部实现细节
2. 继承(Inheritance):子类可以继承父类的属性和方法
3. 多态(Polymorphism):同一接口可以有不同的实现
4. 抽象(Abstraction):提取事物的共同特征,忽略具体细节

类的概念

类是对象的模板

类(Class)是创建对象的蓝图或模板。它定义了对象应该具有的属性和方法,但本身不是对象。可以把类比作建筑图纸,而对象就是根据图纸建造的房子。

class Car:
    """汽车类 - 定义汽车的基本属性和行为"""
    # 类属性
    wheels = 4

    def __init__(self, brand, model):
        # 实例属性
        self.brand = brand
        self.model = model
        self.speed = 0

    def accelerate(self):
        """加速方法"""
        self.speed += 10
        print(f"{self.brand} {self.model} 加速到 {self.speed} km/h")

类的抽象性

类是对现实世界事物的抽象。它提取了事物的共同特征,忽略了具体的细节。例如,”汽车”这个类抽象了所有汽车的共同特征:有品牌、型号、速度等属性,有加速、刹车等行为。

对象的概念

对象是类的实例

对象(Object)是类的具体实例。如果说类是模板,那么对象就是根据模板创建的具体实体。每个对象都有自己独特的属性值,但共享相同的方法。

# 创建汽车对象
car1 = Car("丰田", "卡罗拉")
car2 = Car("本田", "雅阁")

# 每个对象都有独立的属性
print(f"car1: {car1.brand} {car1.model}")
print(f"car2: {car2.brand} {car2.model}")

对象的状态和行为

  • 状态(State):对象的属性值,描述对象当前的情况
  • 行为(Behavior):对象的方法,描述对象能够执行的操作

对象的唯一性

每个对象在内存中都有唯一的标识,即使两个对象的属性值完全相同,它们也是不同的对象:

car1 = Car("丰田", "卡罗拉")
car2 = Car("丰田", "卡罗拉")

print(f"car1的id: {id(car1)}")
print(f"car2的id: {id(car2)}")
print(f"car1 is car2: {car1 is car2}")  # False

面向对象的优势

代码复用

通过继承机制,子类可以复用父类的代码,避免重复编写相同的功能。

模块化设计

每个类都是一个独立的模块,具有明确的职责和接口,便于团队协作开发。

易于维护和扩展

面向对象的设计使得代码结构清晰,修改和扩展功能时影响范围有限。

7.2 类的定义与实例化

类的定义语法

class关键字

Python使用class关键字来定义类:

class ClassName:
    """类的文档字符串"""
    # 类体
    pass

类名命名规范(PascalCase)

类名应该使用PascalCase(帕斯卡命名法),即每个单词的首字母都大写:

class Person:          # 正确
class BankAccount:     # 正确
class HTTPServer:      # 正确

class person:          # 不推荐
class bank_account:    # 不推荐

类体结构

类体包含类的属性和方法定义:

class Student:
    """学生类"""
    # 类属性
    school = "某某大学"

    # 构造方法
    def __init__(self, name, age):
        # 实例属性
        self.name = name
        self.age = age

    # 实例方法
    def study(self, subject):
        return f"{self.name}正在学习{subject}"

类的基本结构

让我们通过一个完整的示例来了解类的基本结构:

class Person:
    """人员类 - 演示基本类定义"""
    # 类属性
    species = "智人"
    count = 0

    def __init__(self, name, age):
        """构造方法 - 初始化对象"""
        self.name = name  # 实例属性
        self.age = age    # 实例属性
        Person.count += 1
        print(f"创建了一个人员对象: {self.name}")

    def introduce(self):
        """实例方法 - 自我介绍"""
        return f"我是{self.name},今年{self.age}岁"

    def have_birthday(self):
        """实例方法 - 过生日"""
        self.age += 1
        print(f"{self.name}过生日了,现在{self.age}岁")

    @classmethod
    def get_count(cls):
        """类方法 - 获取人员总数"""
        return f"目前共有{cls.count}个人员对象"

    @staticmethod
    def is_adult(age):
        """静态方法 - 判断是否成年"""
        return age >= 18

运行示例:

# 创建对象实例
person1 = Person("张三", 25)
person2 = Person("李四", 17)

print(f"\n类属性 species: {Person.species}")
print(f"通过实例访问类属性: {person1.species}")

print(f"\n实例方法调用:")
print(person1.introduce())
print(person2.introduce())

print(f"\n类方法调用:")
print(Person.get_count())
print(person1.get_count())  # 也可以通过实例调用

print(f"\n静态方法调用:")
print(f"张三是否成年: {Person.is_adult(person1.age)}")
print(f"李四是否成年: {Person.is_adult(person2.age)}")

输出结果:

创建了一个人员对象: 张三
创建了一个人员对象: 李四

类属性 species: 智人
通过实例访问类属性: 智人

实例方法调用:
我是张三,今年25
我是李四,今年17

类方法调用:
目前共有2个人员对象
目前共有2个人员对象

静态方法调用:
张三是否成年: True
李四是否成年: False

类变量 vs 实例变量

  • 类变量:属于类本身,被所有实例共享
  • 实例变量:属于特定的实例,每个实例都有自己的副本
print(f"\n修改实例属性:")
person1.have_birthday()
print(person1.introduce())

print(f"\n动态添加属性:")
person1.city = "北京"
print(f"张三的城市: {person1.city}")
# print(f"李四的城市: {person2.city}")  # 这会报错,因为李四没有city属性

对象的实例化

创建对象实例

使用类名加括号的方式创建对象实例:

# 语法:对象名 = 类名(参数)
person = Person("张三", 25)

内存分配

当创建对象时,Python会在内存中为对象分配空间,并返回对象的引用。

对象引用

变量存储的是对象的引用(内存地址),而不是对象本身:

print(f"\n对象的唯一性:")
print(f"person1的id: {id(person1)}")
print(f"person2的id: {id(person2)}")
print(f"person1 == person2: {person1 == person2}")
print(f"person1 is person2: {person1 is person2}")

输出结果:

对象的唯一性:
person1的id: 1835526299264
person2的id: 1835525126384
person1 == person2: False
person1 is person2: False

类与实例的关系

一个类可以创建多个实例

一个类可以创建任意数量的实例,每个实例都是独立的对象。

实例之间的独立性

不同实例的属性值是独立的,修改一个实例的属性不会影响其他实例。

7.3 属性与方法

实例属性

实例属性是属于特定对象实例的数据,每个实例都有自己的属性副本。

属性的定义和访问

class Student:
    def __init__(self, name, age):
        self.name = name    # 定义实例属性
        self.age = age

    def show_info(self):
        # 访问实例属性
        print(f"姓名: {self.name}, 年龄: {self.age}")

student = Student("小明", 18)
print(student.name)     # 直接访问属性
student.show_info()     # 通过方法访问属性

动态添加属性

Python允许在运行时为对象动态添加属性:

student.grade = "高三"   # 动态添加属性
print(f"年级: {student.grade}")

属性的删除

可以使用del语句删除对象的属性:

del student.grade       # 删除属性
# print(student.grade)  # 这会报错,因为属性已被删除

类属性

类属性属于类本身,被所有实例共享。

类属性 vs 实例属性

class Counter:
    count = 0  # 类属性

    def __init__(self, name):
        self.name = name      # 实例属性
        Counter.count += 1    # 修改类属性

c1 = Counter("计数器1")
c2 = Counter("计数器2")

print(f"总计数: {Counter.count}")  # 2
print(f"c1的计数: {c1.count}")     # 2
print(f"c2的计数: {c2.count}")     # 2

类属性的共享性

所有实例共享同一个类属性:

Counter.count = 10
print(f"c1的计数: {c1.count}")     # 10
print(f"c2的计数: {c2.count}")     # 10

实例方法

实例方法是定义在类中的函数,用于操作实例的数据。

self参数的作用

self参数代表调用方法的实例对象,它必须是实例方法的第一个参数:

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

    def area(self):
        # self指向调用此方法的实例
        return 3.14159 * self.radius ** 2

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

circle = Circle(5)
print(f"面积: {circle.area()}")           # self自动传递为circle
print(f"周长: {circle.circumference()}")

类方法

类方法使用@classmethod装饰器定义,第一个参数是cls,代表类本身。

@classmethod装饰器

class Person:
    count = 0

    def __init__(self, name):
        self.name = name
        Person.count += 1

    @classmethod
    def get_count(cls):
        """类方法 - 获取实例总数"""
        return cls.count

    @classmethod
    def create_anonymous(cls):
        """类方法 - 创建匿名对象"""
        return cls("匿名")

# 调用类方法
print(Person.get_count())  # 0

p1 = Person("张三")
p2 = Person.create_anonymous()  # 使用类方法创建对象

print(Person.get_count())  # 2

类方法的应用场景

  1. 提供替代的构造方法
  2. 访问或修改类属性
  3. 实现工厂模式

静态方法

静态方法使用@staticmethod装饰器定义,不需要selfcls参数。

@staticmethod装饰器

class MathUtils:
    @staticmethod
    def add(a, b):
        """静态方法 - 加法运算"""
        return a + b

    @staticmethod
    def is_even(number):
        """静态方法 - 判断是否为偶数"""
        return number % 2 == 0

# 调用静态方法
print(MathUtils.add(3, 5))      # 8
print(MathUtils.is_even(4))     # True

# 也可以通过实例调用
utils = MathUtils()
print(utils.add(2, 3))          # 5

何时使用静态方法

  1. 功能与类相关,但不需要访问实例或类的数据
  2. 可以独立运行的工具函数
  3. 逻辑上属于类,但不依赖类的状态

属性访问控制

Python通过命名约定来实现访问控制。

公有属性

默认情况下,所有属性都是公有的,可以从类的外部直接访问:

class Student:
    def __init__(self, name):
        self.name = name  # 公有属性

student = Student("张三")
print(student.name)  # 可以直接访问

受保护属性(约定)

以单下划线开头的属性被约定为受保护的,不应该从类外部直接访问:

class BankAccount:
    def __init__(self, balance):
        self._balance = balance  # 受保护属性

    def get_balance(self):
        return self._balance

account = BankAccount(1000)
print(account.get_balance())  # 推荐的访问方式
print(account._balance)       # 不推荐,但仍然可以访问

私有属性(名称改写)

以双下划线开头的属性会被Python进行名称改写,变成_ClassName__attribute的形式:

class BankAccount:
    def __init__(self, account_number, initial_balance=0):
        self.account_number = account_number  # 公有属性
        self._balance = initial_balance       # 受保护属性(约定)
        self.__pin = "1234"                  # 私有属性

    def deposit(self, amount):
        """存款"""
        if amount > 0:
            self._balance += amount
            print(f"存款{amount}元,余额: {self._balance}元")
        else:
            print("存款金额必须大于0")

    def withdraw(self, amount, pin):
        """取款"""
        if pin != self.__pin:
            print("密码错误")
            return False
        if amount > self._balance:
            print("余额不足")
            return False
        self._balance -= amount
        print(f"取款{amount}元,余额: {self._balance}元")
        return True

    def get_balance(self):
        """获取余额"""
        return self._balance

account = BankAccount("123456789", 1000)
print(f"账户号码: {account.account_number}")
print(f"初始余额: {account.get_balance()}元")

account.deposit(500)
account.withdraw(200, "1234")
account.withdraw(200, "0000")  # 密码错误

print(f"\n访问受保护属性(不推荐): {account._balance}")
print(f"尝试访问私有属性:")
try:
    print(account.__pin)
except AttributeError as e:
    print(f"错误: {e}")

print(f"通过名称改写访问私有属性: {account._BankAccount__pin}")

输出结果:

账户号码: 123456789
初始余额: 1000
存款500元,余额: 1500
取款200元,余额: 1300
密码错误

访问受保护属性(不推荐): 1300
尝试访问私有属性:
错误: 'BankAccount' object has no attribute '__pin'
通过名称改写访问私有属性: 1234

7.4 构造方法与析构方法

构造方法__init__()

构造方法__init__()是一个特殊方法,当创建类的新实例时自动调用。它用于初始化对象的属性。

对象初始化

class Person:
    def __init__(self, name, age):
        """构造方法 - 初始化对象属性"""
        self.name = name
        self.age = age
        print(f"创建了一个Person对象: {self.name}")

# 创建对象时自动调用__init__方法
person = Person("张三", 25)

参数传递

构造方法可以接受任意数量的参数,用于初始化对象:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width
        self.area = length * width

rect = Rectangle(5, 3)
print(f"矩形面积: {rect.area}")

构造方法的重载(通过默认参数)

Python不支持方法重载,但可以通过默认参数实现类似效果:

class Person:
    def __init__(self, name, age=18, city=None):
        self.name = name
        self.age = age
        self.city = city

# 不同方式创建对象
person1 = Person("张三")
person2 = Person("李四", 25)
person3 = Person("王五", 30, "北京")

print(f"person1: {person1.name}, {person1.age}, {person1.city}")
print(f"person2: {person2.name}, {person2.age}, {person2.city}")
print(f"person3: {person3.name}, {person3.age}, {person3.city}")

析构方法__del__()

析构方法__del__()是一个特殊方法,当对象被销毁时自动调用。它用于执行清理操作。

对象销毁时调用

class FileHandler:
    def __init__(self, filename):
        self.filename = filename
        self.file = open(filename, "w")
        print(f"文件 {filename} 已打开")

    def write(self, text):
        self.file.write(text)

    def __del__(self):
        """析构方法 - 对象销毁时调用"""
        self.file.close()
        print(f"文件 {self.filename} 已关闭")

# 创建对象
handler = FileHandler("test.txt")
handler.write("Hello, World!")

# 删除对象引用,触发垃圾回收
print("删除对象引用...")
del handler
print("程序继续执行...")

资源清理

析构方法常用于释放对象占用的资源,如关闭文件、释放网络连接等。

析构方法的注意事项

  1. 不要依赖析构方法来执行关键操作,因为Python的垃圾回收机制是不确定的
  2. 对于需要立即释放的资源,应该使用上下文管理器(with语句)
  3. 析构方法可能在程序结束时才被调用

对象的生命周期

创建、使用、销毁

Python对象的生命周期包括三个阶段:
1. 创建:调用类创建实例,执行__init__方法
2. 使用:访问和修改对象的属性,调用对象的方法
3. 销毁:对象不再被引用,垃圾回收器回收内存,执行__del__方法

引用计数

Python使用引用计数来跟踪对象的引用情况。当对象的引用计数降为0时,对象将被垃圾回收器回收。

import sys

class Demo:
    def __init__(self, name):
        self.name = name
        print(f"{self.name} 被创建")

    def __del__(self):
        print(f"{self.name} 被销毁")

# 创建对象
obj = Demo("测试对象")
print(f"引用计数: {sys.getrefcount(obj) - 1}")  # 减1是因为getrefcount函数本身会创建一个临时引用

# 创建另一个引用
obj2 = obj
print(f"引用计数: {sys.getrefcount(obj) - 1}")

# 删除一个引用
del obj
print("第一个引用已删除")

# 删除最后一个引用
print("删除最后一个引用...")
del obj2
print("程序结束")

7.5 继承与多态

继承的概念

继承是面向对象编程的核心特性之一,它允许创建一个新类(子类),继承现有类(父类)的属性和方法。

父类(基类、超类)与子类(派生类)

# 父类
class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species

    def make_sound(self):
        print("一些声音")

    def info(self):
        print(f"动物: {self.name}, 物种: {self.species}")

# 子类
class Dog(Animal):
    def __init__(self, name, breed):
        # 调用父类的构造方法
        super().__init__(name, "犬科")
        self.breed = breed

    def make_sound(self):
        print("汪汪!")

    def fetch(self):
        print(f"{self.name}在捡球")

# 创建子类对象
dog = Dog("旺财", "金毛")
dog.info()          # 继承自父类的方法
dog.make_sound()    # 重写的方法
dog.fetch()         # 子类特有的方法

is-a关系

继承表示”is-a”(是一个)关系。例如,狗是一种动物,所以Dog类继承自Animal类。

继承的语法

单继承

class 子类名(父类名):
    # 子类体
    pass

多继承

Python支持多继承,一个类可以继承多个父类:

class Flyable:
    def fly(self):
        print("飞行中...")

class Swimmable:
    def swim(self):
        print("游泳中...")

class Duck(Animal, Flyable, Swimmable):
    def __init__(self, name):
        super().__init__(name, "鸭科")

    def make_sound(self):
        print("嘎嘎!")

duck = Duck("唐老鸭")
duck.info()       # 来自Animal
duck.make_sound() # 重写的方法
duck.fly()        # 来自Flyable
duck.swim()       # 来自Swimmable

方法重写(Override)

子类可以重写(覆盖)父类的方法,提供特定的实现。

重写父类方法

class Cat(Animal):
    def __init__(self, name, color):
        super().__init__(name, "猫科")
        self.color = color

    def make_sound(self):
        print("喵喵!")

    def info(self):
        # 重写父类的info方法
        print(f"猫咪: {self.name}, 颜色: {self.color}, 物种: {self.species}")

cat = Cat("咪咪", "橘色")
cat.info()
cat.make_sound()

super()函数的使用

super()函数用于调用父类的方法:

class Bird(Animal):
    def __init__(self, name, wingspan):
        super().__init__(name, "鸟类")
        self.wingspan = wingspan

    def info(self):
        # 先调用父类的info方法
        super().info()
        # 再添加子类特有的信息
        print(f"翼展: {self.wingspan}厘米")

bird = Bird("小鸟", 15)
bird.info()

方法解析顺序(MRO)

方法解析顺序决定了多继承时方法的查找顺序:

print(f"Duck的MRO: {Duck.__mro__}")

输出结果:

Duck的MRO: (<class '__main__.Duck'>, <class '__main__.Animal'>, <class '__main__.Flyable'>, <class '__main__.Swimmable'>, <class 'object'>)

多态的概念

多态是指同一个操作作用于不同的对象,可以有不同的解释和执行方式。

同一接口,不同实现

def make_speak(animal):
    """多态函数 - 接受任何有make_sound方法的对象"""
    animal.make_sound()

# 不同类型的对象
dog = Dog("旺财", "金毛")
cat = Cat("咪咪", "橘色")
bird = Bird("小鸟", 15)

# 同一个函数作用于不同对象
make_speak(dog)  # 输出: 汪汪!
make_speak(cat)  # 输出: 喵喵!
make_speak(bird) # 输出: 一些声音

鸭子类型(Duck Typing)

Python的多态基于”鸭子类型”:如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子。

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

    def make_sound(self):
        print("机器人: 嘟嘟嘟!")

# Robot类不是Animal的子类,但有make_sound方法
robot = Robot("机器人")
make_speak(robot)  # 输出: 机器人: 嘟嘟嘟!

isinstance()和issubclass()

类型检查

isinstance()函数用于检查对象是否是指定类或其子类的实例:

print(f"dog是Animal的实例? {isinstance(dog, Animal)}")  # True
print(f"cat是Dog的实例? {isinstance(cat, Dog)}")        # False
print(f"robot是Animal的实例? {isinstance(robot, Animal)}")  # False

继承关系检查

issubclass()函数用于检查一个类是否是另一个类的子类:

print(f"Dog是Animal的子类? {issubclass(Dog, Animal)}")  # True
print(f"Cat是Dog的子类? {issubclass(Cat, Dog)}")        # False
print(f"Duck是Flyable的子类? {issubclass(Duck, Flyable)}")  # True

7.6 封装与访问控制

封装的概念

封装是面向对象编程的核心特性之一,它将数据和操作数据的方法绑定在一起,对外部隐藏实现细节。

数据隐藏

封装通过限制对对象内部数据的直接访问,提供了数据的安全性。

接口与实现分离

封装将对象的接口(公共方法)与实现(内部数据和私有方法)分离,使得代码更易于维护。

Python中的访问控制

前面我们已经介绍了Python中的访问控制约定:
- 公有成员:普通命名,如name
- 受保护成员:单下划线前缀,如_balance
- 私有成员:双下划线前缀,如__pin

属性的getter和setter

Python提供了@property装饰器,用于创建属性的getter和setter方法。

@property装饰器

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius

    @property
    def celsius(self):
        """摄氏度 - getter方法"""
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        """摄氏度 - setter方法"""
        if value < -273.15:
            raise ValueError("温度不能低于绝对零度")
        self._celsius = value

    @property
    def fahrenheit(self):
        """华氏度 - 只读属性"""
        return self._celsius * 9/5 + 32

    @property
    def kelvin(self):
        """开尔文温度 - 只读属性"""
        return self._celsius + 273.15

# 使用属性
temp = Temperature(25)
print(f"摄氏度: {temp.celsius}°C")
print(f"华氏度: {temp.fahrenheit}°F")
print(f"开尔文: {temp.kelvin}K")

# 修改属性
temp.celsius = 30
print(f"新摄氏度: {temp.celsius}°C")
print(f"新华氏度: {temp.fahrenheit}°F")

# 尝试设置无效值
try:
    temp.celsius = -300
except ValueError as e:
    print(f"错误: {e}")

# 尝试修改只读属性
try:
    temp.fahrenheit = 100
except AttributeError as e:
    print(f"错误: {e}")

输出结果:

摄氏度: 25°C
华氏度: 77.0°F
开尔文: 298.15K
新摄氏度: 30°C
新华氏度: 86.0°F
错误: 温度不能低于绝对零度
错误: can't set attribute 'fahrenheit'

属性的读取控制

@property装饰器将方法转换为只读属性:

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

    @property
    def radius(self):
        return self._radius

    @property
    def area(self):
        return 3.14159 * self._radius ** 2

    @property
    def circumference(self):
        return 2 * 3.14159 * self._radius

circle = Circle(5)
print(f"半径: {circle.radius}")
print(f"面积: {circle.area}")
print(f"周长: {circle.circumference}")

属性的设置控制

@property.setter装饰器用于定义属性的setter方法:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError("名字必须是字符串")
        if len(value) < 2:
            raise ValueError("名字长度至少为2")
        self._name = value

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if not isinstance(value, int):
            raise TypeError("年龄必须是整数")
        if value < 0 or value > 150:
            raise ValueError("年龄必须在0到150之间")
        self._age = value

person = Person("张三", 25)
print(f"姓名: {person.name}, 年龄: {person.age}")

# 修改属性
person.name = "李四"
person.age = 30
print(f"新姓名: {person.name}, 新年龄: {person.age}")

# 尝试设置无效值
try:
    person.name = "A"  # 名字太短
except ValueError as e:
    print(f"错误: {e}")

try:
    person.age = 200  # 年龄超出范围
except ValueError as e:
    print(f"错误: {e}")

7.7 特殊方法(魔术方法)

特殊方法概述

特殊方法(也称为魔术方法)是Python中以双下划线开头和结尾的方法,如__init____str__等。这些方法为类提供了特殊的行为,如运算符重载、对象表示等。

常用特殊方法

str()和__repr__()

__str__()__repr__()方法用于提供对象的字符串表示:

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

    def __str__(self):
        """返回对象的字符串表示,用于str()和print()"""
        return f"Point({self.x}, {self.y})"

    def __repr__(self):
        """返回对象的官方字符串表示,用于repr()和交互式环境"""
        return f"Point(x={self.x}, y={self.y})"

point = Point(3, 4)
print(f"str(point): {str(point)}")
print(f"repr(point): {repr(point)}")

len()

__len__()方法使对象支持len()函数:

class MyList:
    def __init__(self, items):
        self.items = items

    def __len__(self):
        """返回对象的长度"""
        return len(self.items)

my_list = MyList([1, 2, 3, 4, 5])
print(f"len(my_list): {len(my_list)}")

getitem()和__setitem__()

__getitem__()__setitem__()方法使对象支持索引访问:

class MyDict:
    def __init__(self):
        self.data = {}

    def __getitem__(self, key):
        """获取指定键的值"""
        return self.data.get(key, None)

    def __setitem__(self, key, value):
        """设置指定键的值"""
        self.data[key] = value

my_dict = MyDict()
my_dict["name"] = "张三"
my_dict["age"] = 25

print(f"my_dict['name']: {my_dict['name']}")
print(f"my_dict['age']: {my_dict['age']}")

call()

__call__()方法使对象可调用,像函数一样:

class Adder:
    def __init__(self, n):
        self.n = n

    def __call__(self, x):
        """使对象可调用"""
        return self.n + x

add5 = Adder(5)
print(f"add5(10): {add5(10)}")
print(f"add5(20): {add5(20)}")

eq()、lt()等比较方法

这些方法用于重载比较运算符:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        """重载==运算符"""
        if not isinstance(other, Person):
            return False
        return self.name == other.name and self.age == other.age

    def __lt__(self, other):
        """重载<运算符"""
        if not isinstance(other, Person):
            return NotImplemented
        return self.age < other.age

person1 = Person("张三", 25)
person2 = Person("张三", 25)
person3 = Person("李四", 30)

print(f"person1 == person2: {person1 == person2}")
print(f"person1 == person3: {person1 == person3}")
print(f"person1 < person3: {person1 < person3}")

add()、sub()等算术方法

这些方法用于重载算术运算符:

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

    def __str__(self):
        return f"Vector({self.x}, {self.y})"

    def __add__(self, other):
        """重载+运算符"""
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        """重载-运算符"""
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(self.x - other.x, self.y - other.y)

    def __mul__(self, scalar):
        """重载*运算符"""
        if not isinstance(scalar, (int, float)):
            return NotImplemented
        return Vector(self.x * scalar, self.y * scalar)

v1 = Vector(3, 4)
v2 = Vector(1, 2)

print(f"v1: {v1}")
print(f"v2: {v2}")
print(f"v1 + v2: {v1 + v2}")
print(f"v1 - v2: {v1 - v2}")
print(f"v1 * 2: {v1 * 2}")

enter()和__exit__()

__enter__()__exit__()方法实现上下文管理器协议,支持with语句:

class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        """进入上下文时调用"""
        self.file = open(self.filename, self.mode)
        print(f"打开文件: {self.filename}")
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        """退出上下文时调用"""
        if self.file:
            self.file.close()
            print(f"关闭文件: {self.filename}")
        # 返回True表示异常已处理,False表示异常需要向外传播
        return False

# 使用自定义上下文管理器
with FileManager("test.txt", "w") as f:
    f.write("Hello, World!")
    print("写入文件")

print("上下文已退出")

实际运行结果:

打开文件: test.txt
写入文件
关闭文件: test.txt
上下文已退出

7.8 类的高级特性

类装饰器

类装饰器是应用于类定义的装饰器,用于修改类的行为。

def add_str_method(cls):
    """类装饰器 - 添加__str__方法"""
    def __str__(self):
        attrs = ", ".join(f"{k}={v}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"

    cls.__str__ = __str__
    return cls

@add_str_method
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

student = Student("张三", 20)
print(student)  # 输出: Student(name=张三, age=20)

描述符

描述符是实现了__get____set____delete__方法的类,用于控制属性的访问。

class Descriptor:
    """描述符类 - 验证字符串属性"""
    def __init__(self, name=None):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name, None)

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError(f"{self.name}必须是字符串")
        if len(value) < 2:
            raise ValueError(f"{self.name}长度至少为2")
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        if self.name in instance.__dict__:
            del instance.__dict__[self.name]

class Person:
    name = Descriptor("name")

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

# 使用描述符
person = Person("张三")
print(f"姓名: {person.name}")

# 尝试设置无效值
try:
    person.name = "A"  # 名字太短
except ValueError as e:
    print(f"错误: {e}")

try:
    person.name = 123  # 不是字符串
except TypeError as e:
    print(f"错误: {e}")

数据类(Python 3.7+)

数据类是Python 3.7引入的特性,用于简化创建主要用于存储数据的类。

from dataclasses import dataclass

@dataclass
class Book:
    title: str
    author: str
    pages: int
    price: float = 0.0

    def price_per_page(self):
        return self.price / self.pages if self.pages > 0 else 0

# 创建数据类对象
book1 = Book("Python编程", "张三", 300, 59.9)
book2 = Book("Python编程", "张三", 300, 59.9)
book3 = Book("Java编程", "李四", 400, 69.9)

print(f"book1: {book1}")
print(f"book1 == book2: {book1 == book2}")
print(f"book1 == book3: {book1 == book3}")
print(f"每页价格: {book1.price_per_page():.3f}元")

输出结果:

book1: Book(title='Python编程', author='张三', pages=300, price=59.9)
book1 == book2: True
book1 == book3: False
每页价格: 0.200

抽象基类

抽象基类(ABC)用于定义接口,强制子类实现特定的方法。

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        """计算面积"""
        pass

    @abstractmethod
    def perimeter(self):
        """计算周长"""
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

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

# 尝试实例化抽象类
try:
    shape = Shape()
except TypeError as e:
    print(f"错误: {e}")

# 实例化具体子类
rect = Rectangle(5, 3)
circle = Circle(4)

print(f"矩形面积: {rect.area()}")
print(f"矩形周长: {rect.perimeter()}")
print(f"圆形面积: {circle.area()}")
print(f"圆形周长: {circle.perimeter()}")

输出结果:

错误: Can't instantiate abstract class Shape with abstract methods area, perimeter
矩形面积: 15
矩形周长: 16
圆形面积: 50.26544
圆形周长: 25.13272

总结

本章我们详细介绍了Python面向对象编程的核心概念和高级特性,包括类与对象、属性与方法、构造与析构方法、继承与多态、封装与访问控制、特殊方法以及类的高级特性。通过这些知识,你可以设计出更加灵活、可维护和可扩展的程序。

面向对象编程是一种强大的编程范式,它通过抽象、封装、继承和多态等特性,帮助我们更好地组织和管理代码。在实际开发中,合理运用面向对象的思想和技术,可以大大提高代码的质量和开发效率。

小夜