被迫在Python(/Django)中使用不一致的文件导入路径
最近我在使用Django(Python)时遇到了一些导入的问题... 用文件结构来解释会更清楚:
- project/
- application/
- file.py
- application2/
- file2.py
在 project/application/file.py
里,我有以下内容:
def test_method():
return "Working"
问题出现在 project/application2/file2.py
,当我尝试从上面的文件导入方法时:
from application.file import test_method
通常是可以的,但有时候不行。
from project.application.file import test_method
确实可以工作,但这样做不符合Django的可移植性指南,因为项目文件夹的名称必须始终保持一致。
我其实不介意,但问题在于这个情况时好时坏,大多数时候省略 project
是没问题的,但偶尔又不行(而且我看不出有什么原因)。
我几乎可以肯定我在做一些傻事,但有没有人遇到过这个问题?我是不是应该在所有相关的导入前加上 project
,以保持一致性?老实说,project
文件夹的名称不太可能会改变,我只是想尽量遵循指南。
3 个回答
最好总是用同样的方式来导入,比如说用 project.app.models
。因为如果你用不同的方式导入,可能会导致你的模块被导入了两次,这样有时候会出现一些难以理解的错误,就像在这个问题中讨论的那样。
如果你深入了解Django的理念,你会发现一个项目其实是由多个应用组成的。这些应用之间可能会有依赖关系,这没问题。不过,你总是希望你的应用能够“插拔”,也就是说可以把它们移到其他项目中去使用。为了做到这一点,你需要把代码中与项目相关的部分去掉,这样在导入时就可以像下面这样做。
from aplication.file import test_method
这就是Django推荐的做法。Glenn已经解释了你为什么会遇到错误,所以我就不再说这个了。当你运行命令来启动一个新项目时:
django-admin.py startproject myproject,这会创建一个文件夹,里面有Django需要的一堆文件,比如manage.py和settings.py等等,但它还会为你做另一件事。它会把“myproject”文件夹放到你的Python路径上。简单来说,这意味着你放在这个文件夹里的任何应用,都可以像上面那样导入。你不需要使用django-admin.py来启动项目,因为没有什么神奇的事情发生,这只是一个快捷方式。所以其实你可以把应用文件夹放在任何地方,只要它们在Python路径上,你就可以直接导入它们,这样你的代码就不会依赖于特定的项目,未来的项目也能轻松使用,遵循Django所建立的DRY原则。
要让导入功能找到一个模块,它需要在 sys.path 这个路径列表里。通常,这个列表里会包含一个空字符串"",所以它会搜索当前目录。如果你从项目中加载“application”,它会找到,因为它就在当前目录里。
好吧,这些都是显而易见的事情。让人困惑的是,Python 会记住哪些模块已经被加载了。如果你先加载了 application,然后再加载 application2,而 application2 又导入了 application,这时候模块“application”已经被加载过了。它不需要再去硬盘上找;它直接使用已经加载的那个。另一方面,如果你还没有加载 application,它就会去找,但找不到,因为它不在加载它的目录(“.”)里,也不在其他路径中。
这就可能导致一个奇怪的情况:导入有时候有效,有时候无效;只有在模块已经被加载的情况下,导入才会成功。
如果你想要能够直接用“application”来加载这些模块,你需要把 project/ 这个路径加到 sys.path 里。
(相对导入听起来相关,但似乎 application 和 application2 是两个独立的包——相对导入是用来在同一个包内导入的。)
最后,一定要始终把整个事情当作一个包来处理,或者始终把每个应用当作自己的包来处理。不要混合使用。如果 package/ 在路径中(比如 sys.path 包含 package/..),那么你确实可以用“from package.application import foo”,但如果你还用“from application import foo”,Python 可能不会意识到这两个是同一个东西——它们的名字不同,路径也不同——最后可能会加载两个不同的副本,这可绝对不是你想要的结果。