Python 如何避免Django中的导入时间数据库访问?

Python 如何避免Django中的导入时间数据库访问?,python,django,database,Python,Django,Database,我的Django应用程序有许多分类,我将它们存储在分类模型中。我经常在代码中引用这些内容,因此我发现有一个模块引用这些类别和类别组(“常量”)是很有用的,这样打字错误很快就会失败。这还提供了缓存的好处。最后,它是实际的模型,因此它具有所有相关的功能。它看起来像这样: def load_category(name): return Category.objects.get(name=name) DOGS = load_category("dogs") CATS = load_category

我的Django应用程序有许多分类,我将它们存储在
分类
模型中。我经常在代码中引用这些内容,因此我发现有一个模块引用这些类别和类别组(“常量”)是很有用的,这样打字错误很快就会失败。这还提供了缓存的好处。最后,它是实际的模型,因此它具有所有相关的功能。它看起来像这样:

def load_category(name):
  return Category.objects.get(name=name)

DOGS = load_category("dogs")
CATS = load_category("cats")
但是,这会导致导入时数据库访问,并导致各种问题。在添加了一个具有类似这样引用的新类别之后,我必须先运行数据迁移,然后才能运行
/manage.py
。在切换到使用Django的测试框架时,我遇到了一个新问题,即这些测试框架是从默认(例如dev或prod)数据库加载的,而不是中明确提到的测试数据库

如果您的代码试图访问数据库,而数据库的模块 编译后,这将在设置测试数据库之前发生,并且 潜在的意外结果。例如,如果您有一个数据库 查询模块级代码和真实数据库中存在的生产数据 可能会污染你的测试。有这样重要的时间是个坏主意 在代码中进行数据库查询-重写代码,使其 他不会这么做的

在避免导入时访问数据库的同时,获得这些引用的好处的最佳模式是什么?

一种可能的解决方案是代理模式,它返回一个伪类别,该类别转发所有模型的功能,但在必要时才访问数据库。我想看看其他人是如何用这种方法或其他解决方案解决这个问题的

(相关但不同的问题:)

最后进近

凯文·克里斯托弗·亨利的方法对我很有效。然而,除了修复这些声明的引用之外,我还不得不延迟从其他代码访问这些引用。在这里,我发现两种方法很有用

首先,我发现。这个简单对象将工厂函数作为输入,延迟执行该函数以生成包装对象

MAP_OF_THINGS = Proxy(lambda: {
        DOG: ...
        CAT: ...
})
完成同样事情的一种类似方法是将代码推入到用代码装饰的工厂函数中,这样它们就只能执行一次

注意:我最初尝试使用上面的代理对象作为对模型对象的延迟访问问题的直接解决方案。然而,尽管是很好的模仿,但在查询和过滤这些对象时,我得到了:

TypeError: 'Category' object is not callable
果然,
Proxy
callable
返回
True
(尽管文档称这并不保证它是可调用的)。Django查询似乎太聪明了,必然会发现与虚假模型不兼容的东西


对于您的应用程序,
Proxy
可能已经足够好了。

模块级变量没有多少功能,因为您无法覆盖它们的访问函数。但是,您可以通过为类和实例变量执行此操作。您可以使用该选项惰性地加载类别:

class Categories(object):
    _categories = {'DOGS': 'dogs', 'CATS': 'cats'}
    def __getattribute__(self, key):
        try:
            return super(Categories, self).__getattribute__(key)
        except AttributeError:
            pass
        try:
            value = load_category(self._categories[key])
        except KeyError:
            raise AttributeError(key)
        setattr(self, key, value)
        return value

Categories = Categories()  # Shadow class with singleton instance

然后使用
module.Categories.DOGS
,而不是
module.DOGS
。第一次访问时,将加载并存储该类别,以备将来查找。

我自己也遇到过同样的问题,并同意在此处提供一些最佳做法将非常好

我最终采用了一种基于以下内容的方法:

然后在我的模型类中,我有一些特殊的对象:

class Category(models.Model):
    name = models.CharField()

    DOGS = LazyInstance(name="dogs")
    CATS = LazyInstance(name="cats")

所以在导入时不会发生任何事情。第一次访问特殊对象时,将查找(并在必要时创建)和缓存相关实例。

我在functools.partial中使用了lazy_object_代理(它与传递的函数一起工作,但没有传递的参数)。如下所示:

import lazy_object_proxy
from functools import partial

def load_category(name):
  # prepare an argument-less runnable function
  loader = partial(Category.objects.get, name)

  # pass the function to the proxy
  return lazy_object_proxy.Proxy(loader)

DOGS = load_category("dogs")
CATS = load_category("cats")

谢谢这似乎不支持方法调用,但添加getattr可以修复这一问题。然而,仍然有平等的问题,所以我想我需要特殊的方法处理@约翰莱曼:我不知道你所说的支持方法调用是什么意思。这不是使用代理对象;当你访问Category.DOGS时,你会得到一个普通的Django模型实例。明白了!顺便说一句,您的
LazyInstance
需要从
对象继承,否则它会自动失败。谢谢你的回答。@JohnLehmann:很乐意帮忙。我使用的是Python3,您永远不需要显式地继承
对象
,但我相信您适合Python2。
import lazy_object_proxy
from functools import partial

def load_category(name):
  # prepare an argument-less runnable function
  loader = partial(Category.objects.get, name)

  # pass the function to the proxy
  return lazy_object_proxy.Proxy(loader)

DOGS = load_category("dogs")
CATS = load_category("cats")