Django是如何将字符串转换为模块的?

17 投票
3 回答
3769 浏览
提问于 2025-04-16 03:53

我正在尝试理解Django的一个神奇之处:它可以把字符串转换成模块。

settings.py文件中,INSTALLED_APPS是这样声明的:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
)

里面全是字符串。但是Django会把这些字符串转换成模块,并在后面导入它们。

我也想做到这一点,但我不知道怎么做。

我在settings.py中有一个渲染器调度器的字典:

RESOUCE_RENDERER = {
    'video': 'video_player',
    'audio': 'audio_player', 
}

我想以后这样使用它:RESOURCE_RENDERER['video'](MyVideo)

我不能直接给函数命名(比如video_player),因为它在一个需要settings.py的模块里。

3 个回答

0

你可以使用 import_string() 来导入一个带有点号的模块路径的模型,正如下面的文档所说:

导入一个带点号的模块路径,并返回路径中最后一个名字所指定的属性或类。如果导入失败,会抛出 ImportError 错误。

举个例子,你在 my_app/models.py 文件中有一个 Person 模型,如下所示:

# "my_app/models.py"

class Person(models.Model):
    name = models.CharField(max_length=20)

然后,你可以在 my_app/views.py 文件中使用 import_string() 来导入 Person 模型,如下所示:

# "my_app/views.py"

from django.http import HttpResponse
from django.utils.module_loading import import_string

def test(request): # ↓ Imports "Person" model ↓
    Person = import_string('my_app.models.Person')
    print(Person.objects.all())
    return HttpResponse("Test")

而上面的代码和下面的代码是一样的:

# "my_app/views.py"

from django.http import HttpResponse
from .models import Person # Imports "Person" model

def test(request):
    print(Person.objects.all())
    return HttpResponse("Test")

所以,控制台输出的结果也是一样的:

<QuerySet [<Person: Person object (1)>, <Person: Person object (2)>]>
8

看看 django.conf.__init__.py 文件,基本上它是用 importlib 这个模块来实现的,像这样:

try:
    mod = importlib.import_module(self.SETTINGS_MODULE)
except ImportError, e:
    raise ImportError("Could not import settings '%s' 
               (Is it on sys.path? Does it have syntax errors?):
                %s" % (self.SETTINGS_MODULE, e))

# Settings that should be converted into tuples if they're mistakenly entered
# as strings.
tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS")

编辑: 根据提问者的要求,我扩展了这个例子,并在下面添加了一些内容。

假设你在这个模块里有一个函数列表,比如叫做“要调用的函数”,那么你可以这样调用每个函数:

ARGUMENTS = '()'

for FUNCTION in FUNCTIONS_TO_CALL:
    function_string = FUNCTION + ARGUMENTS
    exec(function_string)

这里假设每个函数都有相同的参数。如果用户输入了函数名,你可以用一个if语句来判断,并根据函数名提供不同的参数。你也可以通过查看Python文件来了解这些函数应该接受什么参数。

你还可以检查模块对象(我猜这是可能的,我不太确定)来看看在调用 exec()eval() 之前那个函数是否存在。我不太清楚是否能从函数对象中判断它需要什么参数。我觉得可能可以,但这可能是一个单独的问题(也许已经有人回答过了?)。

16

自从Django 1.7版本开始,有一个很简单的函数可以用来做这个。比如说:

from django.utils.module_loading import import_string
my_module = import_string('path.to.my_module')

你还可以从模块中获取类:

MyClass = import_string('path.to.my_module.MyClass')

撰写回答