Chapter9_Web应用程序
1、解释下如下代码,重点难点地方请详细解释 [from example9_9.py 修改版]
import asyncio from starlette.applications import Starlette from starlette.endpoints import WebSocketEndpoint from starlette.routing import WebSocketRoute from starlette.responses import PlainTextResponse app = Starlette() # 先定义 app 对象 class UserCounter(WebSocketEndpoint): encoding = 'text' sockets = [] async def on_connect(self, websocket): await websocket.accept() UserCounter.sockets.append(websocket) await self._send_count() async def on_disconnect(self, websocket, close_code): UserCounter.sockets.remove(websocket) await self._send_count() async def on_receive(self, websocket, data): pass async def _send_count(self): if len(UserCounter.sockets) > 0: count_str = str(len(UserCounter.sockets)) task_to_socket = {asyncio.create_task(websocket.send_text(count_str)): websocket for websocket in UserCounter.sockets} done, pending = await asyncio.wait(task_to_socket) for task in done: if task.exception() is not None: if task_to_socket[task] in UserCounter.sockets: UserCounter.sockets.remove(task_to_socket[task]) @app.route('/counter') # 现在可以使用 @app.route 装饰器了 async def counter(request): return PlainTextResponse(f"Current counter value: {len(UserCounter.sockets)}") app.routes.extend([ WebSocketRoute('/counter', UserCounter), app.router.routes ])
这个代码是一个使用 Starlette 框架构建的 WebSocket 应用,能够实时地统计和显示当前连接的 WebSocket 客户端的数量。下面是对代码的详细解释,特别是重点和难点部分的说明:
导入模块
import asyncio from starlette.applications import Starlette from starlette.endpoints import WebSocketEndpoint from starlette.routing import WebSocketRoute from starlette.responses import PlainTextResponse
asyncio: Python 的标准异步 I/O 库,用于处理异步任务。starlette.applications.Starlette: Starlette 框架的核心应用类。starlette.endpoints.WebSocketEndpoint: WebSocket 端点类,用于处理 WebSocket 连接。starlette.routing.WebSocketRoute: 用于定义 WebSocket 路由。starlette.responses.PlainTextResponse: 用于返回简单的文本响应。
定义应用
app = Starlette() # 先定义 app 对象
创建一个 Starlette 应用实例。
定义 WebSocket 端点类
class UserCounter(WebSocketEndpoint): encoding = 'text' sockets = []
encoding = 'text': 指定 WebSocket 消息的编码类型为文本。sockets: 一个类属性,用于存储所有当前连接的 WebSocket 客户端。
处理连接事件
async def on_connect(self, websocket): await websocket.accept() UserCounter.sockets.append(websocket) await self._send_count()
on_connect: 当一个新的 WebSocket 连接建立时调用。await websocket.accept(): 接受 WebSocket 连接。UserCounter.sockets.append(websocket): 将新连接的 WebSocket 客户端添加到sockets列表。await self._send_count(): 调用_send_count方法,发送当前连接数给所有客户端。
处理断开事件
async def on_disconnect(self, websocket, close_code): UserCounter.sockets.remove(websocket) await self._send_count()
on_disconnect: 当 WebSocket 连接关闭时调用。UserCounter.sockets.remove(websocket): 从sockets列表中移除断开的客户端。await self._send_count(): 调用_send_count方法,发送更新后的连接数给所有客户端。
处理接收消息事件
async def on_receive(self, websocket, data): pass
on_receive: 当收到 WebSocket 消息时调用。这个方法在当前实现中未被使用。
发送当前连接数
async def _send_count(self): if len(UserCounter.sockets) > 0: count_str = str(len(UserCounter.sockets)) task_to_socket = {asyncio.create_task(websocket.send_text(count_str)): websocket for websocket in UserCounter.sockets} done, pending = await asyncio.wait(task_to_socket) for task in done: if task.exception() is not None: if task_to_socket[task] in UserCounter.sockets: UserCounter.sockets.remove(task_to_socket[task])
_send_count: 发送当前连接的 WebSocket 客户端数量给所有客户端。count_str: 当前连接数转换为字符串。task_to_socket: 为每个客户端创建一个异步任务,将连接数发送给客户端。字典的键是任务,值是对应的客户端。done, pending = await asyncio.wait(task_to_socket): 等待所有任务完成。for task in done: 检查每个完成的任务,如果有异常,移除相应的客户端。
定义 HTTP 路由
@app.route('/counter') # 现在可以使用 @app.route 装饰器了 async def counter(request): return PlainTextResponse(f"Current counter value: {len(UserCounter.sockets)}")
添加 WebSocket 路由
app.routes.extend([ WebSocketRoute('/counter', UserCounter), app.router.routes ])
WebSocketRoute('/counter', UserCounter): 定义 WebSocket 路由,将/counter路径映射到UserCounter端点。app.routes.extend([...]): 将新的 WebSocket 路由添加到应用的路由中。
重点难点解释
- WebSocket 处理:
UserCounter类处理了 WebSocket 的连接、断开和消息接收。重点是管理sockets列表并广播连接数。 - 异步任务管理:
_send_count方法使用asyncio.create_task创建任务,并用asyncio.wait等待所有任务完成。这部分需要理解异步任务的管理和异常处理。 - 路由配置: Starlette 的路由配置需要注意 HTTP 路由和 WebSocket 路由的区分。
通过这个示例,你可以学习如何使用 Starlette 构建一个支持 WebSocket 和 HTTP 路由的应用,并了解异步任务和事件处理的基本概念。
2、解释下如下代码,重点难点地方请详细解释 【from view.py】
import asyncio from datetime import datetime from aiohttp import ClientSession from django.shortcuts import render import aiohttp async def get_url_details(session: ClientSession, url: str): start_time = datetime.now() response = await session.get(url) response_body = await response.text() end_time = datetime.now() return {'status': response.status, 'time': (end_time - start_time).microseconds, 'body_length': len(response_body)} async def make_requests(url: str, request_num: int): async with aiohttp.ClientSession() as session: requests = [get_url_details(session, url) for _ in range(request_num)] results = await asyncio.gather(*requests, return_exceptions=True) failed_results = [str(result) for result in results if isinstance(result, Exception)] successful_results = [result for result in results if not isinstance(result, Exception)] return {'failed_results': failed_results, 'successful_results': successful_results} async def requests_view(request): url: str = request.GET['url'] request_num: int = int(request.GET['request_num']) context = await make_requests(url, request_num) return render(request, 'async_api/requests.html', context) from functools import partial from django.http import HttpResponse from asgiref.sync import sync_to_async def sleep(seconds: int): import time time.sleep(seconds) async def sync_to_async_view(request): sleep_time: int = int(request.GET['sleep_time']) num_calls: int = int(request.GET['num_calls']) thread_sensitive: bool = request.GET['thread_sensitive'] == 'True' function = sync_to_async(partial(sleep, sleep_time), thread_sensitive=thread_sensitive) await asyncio.gather(*[function() for _ in range(num_calls)]) return HttpResponse('') from asgiref.sync import async_to_sync def requests_view_sync(request): url: str = request.GET['url'] request_num: int = int(request.GET['request_num']) context = async_to_sync(partial(make_requests, url, request_num))() return render(request, 'async_api/requests.html', context)
这个代码展示了如何在 Django 中使用 asyncio 和 aiohttp 实现异步请求处理,以及如何在同步和异步代码之间转换。下面是对代码的详细解释,特别是重点和难点部分的说明:
导入模块
import asyncio from datetime import datetime from aiohttp import ClientSession from django.shortcuts import render import aiohttp from functools import partial from django.http import HttpResponse from asgiref.sync import sync_to_async, async_to_sync
asyncio: Python 的标准异步 I/O 库,用于处理异步任务。datetime: 用于获取当前时间,测量请求时间。aiohttp: 异步 HTTP 客户端,用于发送 HTTP 请求。django.shortcuts.render: 用于渲染 Django 模板。functools.partial: 用于部分应用函数参数。django.http.HttpResponse: 用于返回 HTTP 响应。asgiref.sync.sync_to_async: 用于将同步函数转换为异步函数。asgiref.sync.async_to_sync: 用于将异步函数转换为同步函数。
异步获取 URL 详情
async def get_url_details(session: ClientSession, url: str): start_time = datetime.now() response = await session.get(url) response_body = await response.text() end_time = datetime.now() return {'status': response.status, 'time': (end_time - start_time).microseconds, 'body_length': len(response_body)}
session.get(url): 异步发送 GET 请求。response.text(): 异步获取响应的文本内容。- 记录请求开始和结束时间,计算请求时间。
- 返回请求状态码、耗时和响应体长度。
异步发送多个请求
async def make_requests(url: str, request_num: int): async with aiohttp.ClientSession() as session: requests = [get_url_details(session, url) for _ in range(request_num)] results = await asyncio.gather(*requests, return_exceptions=True) failed_results = [str(result) for result in results if isinstance(result, Exception)] successful_results = [result for result in results if not isinstance(result, Exception)] return {'failed_results': failed_results, 'successful_results': successful_results}
aiohttp.ClientSession(): 创建一个异步 HTTP 会话。requests: 创建多个异步请求任务。asyncio.gather(*requests, return_exceptions=True): 并发执行所有请求任务,收集结果。- 处理请求结果,将异常和成功结果分开。
异步视图函数
async def requests_view(request): url: str = request.GET['url'] request_num: int = int(request.GET['request_num']) context = await make_requests(url, request_num) return render(request, 'async_api/requests.html', context)
- 从请求中获取
url和request_num参数。 - 调用
make_requests执行异步请求。 - 使用
render渲染模板并返回结果。
同步函数
def sleep(seconds: int): import time time.sleep(seconds)
- 简单的同步睡眠函数,用于模拟耗时操作。
异步视图函数调用同步函数
async def sync_to_async_view(request): sleep_time: int = int(request.GET['sleep_time']) num_calls: int = int(request.GET['num_calls']) thread_sensitive: bool = request.GET['thread_sensitive'] == 'True' function = sync_to_async(partial(sleep, sleep_time), thread_sensitive=thread_sensitive) await asyncio.gather(*[function() for _ in range(num_calls)]) return HttpResponse('')
- 获取
sleep_time和num_calls参数。 - 使用
sync_to_async将sleep函数转换为异步函数。 - 使用
asyncio.gather并发执行多次睡眠函数。 - 返回空的 HTTP 响应。
同步视图函数调用异步函数
def requests_view_sync(request): url: str = request.GET['url'] request_num: int = int(request.GET['request_num']) context = async_to_sync(partial(make_requests, url, request_num))() return render(request, 'async_api/requests.html', context)
- 从请求中获取
url和request_num参数。 - 使用
async_to_sync将make_requests异步函数转换为同步函数并调用。 - 使用
render渲染模板并返回结果。
重点和难点
-
异步编程:
- 理解如何使用
asyncio和aiohttp进行异步 HTTP 请求。 - 异步函数之间的调用和任务调度。
- 理解如何使用
-
异步与同步代码的转换:
- 使用
sync_to_async和async_to_sync实现异步与同步代码之间的互操作。这在将已有的同步函数迁移到异步环境中非常有用。
- 使用
-
错误处理:
- 使用
asyncio.gather(..., return_exceptions=True)捕获并处理异步任务中的异常,将异常和成功结果分开处理。
- 使用
通过这些代码,你可以学习如何在 Django 中使用异步编程来处理高并发请求,以及如何在异步和同步代码之间进行转换。
3、Django中的视图是什么概念?MVT又是什么?另外,为什么要同步和异步代码之间互相转换?这样不是很麻烦吗
在这个模式中,视图是控制器的角色,负责协调模型和模板之间的交互。
同步和异步代码之间的转换
为什么要转换?
在现代 web 开发中,异步编程越来越重要,因为它能够更高效地处理 I/O 密集型任务(如网络请求、数据库查询等),避免在等待 I/O 操作时阻塞整个程序。这在高并发和高性能的应用中尤为重要。
但是,在实际开发中,我们可能会遇到以下情况:
- 现有的同步代码:已有的大量同步代码或库,重写成异步版本成本高且复杂。
- 兼容性需求:某些操作必须同步执行,而整体应用是异步的,或者反之。
- 渐进式迁移:从同步到异步的迁移通常是渐进式的,需要一个过程。在这个过程中,需要同时支持同步和异步代码。
如何转换?
sync_to_async:将同步函数转换为异步函数。适用于需要在异步代码中调用同步代码的场景。async_to_sync:将异步函数转换为同步函数。适用于需要在同步代码中调用异步代码的场景。
使用示例:
from asgiref.sync import sync_to_async, async_to_sync # 同步函数 def sync_function(): import time time.sleep(2) return "Done" # 异步调用同步函数 async def async_view(): result = await sync_to_async(sync_function)() return result # 异步函数 async def async_function(): await asyncio.sleep(2) return "Done" # 同步调用异步函数 def sync_view(): result = async_to_sync(async_function)() return result
复杂性与必要性
虽然同步和异步代码之间的转换增加了一些复杂性,但在实际开发中这是必要的。通过这种转换,我们可以:
- 逐步迁移:平滑地将现有的同步代码逐步迁移到异步模式,降低一次性重构的风险。
- 提高性能:在合适的地方使用异步编程,显著提高应用的并发性能和响应速度。
- 兼容性处理:在异步环境中使用必要的同步库,或在同步环境中使用异步特性,确保应用功能的完整性和兼容性。
综上所述,虽然同步和异步代码的转换带来了一定的复杂性,但这是现代 web 开发中实现高性能、高并发应用的一种有效方法。理解并掌握这些技术,对于提升应用性能和编写高效代码至关重要。
4、钩子函数的理解
什么是钩子函数?
钩子函数(Hook Function)是一种允许外部代码在某个特定的操作或事件发生时插入自定义逻辑的机制。钩子函数在计算机编程中非常普遍,广泛应用于各种编程语言和框架中。通过使用钩子函数,程序可以在保持核心逻辑不变的情况下,实现灵活的扩展和自定义。
钩子函数的作用
- 扩展功能:允许开发者在核心功能之外添加额外的行为,而无需修改核心代码。
- 自定义行为:提供在特定事件发生时执行自定义逻辑的机会。
- 代码复用:实现通用的扩展逻辑,可以在不同的上下文中复用。
- 解耦代码:将可变部分和核心逻辑分离,提高代码的可维护性和可扩展性。
通俗解释和举例说明
通俗解释
想象一下,你在家里安装了一套智能家居系统,系统有一个默认的行为,比如早上7点开灯,晚上10点关灯。
但是你希望在早上开灯时能够播放你喜欢的音乐,晚上关灯时能够启动家里的安全系统。你不需要修改智能家居系统的核心代码,而是可以通过一个“钩子”来插入这些自定义的行为。
钩子函数就是这样一种机制,它允许你在特定的事件(比如开灯、关灯)发生时,插入你自己的代码(播放音乐、启动安全系统)。
举例说明
假设我们有一个简单的博客系统,当用户发表新文章时,我们希望在文章发表前后执行一些额外的操作,比如记录日志和发送通知。
以下是一个示例代码:
class Blog: def __init__(self): self.hooks = {"before_post": [], "after_post": []} def add_hook(self, hook_type, func): if hook_type in self.hooks: self.hooks[hook_type].append(func) def post_article(self, title, content): # 在文章发表前执行所有 before_post 钩子函数 for hook in self.hooks["before_post"]: hook(title, content) print(f"Posting article: {title}") # 在文章发表后执行所有 after_post 钩子函数 for hook in self.hooks["after_post"]: hook(title, content) def log_before(title, content): print(f"Logging before posting: {title}") def notify_after(title, content): print(f"Sending notification for: {title}") # 创建博客实例 blog = Blog() # 添加钩子函数 blog.add_hook("before_post", log_before) blog.add_hook("after_post", notify_after) # 发表文章 blog.post_article("My First Blog Post", "This is the content of the blog post.")
输出结果:
Logging before posting: My First Blog Post Posting article: My First Blog Post Sending notification for: My First Blog Post
在这个例子中,我们使用钩子函数 log_before 和 notify_after 来记录日志和发送通知,而无需修改 post_article 方法的核心逻辑。
钩子函数不仅仅在 Python 中存在
钩子函数并不仅仅是 Python 独有的概念。它在许多编程语言和框架中都有广泛的应用。以下是一些例子:
- JavaScript:在许多 JavaScript 框架(如 React、Vue)中,钩子函数被广泛使用。例如,在 React 中,
useEffect是一个常见的钩子函数,用于在组件渲染后执行某些操作。 - C#:在 .NET 框架中,事件和委托机制允许开发者在特定事件发生时执行钩子函数。
- Java:在 Java 的 Spring 框架中,开发者可以使用 AOP(面向切面编程)来定义钩子函数,在方法执行前后插入自定义逻辑。
- PHP:在许多 PHP 框架和 CMS(如 WordPress)中,钩子函数被广泛使用。例如,WordPress 提供了动作(Actions)和过滤器(Filters)钩子,允许开发者在特定事件发生时执行自定义代码。
总结
钩子函数是一种强大的机制,允许开发者在特定事件发生时插入自定义逻辑。
它们在许多编程语言和框架中都有广泛应用,帮助开发者实现功能扩展和自定义,而无需修改核心代码。
这种机制提高了代码的可维护性和可扩展性,是现代软件开发中的重要工具。

浙公网安备 33010602011771号