Django 中的属性代理模式:揭秘 `django.core.cache.cache` 的魔法

9 阅读5分钟

在 Django 开发中,你可能早已习惯这样使用缓存:

from django.core.cache import cache

cache.set("user:1001", user_data, timeout=300)
data = cache.get("user:1001")

但你是否好奇:

cache 到底是什么?它如何在不修改代码的情况下,无缝切换 Redis、Memcached 或本地内存缓存?

答案就藏在 Django 的一个精巧设计中 —— 属性代理模式(Attribute Proxy Pattern) 。本文将带你揭开这层神秘面纱,并深入理解其在缓存系统中的核心作用。


一、什么是属性代理模式?

属性代理模式 是一种设计模式,通过一个代理对象将对属性或方法的访问转发(delegate) 给另一个真实对象,而调用者对此完全无感知。

核心特点:

  • 透明性:客户端像使用普通对象一样使用代理;
  • 解耦性:真实对象可动态替换,不影响调用方;
  • 延迟初始化:真实对象可在首次访问时才创建。

在 Python 中,可通过重写 __getattr____setattr__ 等魔术方法实现。


二、Django 缓存系统的架构设计

Django 支持多种缓存后端:

  • LocMemCache(本地内存)
  • RedisCache
  • MemcachedCache
  • DatabaseCache
  • 自定义后端

但无论使用哪种,开发者都只与 同一个 cache 对象交互。这是如何做到的?

关键角色:

组件作用
django.core.cache.cache全局代理对象(Proxy)
django.core.cache.caches缓存后端注册表(Registry)
BaseCache 及其实现类真实缓存后端(Real Subject)

三、源码剖析:cache 是如何工作的?

步骤1:cache 的定义(django/core/cache/__init__.py

# django/core/cache/__init__.py
from django.core.cache.backends.base import DEFAULT_CACHE_ALIAS
from django.core.cache.utils import CacheHandler

# 创建一个 CacheHandler 实例
caches = CacheHandler()

# 默认缓存别名(通常为 'default')
cache = caches[DEFAULT_CACHE_ALIAS]  # ← 关键!

这里 cache 并不是一个真实的缓存对象,而是 caches['default'] 的结果。


步骤2:CacheHandler —— 属性代理的核心

# django/core/cache/utils.py
class CacheHandler:
    def __init__(self):
        self._caches = {}

    def __getitem__(self, alias):
        # 如果该 alias 的缓存未创建,则创建并缓存
        if alias not in self._caches:
            self._caches[alias] = self.create_cache(alias)
        return self._caches[alias]

    def create_cache(self, alias):
        # 1. 从 settings.CACHES 获取配置
        # 2. 动态导入对应的缓存 backend 类
        # 3. 实例化并返回
        ...

重点CacheHandler 本身不实现任何缓存逻辑,它只是一个按需创建并缓存真实后端的代理工厂


步骤3:DefaultCacheProxy —— 真正的“透明代理”

但在旧版本(<4.0)中,Django 使用了一个更典型的代理类:

# 简化版 DefaultCacheProxy
class DefaultCacheProxy:
    def __getattr__(self, name):
        # 首次访问时,从 caches['default'] 获取真实对象
        return getattr(caches[DEFAULT_CACHE_ALIAS], name)

    def __setattr__(self, name, value):
        setattr(caches[DEFAULT_CACHE_ALIAS], name, value)

    def __delattr__(self, name):
        delattr(caches[DEFAULT_CACHE_ALIAS], name)

# 全局 cache 对象
cache = DefaultCacheProxy()

工作流程:

  1. 调用 cache.set(...)
  2. 触发 __getattr__('set')
  3. 代理自动获取 caches['default'](即真实后端);
  4. 转发调用 real_cache.set(...)

🔍 这就是属性代理模式的精髓:调用者以为在操作 cache,实际在操作 RedisCacheLocMemCache


四、配置驱动:如何切换缓存后端?

一切的秘密都在 settings.py 中:

# settings.py

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
    },
    'session': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    }
}

# 代码无需任何修改!
from django.core.cache import cache
cache.set("key", "value")  # 自动使用 Redis

# 如需使用其他后端
from django.core.cache import caches
session_cache = caches['session']
session_cache.set("sess_id", data)  # 使用内存缓存

动态切换示例(测试场景):

# tests.py
from django.test import override_settings

@override_settings(CACHES={
    'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}
})
def test_without_cache():
    # 此时 cache 是 DummyCache(不实际存储)
    assert cache.get("any_key") is None

零代码侵入,仅靠配置即可切换底层实现!


五、为什么使用代理模式?优势何在?

优势说明
解耦业务代码不依赖具体缓存实现
透明性开发者无需关心 cache 内部是 Redis 还是 Memcached
懒加载真实缓存连接在首次使用时才建立,节省资源
多后端支持通过 caches[alias] 轻松管理多个缓存实例
测试友好可轻松 mock 或替换为 dummy 后端

六、扩展:自定义缓存后端

Django 允许你实现自己的缓存后端,只需继承 BaseCache

# myapp/cache_backends.py
from django.core.cache.backends.base import BaseCache

class MyCustomCache(BaseCache):
    def get(self, key, default=None, version=None):
        # 自定义获取逻辑(如调用外部 API)
        ...

    def set(self, key, value, timeout=300, version=None):
        # 自定义存储逻辑
        ...

配置使用:

CACHES = {
    'default': {
        'BACKEND': 'myapp.cache_backends.MyCustomCache',
    }
}

代理模式确保你的自定义后端能无缝接入现有代码


七、常见误区澄清

❌ 误区1:“cache 是一个单例缓存实例”

  • 纠正cache 是一个代理对象,背后的真实实例由 CacheHandler 管理。

❌ 误区2:“修改 cache 属性会影响所有地方”

  • 纠正:由于代理转发,cache.timeout = 60 实际修改的是真实后端的属性,但不推荐直接修改,应通过配置控制。

❌ 误区3:“代理会带来性能损耗”

  • 纠正:Python 属性查找开销极小,且真实对象会被缓存(_caches 字典),仅首次访问有微小 overhead

八、总结

Django 通过 属性代理模式 + 注册表模式,构建了一个高度灵活、可插拔的缓存系统

  • cache 是一个智能代理,隐藏了底层复杂性;
  • CACHES 配置 决定了真实后端类型;
  • 开发者只需关注业务逻辑,无需关心缓存实现细节。

💡 设计启示
当你需要“同一接口,多种实现,运行时切换”时,属性代理模式是一个优雅的解决方案。

这种模式不仅用于缓存,还广泛应用于 Django 的:

  • 数据库路由(db_router
  • 日志处理器
  • 邮件后端
  • 文件存储(default_storage

掌握它,你就掌握了 Django “约定优于配置” 和 “可插拔架构” 的核心思想。


延伸阅读