PYTHONPATH与PERL5LIB的行为

4 投票
1 回答
688 浏览
提问于 2025-04-16 16:04

Python的路径处理方式和PERL5LIB不一样,这让把库分开放到不同的地方变得有点麻烦。让我来解释一下PERL是怎么工作的:

假设目录结构是这样的:

project/lib/bar/foo.pm
common/lib/bar/baz.pm

现在把PERL5LIB设置为'project/lib:common/lib'

在我的PERL脚本中,我可以这样做:

use bar::foo; # this comes from project/lib/bar/foo.pm
use bar::baz; # this comes from common/lib/bar/baz.pm

但是在Python中,如果目录结构一样(只是文件变成了.py),并且把同样的目录添加到PYTHONPATH(当然还要在project/lib/bar和common/lib/bar目录里加上一个空的__init__.py文件):

import bar.foo # this successfully imports from project/lib/bar/foo.py
import bar.baz # this fails!

在Python中有没有什么办法解决这个问题,因为这样分开代码真的很麻烦。

编辑:为了更清楚地说明__init__.py文件的位置。

1 个回答

3

你的第一个问题是,你试图把Perl的思维方式强行套用到Python上。第二个问题是,你似乎不太明白Python的命名空间系统是怎么工作的。你不能在全局命名空间中导入同一个包两次。这种方式是行不通的。

Python会使用它找到的第一个匹配项。当你从第一个位置导入bar.foo时,Python就找到了bar这个包,所以在你尝试从后一个位置导入bar.baz时,它根本不会再去试。

当你导入一个模块时,它会被添加到全局命名空间中,同时也会在sys.modules字典中被记录下来。假设你刚启动一个新的Python解释器,如果你导入sys,你会看到它在全局命名空间中:

>>> import sys
>>> globals().keys()
['__builtins__', '__name__', 'sys', '__doc__', '__package__']

如果你通过键获取它,你会得到模块对象:

>>> globals()['sys']
<module 'sys' (built-in)>

现在如果你导入foo,你也会看到它同样出现在全局命名空间中:

>>> import foo
>>> globals().keys()
['__builtins__', '__package__', 'sys', '__name__', 'foo', '__doc__']

而且也在sys.modules中:

>>> sys.modules['foo']
<module 'foo' from 'foo.pyc'>
>>> globals()['foo']
<module 'foo' from 'foo.pyc'>

即使你从全局命名空间中删除foo,它仍然会保留在sys.modules中:

>>> del foo
>>> 'foo' in globals()
False
>>> 'foo' in sys.modules
True

这是为什么呢?可以把sys.modules想象成一个已经导入模块的注册表。这是为了优化,如果你导入同一个包的多个部分,已经加载的部分就不会再被重复加载。真正“卸载”一个模块的唯一方法是从sys.modules和全局命名空间中删除它。

我希望通过这个说明,你也能明白每个包或模块对象在任何给定程序的命名空间中只能存在一次。这就是你尝试的操作失败的原因。Python第一次成功导入了bar,所以它不会再尝试第二次导入。

如果你真的想让同一个包名的两个不同路径在文件系统中位于不同的位置,你应该研究一下Python的命名空间包。这将允许你在其他位置安装包,并将它们绑定到另一个包的命名空间。

另见:

撰写回答