杰瑞科技汇

python django 面试题

目录

  1. Django 基础
  2. ORM (对象关系映射)
  3. 视图、模板与 URL
  4. 中间件
  5. 表单
  6. 认证与授权
  7. 性能优化
  8. 高级特性
  9. 项目与部署
  10. 系统设计
  11. 开放性问题

Django 基础

1 什么是 Django?它的核心理念是什么?

参考回答: Django 是一个高级的 Python Web 框架,它鼓励快速开发和干净、实用的设计,它的核心理念是 "DRY" (Don't Repeat Yourself)"BBAW" (Batteries Included)

python django 面试题-图1
(图片来源网络,侵删)
  • DRY: 不要重复自己,鼓励代码复用。
  • BBAW: 内置电池,提供了 Web 开发所需的大部分功能,如 ORM、路由、模板引擎、后台管理等,开发者不需要自己“重新造轮子”。

2 Django 的 MTV 架构是什么?它和 MVC 有什么区别?

参考回答: Django 遵循 MTV (Model-Template-View) 架构模式,这是一种 MVC (Model-View-Controller) 的变种。

  • Model (模型): 负责与数据库交互,定义数据结构,对应 MVC 中的 Model
  • Template (模板): 负责展示,是 HTML 文件,嵌入 Python 代码来动态生成页面,对应 MVC 中的 View (用户看到的界面)。
  • View (视图): 负责业务逻辑,接收请求,从 Model 获取数据,调用 Template 渲染页面,并返回响应,对应 MVC 中的 Controller (处理用户输入并调用 Model 和 View)。

区别:主要是术语上的不同,Django 的 View 更像是 MVC 中的 Controller,而 Django 的 Template 才是 MVC 中的 View。

3 描述一下 Django 的请求生命周期。

参考回答: 当一个请求到达 Django 应用时,它会经历以下步骤:

  1. WSGI 服务器: 请求首先由 WSGI 服务器(如 Gunicorn, uWSGI)接收。
  2. 加载配置: WSGI 服务器加载 Django 项目的 settings.py 文件。
  3. URL 路由: Django 的 URL Dispatcher 根据 urls.py 中的配置,匹配请求的 URL,找到对应的 view 函数或类。
  4. 中间件: 在调用 view 之前,请求会依次通过所有已激活的 Middlewareprocess_request 方法,这些方法可以进行请求预处理、身份验证等。
  5. 视图: 找到的 view 函数被调用,它负责处理业务逻辑,可能包括查询数据库、调用其他服务等。
  6. 模板渲染: view 返回一个 HttpResponse,并且需要渲染模板,它会将数据传递给模板引擎,生成最终的 HTML。
  7. 响应: view 返回一个 HttpResponse 对象。
  8. 中间件: 在响应返回给客户端之前,它会依次通过所有 Middlewareprocess_response 方法,可以进行响应后处理,如添加 CORS 头、压缩等。
  9. WSGI 服务器: WSGI 服务器将最终的 HTTP 响应发送给客户端。

4 Django 的 settings.py 文件中,必须包含哪些关键配置?

参考回答: 一些核心的 settings.py 配置包括:

python django 面试题-图2
(图片来源网络,侵删)
  • DEBUG: 是否开启调试模式,生产环境必须设为 False
  • SECRET_KEY: 用于加密 session、密码等,是安全的关键。
  • INSTALLED_APPS: 列出项目中所有启用的 Django 应用。
  • DATABASES: 数据库连接配置,如 default 数据库的类型、主机、端口、用户名、密码等。
  • ROOT_URLCONF: 项目的根 URL 配置文件路径。
  • MIDDLEWARE: 中间件的列表,决定了请求和响应的处理顺序。
  • TEMPLATES: 模板引擎的配置,如模板目录、加载器等。
  • STATIC_URLSTATIC_ROOT: 静态文件(CSS, JS, 图片)的 URL 前缀和收集路径。
  • MEDIA_URLMEDIA_ROOT: 用户上传文件的 URL 前缀和存储路径。

ORM (对象关系映射)

1 解释 Django ORM 中的 select_relatedprefetch_related 的区别和使用场景。

参考回答: 两者都是为了解决“N+1 查询问题”(即循环 N 次数据库查询,每次查询关联对象),但实现方式不同。

  • select_related:

    • 原理: 使用 SQL 的 JOIN 语句在查询时一次性获取主对象和其外键关联的对象。
    • 适用场景: 主要用于处理 一对一多对一 的外键关系,因为它使用 JOIN,所以对于深度嵌套的关系,可能会生成非常复杂的 SQL,甚至性能更差。
    • 示例: Post.objects.select_related('author').all() 会在一条查询中获取所有文章及其作者信息。
  • prefetch_related:

    • 原理: 执行两条(或多条)独立的查询,第一条查询获取所有主对象,第二条查询获取所有关联对象,然后在 Python 内存中将它们关联起来。
    • 适用场景: 主要用于处理 多对多 关系,也适用于 select_related 不适用或性能不佳的 多对一 关系,因为它不使用 JOIN,所以不会生成复杂的 SQL,对于深度嵌套关系更友好。
    • 示例: Post.objects.prefetch_related('tags').all() 会先查询所有文章,再查询所有文章对应的标签,然后在内存中匹配。

优先使用 select_related(对于外键),如果遇到多对多关系或 select_related 效果不佳时,使用 prefetch_related

python django 面试题-图3
(图片来源网络,侵删)

2 什么是 QuerySet?它有什么特性?

参考回答: QuerySet 是 Django ORM 中表示数据库对象的集合,它是一个惰性执行的“查询”对象,只有在真正需要数据时(如迭代、切片、调用 len()list())才会向数据库发送查询。

主要特性

  1. 惰性求值: Post.objects.all() 这行代码本身不会执行数据库查询。
  2. 链式调用: 可以连续调用过滤、排序等方法,如 Post.objects.filter(published=True).order_by('-pub_date')
  3. 缓存: 对同一个 QuerySet 进行多次数据访问,数据库查询只执行一次。q = Post.objects.all()len(q)list(q) 都只会触发一次查询。
  4. 可切片: 可以使用 Python 的切片语法 [:] 来获取一部分数据,但要注意切片后的 QuerySet 不会被缓存。

3 如何在 Django 中实现数据库事务?

参考回答: Django 提供了多种方式使用事务:

  1. 装饰器 (最常用):

    from django.db import transaction
    @transaction.atomic
    def create_order(user, items):
        # 这些操作都在一个事务中
        order = Order.objects.create(user=user)
        for item in items:
            OrderItem.objects.create(order=order, product=item.product, quantity=item.quantity)
        # 如果这里发生异常,整个事务会回滚
  2. 上下文管理器 (更灵活):

    from django.db import transaction
    def create_order(user, items):
        with transaction.atomic():
            order = Order.objects.create(user=user)
            # ... 其他操作 ...
  3. 在视图中使用: 通过 transaction.atomic() 装饰器整个视图函数,或者使用 @transaction.atomicsavepoint 进行更细粒度的控制。


视图、模板与 URL

1 解释类视图 和函数视图 的区别和优缺点。

参考回答:

  • 函数视图:

    • 优点: 简单直观,易于理解,适合逻辑简单的视图。
    • 缺点: 当视图逻辑变复杂时,函数会变得臃肿且难以维护,代码复用性差。
  • 类视图:

    • 优点:
      • 代码复用: 基于 OOP,可以通过继承和组合来复用代码。
      • 逻辑分离: 使用不同的方法(如 get(), post())处理不同的 HTTP 方法,逻辑更清晰。
      • 扩展性强: 可以轻松地使用 Mixin(混入类)来添加功能,如权限检查、登录重定向等。
      • 内置功能: Django 提供了大量内置的通用类视图(如 ListView, DetailView),可以快速实现常见功能。
    • 缺点: 对于初学者来说,理解成本较高,不如函数视图直观。

现代 Django 开发更推荐使用类视图,特别是对于有复杂逻辑或需要复用的视图。

2 什么是模板继承?如何使用 {% block %}

参考回答: 模板继承是 Django 模板引擎的一个强大功能,它允许你创建一个基础模板,然后在子模板中重用它的结构,只覆盖需要修改的部分。

  • {% block %}: 在基础模板中定义一个或多个 block,这些 block 是子模板可以填充的占位符。

    <!-- base.html -->
    <html>
    <head>
        <title>{% block title %}My Site{% endblock %}</title>
    </head>
    <body>
        {% block content %}
        {% endblock %}
    </body>
    </html>
  • {% extends %}: 在子模板中,使用 extends 来指定它继承自哪个基础模板。

    <!-- article_detail.html -->
    {% extends "base.html" %}
    {% block title %}{{ article.title }}{% endblock %}
    {% block content %}
        <h1>{{ article.title }}</h1>
        <p>{{ article.body }}</p>
    {% endblock %}

3 Django 的 reverse 函数是做什么用的?

参考回答: reverse 函数用于根据 URL 的名称(在 urls.py 中通过 name 参数定义)来动态生成 URL 的路径字符串,这对于代码的可维护性至关重要,因为如果 URL 路径改变,你只需要在 urls.py 中修改一处,而不需要在所有模板和视图中硬编码 URL。

示例:

  1. urls.py 中定义一个带名称的 URL:

    path('article/<int:pk>/', views.ArticleDetailView.as_view(), name='article_detail'),
  2. 在模板中使用:

    <a href="{% url 'article_detail' pk=article.id %}">Read more</a>
  3. 在视图中使用:

    from django.urls import reverse
    def some_view(request):
        url = reverse('article_detail', kwargs={'pk': 1})
        # url 的值将是 '/article/1/'

中间件

1 什么是中间件?列举一个你使用过的中间件并说明其作用。

参考回答: 中间件是一个轻量级、低级别的“插件”系统,用于在全局范围内改变 Django 的输入或输出,它是一个 Python 类,位于请求和响应处理过程的中间,可以拦截并处理请求和响应。

常见的中间件

  1. django.middleware.security.SecurityMiddleware: 启用各种安全功能,如 X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security (HSTS) 等,以及 HTTPS 重定向。
  2. django.contrib.sessions.middleware.SessionMiddleware: 处理会话,使得用户登录状态等信息可以在多个请求间保持。
  3. django.middleware.common.CommonMiddleware: 处理一些通用任务,如规范化请求字符集、处理 ETag 等。
  4. 自定义中间件: 一个用于记录每个请求耗时的中间件。

自定义中间件示例 (记录请求耗时):

import time
from django.utils.deprecation import MiddlewareMixin
class RequestTimeMiddleware(MiddlewareMixin):
    def process_request(self, request):
        request.start_time = time.time()
    def process_response(self, request, response):
        total_time = time.time() - request.start_time
        print(f"Request to {request.path} took {total_time:.2f} seconds.")
        return response

表单

1 Django 的 ModelForm 和普通 Form 有什么区别?

参考回答:

  • Form:

    • 用于处理与模型无关的数据,如用户注册、联系表单等。
    • 字段需要手动定义。
    • 提供了数据验证和渲染 HTML 表单的功能。
  • ModelForm:

    • Form 的一个子类,与 Django 模型紧密集成。
    • 可以通过 class Meta 中的 modelfields 自动从模型生成表单字段。
    • 提供了 save() 方法,可以将表单数据直接保存或更新到数据库,非常方便。
    • 适用于基于模型的 CRUD 操作。

示例:

# Model
from django.db import models
class Article(models.Model):= models.CharField(max_length=100)
    content = models.TextField()
# ModelForm
from django.forms import ModelForm
class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content'] # 自动生成 title 和 content 字段
# 使用
form = ArticleForm(request.POST)
if form.is_valid():
    article = form.save() # 一行代码保存到数据库

认证与授权

1 如何实现一个自定义的用户模型?

参考回答: 从 Django 1.10 开始,官方推荐使用 AbstractUserAbstractBaseUser 来创建自定义用户模型,而不是直接继承 User

  1. settings.py 中指定自定义用户模型:

    AUTH_USER_MODEL = 'myapp.CustomUser'
  2. 创建自定义用户模型:

    # myapp/models.py
    from django.contrib.auth.models import AbstractUser
    from django.db import models
    class CustomUser(AbstractUser):
        # 添加自定义字段
        bio = models.TextField(blank=True)
        birth_date = models.DateField(null=True, blank=True)
        # 可以重写或添加方法
  3. 创建并应用迁移:

    python manage.py makemigrations
    python manage.py migrate
  4. 创建超级用户时,需要使用自定义模型的字段:

    python manage.py createsuperuser --username=myuser --email=myuser@example.com

2 解释 Django 的权限系统是如何工作的?

参考回答: Django 的权限系统基于 auth_permission 表,它提供了细粒度的控制。

  1. 自动创建权限: 对于每个模型,Django 默认会创建三个权限:add_<model_name>, change_<model_name>, delete_<model_name>

  2. 权限分配:

    • 用户权限: 可以直接分配给 User 模型的实例。
    • 组权限: 可以创建 Group,将权限分配给组,然后将用户添加到组中,用户自动继承组的所有权限。
  3. 在模板中使用:

    {% if perms.myapp.add_article %}
        <a href="/create-article/">Create Article</a>
    {% endif %}
  4. 在视图中使用:

    from django.contrib.auth.mixins import PermissionRequiredMixin
    class CreateArticleView(PermissionRequiredMixin, View):
        permission_required = 'myapp.add_article'
        # ...

性能优化

1 如何优化 Django 应用的性能?

参考回答: 性能优化可以从多个层面进行:

  1. 数据库层面:

    • 索引: 为经常用于 WHERE, JOIN, ORDER BY 的字段添加数据库索引。
    • 查询优化: 使用 select_relatedprefetch_related 避免 N+1 查询,使用 only()defer() 只查询需要的字段。
    • 数据库连接池: 使用 django-db-connection-pool 或配置 WSGI 服务器的连接池。
  2. 缓存层面:

    • 全站缓存: 使用 django.middleware.cache.UpdateCacheMiddlewaredjango.middleware.cache.FetchFromCacheMiddleware
    • 视图缓存: 使用 @cache_page 装饰器缓存整个视图的输出。
    • 模板片段缓存: 使用 {% cache %} 标签缓存模板的某个部分。
    • 低级缓存: 使用 cache.set()cache.get() 手动缓存复杂计算结果或数据库查询结果。
  3. 静态文件:

    • 使用 CDN (Content Delivery Network) 来分发静态文件。
    • 启用 Gzip/Brotli 压缩。
  4. 代码层面:

    • 异步任务: 将耗时操作(如发送邮件、处理图片)放入 Celery 或 RQ 等任务队列中异步执行。
    • 代码优化: 避免在循环中进行数据库查询或 I/O 操作。
  5. 服务器层面:

    • 使用高效的 WSGI 服务器(如 Gunicorn, uWSGI)和 ASGI 服务器(如 Daphne, Uvicorn)。
    • 使用 Nginx 作为反向代理和静态文件服务器。

高级特性

1 什么是 Django 信号?它有什么用?

参考回答: Django 信号是一种解耦的机制,允许某些发送者通知接收者发生了某些动作,它实现了观察者模式,允许应用中的不同部分在发生特定事件时相互通信,而无需将它们紧密耦合。

常用信号:

  • pre_save / post_save: 在模型保存之前/之后触发。
  • pre_delete / post_delete: 在模型删除之前/之后触发。
  • m2m_changed: 在多对多关系改变时触发。

示例: 当创建新用户时,自动创建一个关联的配置文件。

# myapp/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import UserProfile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.userprofile.save()
# myapp/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
    name = 'myapp'
    def ready(self):
        import myapp.signals

项目与部署

1 Django 项目的生产环境部署流程是怎样的?

参考回答: 一个典型的生产环境部署流程如下:

  1. 代码准备:

    • 将代码放入版本控制(如 Git)。
    • 创建生产环境的分支或标签。
    • 使用 requirements.txtPipfile 管理依赖。
  2. 环境配置:

    • 设置 DEBUG = False
    • 配置 ALLOWED_HOSTS
    • 使用环境变量(如 python-decoupledjango-environ)管理敏感信息(数据库密码、密钥等)。
  3. 服务器选择:

    • Web 服务器: Nginx,用于处理静态文件、代理请求到应用服务器。
    • 应用服务器: Gunicorn (WSGI) 或 Uvicorn (ASGI),用于运行 Django 应用。
  4. 部署步骤:

    • 在服务器上克隆代码库。
    • 创建并激活虚拟环境。
    • 安装依赖 (pip install -r requirements.txt)。
    • 收集静态文件 (python manage.py collectstatic)。
    • 执行数据库迁移 (python manage.py migrate)。
    • 使用 Gunicorn 启动应用 (gunicorn myproject.wsgi:application)。
  5. 进程管理:

    • 使用 supervisorsystemd 来管理 Gunicorn 进程,确保它在崩溃后自动重启。
  6. 容器化 (可选):

    使用 Docker 将应用和其依赖打包成镜像,使用 Docker Compose 编排 Nginx、应用和数据库服务,实现环境一致性和易于扩展。


系统设计

1 如何设计一个支持高并发的秒杀系统?

参考回答: 这是一个经典的系统设计题,考察候选人应对高并发场景的思路,关键点在于削峰防止超卖

核心思路:

  1. 前端优化:

    • 按钮置灰,防止用户重复点击。
    • 使用静态页面,减少服务器压力。
  2. 后端架构:

    • 读多写少: 秒杀活动开始前,商品信息(库存)会被大量用户访问,可以将商品信息(尤其是库存)缓存到 Redis 或 Memcached 中。
    • 写操作集中: 秒杀开始时,所有请求都集中在“减库存”这个操作上,这是系统的瓶颈。

防止超卖的核心策略:

  1. 数据库行级锁:

    • 使用 SELECT ... FOR UPDATE 对库存记录加锁,确保在事务完成前,其他事务无法修改。
    • 缺点: 在高并发下,锁竞争会非常激烈,性能差。
  2. Redis 原子操作 (推荐):

    • 使用 Redis 的 DECRDECRBY 命令。DECR 是原子操作,能保证库存不会被减成负数。
    • 流程:
      1. 用户请求到达,先从 Redis 中获取库存 (GET stock)。
      2. 如果库存大于 0,执行 DECR stockDECR 是原子操作,DECR 后的值大于等于 0,则表示抢购成功。
      3. DECR 后的值小于 0,说明库存已空,抢购失败,需要将库存加回来 (INCR)。
    • 优点: 性能极高,无锁竞争。
  3. 消息队列:

    • 将所有秒杀请求放入一个消息队列(如 RabbitMQ, Kafka)中。
    • 使用多个消费者(Worker)从队列中取请求,串行处理,将并发请求转化为串行处理。
    • 优点: 削峰填谷,系统更稳定,易于扩展。
    • 缺点: 引入了额外的组件,系统更复杂。

整体流程 (Redis + MQ):

  1. 预热: 活动开始前,将商品库存加载到 Redis。
  2. 用户请求: 用户点击秒杀,请求先打到 Nginx。
  3. 网关/限流: Nginx 或 API 网关进行简单的限流(如限制 IP QPS)。
  4. 应用服务: 请求到达 Django 应用。
  5. Redis 预检查: 应用服务先去 Redis 检查库存并尝试原子减库存,如果失败,直接返回“已售罄”。
  6. MQ 入队: Redis 预检查成功,将用户订单信息(如用户ID、商品ID)发送到消息队列。
  7. 异步下单: 消费者从队列中取出消息,执行数据库操作(创建订单、更新数据库库存),并通知用户下单成功。

开放性问题

1 你在 Django 项目中遇到的最大挑战是什么?你是如何解决的?

参考回答: 这是一个开放性问题,考察候选人的解决问题的能力和经验,回答时建议使用 STAR 法则 (Situation, Task, Action, Result)。

示例回答: “在我上一个项目中,我们遇到了一个严重的性能瓶颈,一个报表页面的加载时间长达 30 秒。

  • (Situation): 这个页面需要从多个表中聚合大量数据,并且有复杂的过滤条件。
  • (Task): 我的任务是优化这个页面,将其加载时间降低到 3 秒以内。
  • (Action):
    1. 分析: 我首先使用 Django Debug Toolbar 和数据库慢查询日志,定位到主要耗时在于一个没有索引的 JOIN 操作和一个循环查询的 N+1 问题。
    2. 优化: 我为相关字段添加了数据库索引,并使用 select_related 一次性获取所有关联数据,解决了 N+1 查询。
    3. 缓存: 我发现这个报表的数据每天只更新一次,于是我引入了 cache_page 装饰器,将报表缓存 24 小时。
    4. 异步: 对于必须实时计算的部分,我将其重构为一个异步任务,用户先看到“正在生成”的提示,后台任务完成后通过 WebSocket 或邮件通知用户。
  • (Result): 经过这些优化,页面的加载时间成功降低到了 2 秒以内,用户体验得到了显著提升。”

分享:
扫描分享到社交APP
上一篇
下一篇