<p><code>os.path</code>的工作方式很有趣。看起来<code>os</code>应该是一个带有子模块<code>path</code>的包,但实际上<code>os</code>是一个普通的模块,它使用<code>sys.modules</code>来注入<code>os.path</code>具有魔力。下面是发生的事情:</p>
<ul>
<li><p>当Python启动时,它会将一堆模块加载到<code>sys.modules</code>。它们没有绑定到脚本中的任何名称,但是当您以某种方式导入它们时,可以访问已经创建的模块。</p>
<ul>
<li><code>sys.modules</code>是缓存模块的dict。当您导入一个模块时,如果它已经被导入到某个地方,它将获取存储在<code>sys.modules</code>中的实例。</li>
</ul></li>
<li><p><code>os</code>是Python启动时加载的模块之一。它将其<code>path</code>属性分配给操作系统特定的路径模块。</p></li>
<li><p>它注入<code>sys.modules['os.path'] = path</code>,这样就可以像子模块一样执行<code>import os.path</code>。</p></li>
</ul>
<p>我倾向于把<code>os.path</code>看作是一个我想使用的模块,而不是<code>os</code>模块中的一个东西,所以即使它不是真正的<code>os</code>包的子模块,我还是像一个模块一样导入它,并且我总是这样做。这与<code>os.path</code>的记录方式是一致的。</p>
<hr/>
<p>顺便说一句,我认为这种结构导致了很多Python程序员对模块、包和代码组织的早期混淆。这有两个原因</p>
<ol>
<li><p>如果您将<code>os</code>视为一个包,并且知道您可以执行<code>import os</code>,并且可以访问子模块<code>os.path</code>,那么稍后您可能会感到惊讶,因为您无法执行<code>import twisted</code>,并且在不导入它的情况下自动访问<code>twisted.spread</code>。</p></li>
<li><p>令人困惑的是,<code>os.name</code>是一个普通的东西,一个字符串,<code>os.path</code>是一个模块。我总是用空的<code>__init__.py</code>文件来构造包,这样在同一级别上我总是有一种类型的东西:模块/包或其他东西。一些大型Python项目采用这种方法,这种方法倾向于生成更结构化的代码。</p></li>
</ol>