昨天学习小组里有人遇到了个奇怪的问题
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
### 输出 ###
<module 'PythonCard.model' from '/Users/helsonxiao/CodeCamp/importing/lib/python2.7/site-packages/PythonCard/model.pyc'>
Traceback (most recent call last):
File "test.py", line 7, in <module>
print PythonCard
NameError: name 'PythonCard' is not defined
import PythonCard
print PythonCard
print PythonCard.model
### 输出 ###
<module 'PythonCard' from '/Users/helsonxiao/CodeCamp/importing/lib/python2.7/site-packages/PythonCard/__init__.pyc'>
Traceback (most recent call last):
File "test.py", line 4, in <module>
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
### 输出 ###
<module 'PythonCard.model' from '/Users/helsonxiao/CodeCamp/importing/lib/python2.7/site-packages/PythonCard/model.pyc'>
这表明 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
### 输出 ###
<module 'PythonCard' from '/Users/helsonxiao/CodeCamp/importing/lib/python2.7/site-packages/PythonCard/__init__.py'>
<module 'PythonCard.model' from '/Users/helsonxiao/CodeCamp/importing/lib/python2.7/site-packages/PythonCard/model.pyc'>
这样就可以修复上面代码的运行错误了,既然在 __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 <module>
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
### 输出 ###
<module 'PythonCard.model' from '/Users/helsonxiao/CodeCamp/importing/lib/python2.7/site-packages/PythonCard/model.pyc'>
import statement
我在这里还看到了引入语句的语法和详细执行过程,内容略多,我还没去理解。
这里还有一篇更详细的文章,如果你理解了以上内容,看完一定会有收获。
https://chrisyeh96.github.io/2017/08/08/definitive-guide-python-imports.html