Python import 到底引入了什么

昨天学习小组里有人遇到了个奇怪的问题
AttributeError: 'module' object has no attribute 'model'

先看运行正常的代码

from PythonCard import model
class MainWindow(model.Background):
    pass
app = model.Application(MainWindow)
app.MainLoop()

再看下面运行失败的

import PythonCard
class MainWindow(PythonCard.model.Background):
    pass
app = PythonCard.model.Application(MainWindow)
app.MainLoop()

这两者似乎没有区别,那么错在了哪里呢?两种 import 到底有什么不同?我进行了初步实验。

from PythonCard import model
print model
print PythonCard

### 输出 ###

Traceback (most recent call last):
  File "test.py", line 7, in 
    print PythonCard
NameError: name 'PythonCard' is not defined
import PythonCard
print PythonCard
print PythonCard.model

### 输出 ###

Traceback (most recent call last):
  File "test.py", line 4, in 
    print PythonCard.model
AttributeError: 'module' object has no attribute 'model'

Python 之所以能 import package,是因为在 package 的目录下有个 __init__.py 文件,它表明了这个目录的 package 身份。(py3.3 以上不需要此文件)
我猜想 import PythonCard 是将 __init__.py 的 namespace 并入了当前文件的 namespace,而 from PythonCard import model 直接引入了 model 这个 module。

我又试了一下

from PythonCard.model import WidgetDict
print WidgetDict
### 输出 ###
PythonCard.model.WidgetDict

import PythonCard.model
print PythonCard.model
### 输出 ###

这表明 from 和 import 后既可以是 package 又可以是 module。(想想也是,要是不支持,岂不是麻烦死了)

修复错误

试了这么多,那为什么 import PythonCard 后没法调用 PythonCard.model 呢?还是要回到 namespace 上来,我尝试修改 __init__.py 文件。

"""
Created: 2001/08/05
Purpose: Turn PythonCard into a package

__version__ = "$Revision: 1.1.1.1 $"
__date__ = "$Date: 2001/08/06 19:53:11 $"

"""

# 我添加的
import model

添加了 import model 之后

import PythonCard
print PythonCard
print PythonCard.model

### 输出 ###


这样就可以修复上面代码的运行错误了,既然在 __init__.py 这个 namespace 中没有 model,那么我们只要引入就可以了。(但是,既然作者没有支持这种引入方式,如果你依然这样引入,可能需要不停地添加,不如直接引入 module,避免链式调用失败)

参考资料

接下来我在官方文档及网络上查阅资料,在模块这一章发现了这些

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...

import sound.effects.echo
# This loads the submodule sound.effects.echo. It must be referenced with its full name.
sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

# An alternative way of importing the submodule is:
from sound.effects import echo
# This also loads the submodule echo, and makes it available without its package prefix, so it can be used as follows:
echo.echofilter(input, output, delay=0.7, atten=4)

# Yet another variation is to import the desired function or variable directly:
from sound.effects.echo import echofilter
# Again, this loads the submodule echo, but this makes its function echofilter() directly available:
echofilter(input, output, delay=0.7, atten=4)

这几种是当 __init__.py 无内容时,正确的引入方式。

The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code for the package or set the __all__ variable, described later.
__init__.py 可以没有内容,但也可以为 package 执行一些初始化操作,它还拥有一个 __all__ 变量。初始化操作我在上头已经验证过,不再重复,但这个变量是干嘛的?

再看文档的下面一段

if a package’s __init__.py code defines a list named __all__, it is taken to be the list of module names that should be imported when from package import * is encountered.
当你在 __init__.py 中定义了 __all__ 时,如果你 from package import * ,那么这时会引入你指定的几个 module。

测试一下,未指定模块时

from PythonCard import *
print model

### 输出 ###
Traceback (most recent call last):
  File "test.py", line 6, in 
    print model
NameError: name 'model' is not defined

指定模块时

"""
Created: 2001/08/05
Purpose: Turn PythonCard into a package

__version__ = "$Revision: 1.1.1.1 $"
__date__ = "$Date: 2001/08/06 19:53:11 $"

"""

# import model
__all__ = ["model"]

测试一下能否成功引入

from PythonCard import *
print model

### 输出 ###

import statement

我在这里还看到了引入语句的语法和详细执行过程,内容略多,我还没去理解。

这里还有一篇更详细的文章,如果你理解了以上内容,看完一定会有收获。
https://chrisyeh96.github.io/2017/08/08/definitive-guide-python-imports.html

你可能感兴趣的:(Python import 到底引入了什么)