第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
类方法的应用场景
- 提供替代的构造方法
- 访问或修改类属性
- 实现工厂模式
静态方法¶
静态方法使用@staticmethod装饰器定义,不需要self或cls参数。
@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
何时使用静态方法
- 功能与类相关,但不需要访问实例或类的数据
- 可以独立运行的工具函数
- 逻辑上属于类,但不依赖类的状态
属性访问控制¶
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("程序继续执行...")
资源清理
析构方法常用于释放对象占用的资源,如关闭文件、释放网络连接等。
析构方法的注意事项
- 不要依赖析构方法来执行关键操作,因为Python的垃圾回收机制是不确定的
- 对于需要立即释放的资源,应该使用上下文管理器(with语句)
- 析构方法可能在程序结束时才被调用
对象的生命周期¶
创建、使用、销毁
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面向对象编程的核心概念和高级特性,包括类与对象、属性与方法、构造与析构方法、继承与多态、封装与访问控制、特殊方法以及类的高级特性。通过这些知识,你可以设计出更加灵活、可维护和可扩展的程序。
面向对象编程是一种强大的编程范式,它通过抽象、封装、继承和多态等特性,帮助我们更好地组织和管理代码。在实际开发中,合理运用面向对象的思想和技术,可以大大提高代码的质量和开发效率。