Python 类和对象:详细讲解(下篇)
15. 接口和协议(Interfaces and Protocols)
15.1 什么是接口?
接口是一个类必须遵循的规则或约定,它定义了类应该具备哪些方法,但不提供具体的实现。在 Python 中,接口常用在规定某些类必须实现特定的方法。通过接口,不同的类可以被相同的代码调用,只要它们实现了接口的要求。
15.2 协议的基本概念
协议是 Python 中的一种接口定义方式,常用于规定一个类应该具备哪些方法。协议是“非正式”的接口,它不要求显式地继承任何东西,只需要类实现了协议中的方法。
例子:定义飞行协议
代码语言:javascript复制from typing import Protocol
# 定义一个飞行协议,规定类必须有 fly 方法
class Flyer(Protocol):
def fly(self) -> None:
pass
# 定义一个 Bird 类,它实现了 fly 方法
class Bird:
def fly(self) -> None: # 注意 -> None 表示这个方法不返回任何值
print("Bird is flying")
# 定义一个 Airplane 类,它也实现了 fly 方法
class Airplane:
def fly(self) -> None:
print("Airplane is flying")
# 定义一个函数,这个函数接受任何有 fly 方法的对象
def make_fly(flyer: Flyer):
flyer.fly()
# 创建 Bird 和 Airplane 的实例,并传递给 make_fly 函数
bird = Bird()
plane = Airplane()
make_fly(bird) # 输出: Bird is flying
make_fly(plane) # 输出: Airplane is flying
详细解释
- Flyer(Protocol):
Flyer
是一个协议类,它定义了所有实现此协议的类必须具备的fly
方法。 - fly(self) -> None:
-> None
的意思是这个方法不返回任何值。None
是 Python 的一种特殊类型,表示什么都没有。 - make_fly(flyer: Flyer): 这个函数接受任何实现
Flyer
协议的对象作为参数。无论是Bird
还是Airplane
,只要它们实现了fly
方法,就可以传给这个函数。
输出示例
代码语言:javascript复制Bird is flying
Airplane is flying
这个示例展示了 Bird
和 Airplane
类如何实现同样的 fly
方法,使得它们都可以被 make_fly
函数调用。
16. 装饰器模式(Decorator Pattern)
16.1 什么是装饰器?
装饰器是 Python 中的一个强大特性,允许你在不修改原始函数的情况下,为函数添加额外的功能。装饰器本质上是一个函数,它接收另一个函数作为参数,并返回一个新的函数。
例子:简单的函数装饰器
代码语言:javascript复制# 定义一个简单的装饰器函数
def simple_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func() # 调用原始函数
print("Something is happening after the function is called.")
return wrapper
# 使用装饰器
@simple_decorator # @符号表示我们使用simple_decorator来装饰say_hello函数
def say_hello():
print("Hello!")
# 调用被装饰的函数
say_hello()
详细解释
- @simple_decorator: 这个语法糖(简便写法)表示将
say_hello
函数传递给simple_decorator
装饰器,相当于say_hello = simple_decorator(say_hello)
。 - wrapper(): 装饰器内部定义的
wrapper
函数包裹了原始的say_hello
函数,在调用say_hello
时,会先执行wrapper
内的代码。 - func():
func
是原始的say_hello
函数,通过调用它,我们在wrapper
中执行了原始函数的功能。
输出示例
代码语言:javascript复制Something is happening before the function is called.
Hello!
Something is happening after the function is called.
16.2 为什么 @ 符号放在函数定义上面?
- @符号的作用: 它是用来简化装饰器应用的。如果没有
@
符号,你需要手动将函数传给装饰器。使用@
符号时,装饰器会在函数定义之后立即应用,不需要手动传递。
代码对比
代码语言:javascript复制# 使用 @ 语法糖
@simple_decorator
def say_hello():
print("Hello!")
# 不使用 @ 语法糖,等同于上面的代码
def say_hello():
print("Hello!")
say_hello = simple_decorator(say_hello)
17. 上下文管理器(Context Managers)
17.1 什么是上下文管理器?
上下文管理器用于在一段代码运行前后自动管理资源,比如文件、网络连接等。上下文管理器确保资源在使用后被正确释放,避免资源泄漏问题。
示例:使用上下文管理器管理文件
代码语言:javascript复制# 使用上下文管理器打开文件
with open('example.txt', 'w') as file:
file.write('Hello, World!')
# 这个文件会在 with 语句块结束时自动关闭
17.2 自定义上下文管理器
你可以通过定义 __enter__
和 __exit__
方法来自定义上下文管理器。这两个方法分别在上下文管理器的进入和退出时执行。
示例:自定义上下文管理器
代码语言:javascript复制class MyContextManager:
def __enter__(self):
print("Entering the context")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Exiting the context")
if exc_type:
print(f"Exception type: {exc_type}")
print(f"Exception value: {exc_val}")
print(f"Traceback: {exc_tb}")
return True # 处理异常后,继续执行
# 使用自定义上下文管理器
with MyContextManager():
print("Inside the context")
raise ValueError("Oops!") # 故意引发一个异常
详细解释
- enter: 当你进入
with
语句时,__enter__
方法会被自动调用。你可以在这里做一些初始化操作。 - exit: 当
with
语句结束时,__exit__
方法会被调用。这个方法接受三个参数:exc_type
(异常类型)、exc_val
(异常值)、exc_tb
(异常的追踪信息)。这些参数用于处理在with
语句块内发生的任何异常。 - return True: 如果
__exit__
返回True
,异常将被抑制,不会向外抛出。如果返回False
或者不返回,异常将会被继续抛出。
输出示例
代码语言:javascript复制Entering the context
Inside the context
Exiting the context
Exception type: <class 'ValueError'>
Exception value: Oops!
Traceback: <traceback object at 0x...>
18. 元类(Metaclasses)
18.1 什么是元类?
元类是用来创建类的“类”。普通类是用来创建对象的,而元类是用来创建类的。元类通常用于自动修改类的定义或行为。
示例:简单的元类
代码语言:javascript复制class MyMeta(type):
def __new__(cls, name, bases, dct):
print(f"Creating class {name}")
return super().__new__(cls, name, bases, dct)
class MyClass(metaclass=MyMeta):
pass
# 创建 MyClass 的实例
obj = MyClass()
详细解释
- type:
type
是 Python 内置的元类,用于创建所有的类。MyMeta
继承自type
,我们通过__new__
方法来控制类的创建。 - new(cls, name, bases, dct):
__new__
是一个特殊的方法,它在__init__
之前被调用,用于创建类。cls
是元类本身,name
是类的名称,bases
是类的基类(父类),dct
是类的属性和方法的字典。 - metaclass=MyMeta: 当你定义
MyClass
时,Python 会使用MyMeta
元类来创建MyClass
类。
输出示例
代码语言:javascript复制Creating class MyClass
当你定义 MyClass
时,会输出 Creating class MyClass
,表示元类 MyMeta
正在创建 MyClass
。
19. 面向对象设计原则(SOLID Principles)
19.1 SOLID 原则简介
SOLID 是面向对象设计的五大原则,用来编写可维护、可扩展的代码。我们一条一条来看。
19.1.1 单一职责原则(SRP)
单一职责原则(Single Responsibility Principle)要求一个类应该只负责一件事。这样可以让类更简单、更易维护。
示例:单一职责原则
代码语言:javascript复制class ReportGenerator:
def generate(self):
print("Generating report")
class ReportSaver:
def save(self):
print("Saving report")
ReportGenerator
负责生成报告。ReportSaver
负责保存报告。每个类都有明确的职责,不会混在一起。
19.1.2 开放/封闭原则(OCP)
开放/封闭原则(Open/Closed Principle)要求软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。
示例:开放/封闭原则
代码语言:javascript复制class Shape:
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * (self.radius ** 2)
class Rectangle(Shape):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
- 通过继承
Shape
类,我们可以创建新的形状类,而不需要修改现有代码。
19.1.3 里氏替换原则(LSP)
里氏替换原则(Liskov Substitution Principle)要求子类对象必须能够替换基类对象,而不会影响程序的正确性。
示例:里氏替换原则
代码语言:javascript复制class Bird:
def fly(self):
print("Bird is flying")
class Penguin(Bird):
def fly(self):
raise NotImplementedError("Penguins cannot fly")
# 这种设计违反了里氏替换原则,因为企鹅不能飞,但它继承了能飞的鸟类
Penguin
不能替代Bird
,因为企鹅不会飞,这违反了里氏替换原则。
19.1.4 接口隔离原则(ISP)
接口隔离原则(Interface Segregation Principle)要求类不应该被强迫实现它不需要的接口或方法。
示例:接口隔离原则
代码语言:javascript复制class Workable:
def work(self):
pass
class Eatable:
def eat(self):
pass
class Worker(Workable, Eatable):
def work(self):
print("Working")
def eat(self):
print("Eating")
class Robot(Workable):
def work(self):
print("Working")
# Robot 只实现了它需要的接口,而不是所有接口
Robot
只实现了Workable
接口,而不需要实现Eatable
接口。
19.1.5 依赖倒置原则(DIP)
依赖倒置原则(Dependency Inversion Principle)要求高层模块不应该依赖于低层模块,而应该依赖于抽象(接口)。
示例:依赖倒置原则
代码语言:javascript复制class Keyboard:
def type(self):
return "Typing"
class Monitor:
def display(self):
return "Displaying"
class Computer:
def __init__(self, keyboard: Keyboard, monitor: Monitor):
self.keyboard = keyboard
self.monitor = monitor
def operate(self):
print(self.keyboard.type())
print(self.monitor.display())
Computer
依赖于Keyboard
和Monitor
抽象,而不是它们的具体实现,这使得更换这些组件变得容易。
以上就是关于【Python篇】Python 类和对象:详细讲解(下篇)的内容啦,各位大佬有什么问题欢迎在评论区指正,您的支持是我创作的最大动力!❤️