Django是如何将字符串转换为模块的?
我正在尝试理解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 个回答
你可以使用 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)>]>
看看 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()
之前那个函数是否存在。我不太清楚是否能从函数对象中判断它需要什么参数。我觉得可能可以,但这可能是一个单独的问题(也许已经有人回答过了?)。
自从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')