面向对象(二)
类的继承
-
说明
类继承是面向对象编程的核心概念之一,它允许我们基于已有的类创建新类,从而实现代码重用和层次化设计。下面详细介绍 Python 中的类继承机制。
-
继承的特点
- 代码重用:子类自动获得父类的所有功能
- 扩展性:子类可以添加新的属性和方法
- 多态性:子类可以重写父类的方法,实现不同的行为
-
基本继承(单继承)
1
2
3
4
5
6
7
8
9
10
11class ParentClass:
def parent_method(self):
print("这是父类方法")
class ChildClass(ParentClass): # 继承ParentClass
def child_method(self):
print("这是子类方法")
child = ChildClass()
child.parent_method() # 调用继承的父类方法
child.child_method() # 调用子类自己的方法 -
方法重写(Override)
子类可以重写父类的方法以提供特定实现
1
2
3
4
5
6
7
8
9
10class Animal:
def make_sound(self):
print("动物发出声音")
class Dog(Animal):
def make_sound(self): # 重写父类方法
print("汪汪汪!")
dog = Dog()
dog.make_sound() # 输出: 汪汪汪! -
调用父类方法
可以使用
super()
函数调用父类的方法1
2
3
4
5
6
7
8
9
10
11
12class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类的__init__
self.breed = breed
dog = Dog("旺财", "金毛")
print(dog.name) # 旺财
print(dog.breed) # 金毛 -
多继承
Python 支持多继承,即一个类可以继承多个父类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Father:
def skills(self):
print("编程、修理")
class Mother:
def skills(self):
print("烹饪、艺术")
class Child(Father, Mother):
def skills(self):
Father.skills(self) # 调用Father的skills方法
Mother.skills(self) # 调用Mother的skills方法
print("运动")
child = Child()
child.skills()方法解析顺序(MRO):多继承时,Python 使用 C3 线性化算法确定方法调用顺序
1
print(Child.__mro__) # 查看方法解析顺序
-
抽象基类
使用
abc
模块可以定义抽象基类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17from abc import ABC, abstractmethod
class Shape(ABC):
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self): # 必须实现抽象方法
return 3.14 * self.radius ** 2
# shape = Shape() # 会报错,不能实例化抽象类
circle = Circle(5)
print(circle.area()) -
继承的实用技巧
检查继承关系:使用
isinstance()
和issubclass()
1
2issubclass(Child, Parent) # 检查继承关系
isinstance(obj, Class) # 检查对象是否是类的实例 -
混入类(Mixin):一种多继承的特殊用法,用于添加功能而非作为主要类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14class JsonMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)
class Person:
def __init__(self, name):
self.name = name
class JsonPerson(Person, JsonMixin):
pass
p = JsonPerson("张三")
print(p.to_json()) # 输出: {"name": "张三"}
访问权限
-
说明
在 Python 中,类的成员(属性和方法)的访问权限是通过命名约定来实现的,而不是像 Java 或 C++ 那样有严格的访问修饰符(如 public、private、protected)。Python 采用的是"约定优于强制"的原则。
-
公开成员 (Public)
- 默认情况下,所有类成员都是公开的
- 可以从类的外部直接访问
- 命名方式:直接使用名称,不加任何前缀
- 示例
1
2
3
4
5
6
7
8
9
10class MyClass:
def __init__(self):
self.public_attr = "I'm public"
def public_method(self):
return "Public method"
obj = MyClass()
print(obj.public_attr) # 可以直接访问
print(obj.public_method()) # 可以直接调用
-
受保护成员 (Protected)
- 约定上应该只在类及其子类中访问,但实际上外部仍可访问
- 命名方式:以单下划线
_
开头 - 这是一种提示,告诉其他程序员"这是内部实现,不建议直接访问"
- 示例
1
2
3
4
5
6
7
8
9
10class MyClass:
def __init__(self):
self._protected_attr = "I'm protected"
def _protected_method(self):
return "Protected method"
obj = MyClass()
print(obj._protected_attr) # 仍然可以访问,但不建议
print(obj._protected_method()) # 仍然可以调用,但不建议
-
私有成员 (Private)
- 约定上只能在类内部访问,外部不能直接访问
- 命名方式:以双下划线
__
开头(但不以双下划线结尾) - Python 会对这样的名称进行名称修饰 (name mangling),使其难以直接访问
- 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class MyClass:
def __init__(self):
self.__private_attr = "I'm private"
def __private_method(self):
return "Private method"
def access_private(self):
print(self.__private_attr) # 类内部可以访问
print(self.__private_method()) # 类内部可以调用
obj = MyClass()
# print(obj.__private_attr) # 直接访问会报错
# print(obj.__private_method()) # 直接调用会报错
obj.access_private() # 可以通过公有方法间接访问
# 实际上可以通过名称修饰后的名称访问(但不推荐)
print(obj._MyClass__private_attr)
print(obj._MyClass__private_method())
-
特殊成员
- 以双下划线开头和结尾的成员是 Python 的特殊方法
- 例如
__init__
,__str__
,__len__
等 - 这些方法可以被外部调用,但通常不直接调用而是通过特定操作触发
- 示例
1
2
3
4
5
6
7
8
9
10class MyClass:
def __init__(self):
self.value = 42
def __str__(self):
return f"MyClass with value {self.value}"
obj = MyClass()
print(obj) # 自动调用 __str__
print(obj.__str__()) # 也可以直接调用(但不常见)
-
总结
访问权限 命名约定 实际可访问性 使用建议 公开 无前缀 任何地方 无限制 受保护 _
开头任何地方 仅在类和子类中使用 私有 __
开头类内部(可通过名称修饰访问) 仅在类内部使用 特殊 __
开头和结尾任何地方 按需实现特殊方法
类属性
-
说明
- 定义时,写在类中,但是方法外的属性,通常放在类的开头位置
- 类属性是 Python 面向对象编程中的一个重要概念,它是属于类本身的属性,而不是属于类的实例的属性。
-
基本概念
- 定义在类内部,但在任何方法之外
- 属于类本身,而不是类的实例
- 所有实例共享同一个类属性
- 可以通过类名或实例名访问
-
定义类属性
1
2
3
4
5class MyClass:
class_attribute = "我是类属性" # 这是一个类属性
def __init__(self):
self.instance_attribute = "我是实例属性" # 这是一个实例属性 -
访问类属性
-
通过类名直接访问:
1
print(MyClass.class_attribute) # 输出: 我是类属性
-
通过实例访问:
1
2obj = MyClass()
print(obj.class_attribute) # 输出: 我是类属性
-
-
类属性 vs 实例属性
- 类属性:
- 属于类本身
- 所有实例共享
- 修改会影响所有实例
- 实例属性:
- 属于特定实例
- 每个实例有自己的副本
- 修改只影响当前实例
- 类属性:
-
修改类属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Dog:
species = "Canis familiaris" # 类属性
def __init__(self, name):
self.name = name # 实例属性
# 修改类属性
Dog.species = "Canis lupus" # 会影响所有实例
dog1 = Dog("Buddy")
dog2 = Dog("Max")
print(dog1.species) # 输出: Canis lupus
print(dog2.species) # 输出: Canis lupus -
类属性的常见用途
-
存储类级别的常量
1
2
3
4
5
6
7
8class Circle:
PI = 3.14159 # 类属性作为常量
def __init__(self, radius):
self.radius = radius
def area(self):
return self.PI * self.radius ** 2 -
跟踪类级别的信息
1
2
3
4
5
6class Person:
count = 0 # 跟踪创建的实例数量
def __init__(self, name):
self.name = name
Person.count += 1 -
提供默认值
1
2
3
4
5class Car:
wheels = 4 # 所有汽车的默认轮子数量
def __init__(self, model):
self.model = model
-
-
注意事项
如果通过实例修改类属性,实际上会创建一个同名的实例属性,而不会修改类属性
1
2
3
4
5
6
7
8
9
10
11class Test:
class_attr = 10
t1 = Test()
t2 = Test()
t1.class_attr = 20 # 这会创建一个实例属性,不会修改类属性
print(t1.class_attr) # 输出: 20 (实例属性)
print(t2.class_attr) # 输出: 10 (类属性)
print(Test.class_attr) # 输出: 10 (类属性未改变) -
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30class Person:
# 定义类属性
nation = '中国'
def __init__(self, name):
self.name = name
self.nation = 'china'
# 通过类名访问类属性
print(Person.nation)
# 通过对象也可以访问类属性,但是不建议
# 当对象有同名的成员属性时,使用的就是成员属性
p = Person('xiaoming')
print(p.nation)
# 也可以动态添加
Person.hello = 'hello'
print(Person.hello)
# 特殊的类属性
# 表示类名字符串
print(Person.__name__)
# 表示父类构成的元组
print(Person.__bases__)
# 存储类相关的信息
print(Person.__dict__)
类方法
-
说明
- 定义时使用装饰器:classmethod
- 类方法是绑定到类而不是实例的方法,它接收类作为第一个隐式参数(通常命名为
cls
),而不是实例(self
)。
-
特点
- 使用
@classmethod
装饰器定义 - 第一个参数是类本身(通常命名为
cls
) - 可以访问和修改类状态
- 可以被类和实例调用
- 使用
-
使用场景
- 当需要操作类属性而不是实例属性时
- 实现替代构造函数(提供多种实例化方式)
- 在继承时,确保子类使用时能获取正确的类
-
示例1:语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14class MyClass:
class_attr = "类属性"
def class_method(cls):
print(f"这是一个类方法,可以访问{cls.class_attr}")
return cls() # 可以返回类的新实例
# 通过类调用
MyClass.class_method()
# 通过实例调用
obj = MyClass()
obj.class_method() -
示例2
设计一个数字类,有两个属性,能够进行加减乘除运算,要求:计算两个数的平方和
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38class Number:
def __init__(self, num1, num2):
self.num1 = num1
self.num2 = num2
def add(self):
return self.num1 + self.num2
def sub(self):
return self.num1 - self.num2
def mul(self):
return self.num1 * self.num2
def div(self):
if self.num2 == 0:
return 0
return self.num1 / self.num2
def pingfanghe(cls, num1, num2):
# 第一个数
n1 = Number(num1, num1)
# 求平方
n12 = n1.mul()
# 第二个数
n2 = Number(num2, num2)
# 求平方
n22 = n2.mul()
# 第三个数
n3 = Number(n12, n22)
# 求和
return n3.add()
print(Number.pingfanghe(3, 4))
静态方法
-
说明
- 使用装饰器:staticmethod进行修饰
- 静态方法不接收隐式的第一个参数(既不是
self
也不是cls
),它就像定义在类中的普通函数。
-
特点
- 使用
@staticmethod
装饰器定义 - 不接受自动传入的
self
或cls
参数 - 不能访问类或实例的状态
- 可以被类和实例调用
- 使用
-
使用场景
- 当方法与类相关但不依赖类或实例状态时
- 将函数组织到类中(逻辑上属于该类)
- 实现与类相关的工具函数
-
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class MathUtils:
def add(x, y):
return x + y
def multiply(x, y):
return x * y
# 通过类调用
print(MathUtils.add(2, 3)) # 输出: 5
# 通过实例调用
calc = MathUtils()
print(calc.multiply(4, 5)) # 输出: 20 -
主要区别
特性 实例方法 类方法 静态方法 装饰器 无 @classmethod @staticmethod 第一个参数 self (实例) cls (类) 无 访问实例属性 可以 不可以 不可以 访问类属性 通过self.class 可以 不可以 调用方式 必须通过实例 类或实例 类或实例
多态特性
-
说明
多态(Polymorphism)是面向对象编程的三大基本特征之一(另外两个是封装和继承),指的是同一操作作用于不同类的实例时,能产生不同的执行结果。
-
多态的核心概念
- 同一接口,不同实现:不同的类可以有相同名称的方法,但具体实现不同
- 运行时确定:在运行时根据对象的实际类型决定调用哪个方法
- 鸭子类型(Duck Typing):Python特有的多态实现方式,“如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子”
-
实现多态的方式
-
继承与方法重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "汪汪!"
class Cat(Animal):
def speak(self):
return "喵喵!"
def animal_sound(animal):
print(animal.speak())
dog = Dog()
cat = Cat()
animal_sound(dog) # 输出: 汪汪!
animal_sound(cat) # 输出: 喵喵! -
鸭子类型
Python 更推崇"鸭子类型",不强制要求继承关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Dog:
def speak(self):
return "汪汪!"
class Cat:
def speak(self):
return "喵喵!"
class Car:
def speak(self):
return "滴滴!"
def make_sound(obj):
print(obj.speak())
make_sound(Dog()) # 汪汪!
make_sound(Cat()) # 喵喵!
make_sound(Car()) # 滴滴! -
运算符重载
Python 通过特殊方法实现运算符多态
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1 + v2) # 输出: Vector(6, 8)
-
-
多态的优点
- 提高代码灵活性:可以编写更通用的代码
- 增强可扩展性:添加新类时不需要修改现有代码
- 接口统一:不同对象可以使用相同的方式进行操作
- 简化代码:减少条件判断语句的使用
属性方法
-
说明
- 使用装饰器:property 进行修饰
- 属性方法(property method)是 Python 中一种强大的特性,它允许你将方法作为属性来访问,同时可以在访问时执行额外的逻辑。这是 Python 实现 getter 和 setter 的一种优雅方式。
-
基本概念
属性方法使用
@property
装饰器来创建,通常与@<property_name>.setter
和@<property_name>.deleter
装饰器配合使用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33class MyClass:
def __init__(self, value):
self._value = value # 注意这里使用下划线前缀表示这是一个"受保护"的属性
def value(self):
"""Getter 方法"""
print("Getting value")
return self._value
def value(self, new_value):
"""Setter 方法"""
print("Setting value")
self._value = new_value
def value(self):
"""Deleter 方法"""
print("Deleting value")
del self._value
obj = MyClass(10)
# 访问属性(调用getter)
print(obj.value) # 输出: Getting value \n 10
# 设置属性(调用setter)
obj.value = 20 # 输出: Setting value
# 删除属性(调用deleter)
del obj.value # 输出: Deleting value -
优点
- 封装性:可以在访问属性时添加验证或其他逻辑
- 向后兼容:可以将普通属性转换为属性方法而不影响现有代码
- 计算属性:可以创建基于其他属性的动态计算属性
-
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35class Circle:
def __init__(self, radius):
self.radius = radius
def radius(self):
return self._radius
def radius(self, value):
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
def diameter(self):
return 2 * self._radius
def area(self):
return 3.14159 * (self._radius ** 2)
c = Circle(5)
print(c.radius) # 5
print(c.diameter) # 10
print(c.area) # 78.53975
c.radius = 10
print(c.diameter) # 20
try:
c.radius = -1 # 抛出 ValueError
except ValueError as e:
print(e) -
注意事项
- 属性方法不能接受除 self 之外的参数
- 属性方法通常用于封装内部表示,提供更友好的接口
- 过度使用属性方法可能会使代码变得复杂
函数判断
-
问题
如何判断一个对象是否可以像函数一样调用(即可调用对象)?
-
使用
callable()
函数最简单的方法是使用内置的
callable()
函数:1
2
3
4
5
6
7
8
9
10def my_func():
pass
class MyClass:
def __call__(self):
pass
print(callable(my_func)) # 输出: True
print(callable(MyClass())) # 输出: True (因为实现了 __call__)
print(callable("hello")) # 输出: False -
检查
__call__
方法你也可以检查对象是否有
__call__
方法:1
2
3
4
5def is_callable(obj):
return hasattr(obj, '__call__')
print(is_callable(lambda x: x+1)) # 输出: True
print(is_callable([1,2,3])) # 输出: False -
使用
collections.abc.Callable
(Python 3.3+)对于更正式的类型检查,可以使用
collections.abc
模块:1
2
3
4
5
6
7from collections.abc import Callable
def my_func():
return 42
print(isinstance(my_func, Callable)) # 输出: True
print(isinstance(123, Callable)) # 输出: False
-
注意事项
callable()
在 Python 3.x 中总是可用,但在 Python 2 中曾被移除过一段时间- 类本身也是可调用的(调用类会创建实例)
- 实现了
__call__
方法的类的实例是可调用的 - 方法(绑定或未绑定的)也是可调用的
-
实际应用示例
1
2
3
4
5
6
7
8def safe_call(func, *args, **kwargs):
if not callable(func):
raise TypeError(f"对象 {func} 不可调用")
return func(*args, **kwargs)
# 使用示例
safe_call(print, "Hello, World!") # 正常调用
safe_call("not a function") # 抛出 TypeError -
inspect.isfunction()
inspect.isfunction()
不能 完全替代callable()
或其它方法来判断对象是否可调用,因为它的用途更特定,只检测标准函数(function)类型,而 Python 中可调用对象的范围更广。inspect.isfunction()
的局限性-
仅识别纯函数
它只能识别用def
定义的函数或lambda
创建的函数,但会忽略其他可调用对象:1
2
3
4
5
6
7from inspect import isfunction
def my_func(): pass
print(isfunction(my_func)) # True ✅
lambda_func = lambda x: x
print(isfunction(lambda_func)) # True ✅ -
漏判其他可调用对象
以下常见可调用对象会被误判为False
:1
2
3
4
5
6
7
8
9class CallableClass:
def __call__(self): pass
obj = CallableClass()
print(isfunction(obj)) # False ❌(实际可调用)
print(isfunction(print)) # False ❌(内置函数)
print(isfunction(str.upper)) # False ❌(方法)
print(isfunction(int)) # False ❌(类是可调用的)
-
-
如何选择正确的判断方法?
方法 覆盖范围 典型用例 callable(obj)
最广泛(函数、类、方法、实现了 __call__
的对象等)通用场景,推荐优先使用 inspect.isfunction(obj)
仅纯函数( def
/lambda
)需要严格限定为函数对象时 isinstance(obj, Callable)
类似 callable()
,但更符合类型注解规范类型检查或静态分析
数据持久化存储
-
概述
Python 提供了多种数据持久化存储的方式,可以将内存中的数据保存到磁盘中,以便后续使用。
-
pickle 模块
-
简介
pickle
是 Python 标准库中最常用的序列化模块,可以将几乎任何 Python 对象转换为字节流(序列化),也可以将字节流还原为 Python 对象(反序列化)。 -
基本用法
1
2
3
4
5
6
7
8
9
10
11import pickle
# 序列化对象到文件
data = {'name': 'Alice', 'age': 25, 'scores': [88, 92, 95]}
with open('data.pkl', 'wb') as f:
pickle.dump(data, f)
# 从文件反序列化
with open('data.pkl', 'rb') as f:
loaded_data = pickle.load(f)
print(loaded_data) # {'name': 'Alice', 'age': 25, 'scores': [88, 92, 95]} -
优缺点
- 优点:可以序列化几乎所有 Python 对象,使用简单
- 缺点:不安全(可能执行任意代码),不同 Python 版本间可能不兼容
-
-
json 模块
-
简介
对于基本的数据结构(字典、列表、字符串、数字等),可以使用 JSON 格式进行序列化。
-
基本用法
1
2
3
4
5
6
7
8
9
10
11
12import json
data = {'name': 'Bob', 'age': 30, 'married': True}
# 写入 JSON 文件
with open('data.json', 'w') as f:
json.dump(data, f)
# 读取 JSON 文件
with open('data.json', 'r') as f:
loaded_data = json.load(f)
print(loaded_data) # {'name': 'Bob', 'age': 30, 'married': True} -
优缺点
- 优点:跨语言兼容,人类可读,安全
- 缺点:只能处理基本数据类型,无法直接序列化自定义类实例
-
-
shelve 模块
-
简介
shelve
提供了一个简单的键值存储,类似于字典,但数据持久化到磁盘。 -
基本用法
1
2
3
4
5
6
7
8
9
10
11import shelve
# 写入数据
with shelve.open('mydata') as db:
db['name'] = 'Charlie'
db['info'] = {'age': 35, 'job': 'developer'}
# 读取数据
with shelve.open('mydata') as db:
print(db['name']) # Charlie
print(db['info']) # {'age': 35, 'job': 'developer'}
-
-
SQLite 数据库
-
简介
Python 内置了 SQLite 支持,适合更结构化的数据存储。
-
基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import sqlite3
# 创建数据库和表
conn = sqlite3.connect('example.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS users
(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)''')
# 插入数据
c.execute("INSERT INTO users VALUES (1, 'Diana', 28)")
conn.commit()
# 查询数据
for row in c.execute('SELECT * FROM users'):
print(row) # (1, 'Diana', 28)
conn.close()
-
-
HDF5 (h5py)
-
简介
对于科学计算中的大型数值数据,HDF5 是高效的选择。
-
基本用法
1
2
3
4
5
6
7
8
9
10
11import h5py
import numpy as np
# 创建 HDF5 文件并写入数据
data = np.random.rand(100, 100)
with h5py.File('data.h5', 'w') as f:
f.create_dataset('dataset', data=data)
# 读取数据
with h5py.File('data.h5', 'r') as f:
loaded_data = f['dataset'][:]
-
-
选择建议
- 简单 Python 对象:
pickle
,注意:使用pickle
时不要反序列化不受信任的数据,这可能导致安全问题。 - 跨语言/人类可读:
json
- 键值存储:
shelve
- 结构化数据:
sqlite3
- 大型数值数据:
h5py
- 简单 Python 对象: