用实例来说明 import 的作用吧.
创建以下包结构. 一个文件夹 cookFish/, 下面包含两个文件, __init__.py 和 cookBook.py.
为什么取这几个名字呢? 假设我想用 Python 去做和鱼相关的菜, 这件事情很复杂, 所以我给它创建了一个包, 名叫 cookFish, 既然是包, 在它下面必须得创建一个文件__init__.py. 烧鱼必备条件之一就是菜谱, 所以接着创建了 cookBook.py. 这几个文件对我们这次来说就足够了, 所以就没有再创建其他文件了.
cookFish/
__init__.py
cookBook.py
在 cookFish/__init__.py 中输入如下代码:
- __version__ = '0.1'
- __author__ = 'XIE Byron'
- def cookFish_hello():
- print("cookFish_Hello() from cookFish/__init__.py")
在 cookFish/cookBook.py 中输入如下代码:
- def cookBook_hello():
- print("cookBook_hello() from cookBook.py")
提示: 下面的实例都是在 Python 自带的命令行解释器 (Windows+python 3.7) 中运行的结果. 如果你在其他环境下运行, 比如 jupyter notebook, 输出会有差异.
"import package-name" 都做了什么?
导入包 cookFish.
>>> import cookFish
提示:
如果 import 时出现错误 ModuleNotFoundError, 如下:
- >>> import cookFish
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- ModuleNotFoundError: No module named 'cookFish'
建议先将 Python 的当前工作目录设置为 cookFish 的 父文件夹(就是包含 cookFish 文件夹的文件夹). 命令如下:
- >>> import os
- >>> os.chdir(r'path\to\parent\folder\of\cookFish')
用 dir 操作查看当前命名空间和 cookFish 命名空间下都有哪些内容.
- >>> dir() # 查看当前命名空间下的对象. 注意: cookFish 在当前命名空间下.
- ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cookFish', 'os']
- >>> dir(cookFish) # 查看 cookFish 命名空间下的对象.
- ['__author__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'cookFish_hello']
其中的的 __author__, __version__, cookFish_hello 是我们定义的, 都导入到了 cookFish 的命名空间下. 但是 cookFish 下的模块 cookBook.py 没有被导入. 这是因为直接 import cookFish 只运行 cookFish 文件夹下的 __init__.py 文件, 不会运行其他模块, 所以 cookBook 没有被导入.
提示: Python 中的模块指后缀 .py 的文件, 也叫脚本. 包 指包含 __init__.py 文件的一个文件夹, 一般还会包含其他模块.
包 / 模块的命名空间
这里讲一下我对概念 "在 cookFish 的命名空间下" 的理解.
Python 的 import A 会把 A 的 Python 代码运行一遍, 并把运行结果放在一个叫 A 的命名空间下.
提示: 如果 A 是包, A 的 Python 代码就是 文件夹 A 下的 __init__.py 中的代码. 如果 A 是模块, 那么就是文件 A.py 中的代码.
import B 会把 B 的 Python 代码运行一遍, 并把运行结果放在一个叫 B 的命名空间下. 假设 A 和 B 中都有一个叫 X 的对象, A 中的 X 在当前命名空间下叫 A.X,B 中的 X 在当前命名空间下叫 B.X, 两个 X 在当前命名空间下不重名.
提示: 这里的对象 指 Python 中的变量 / 属性, 函数, 类, 实例等等.
比如__version__属性 (或者叫它变量) 就在 cookFish 的命名空间下, 我们只能通过 cookFish.__version__ 才能访问到 __version__, 直接输入 __version__ 访问不到它, 会报错.
直接输入__version__ 运行会报如下错误:
- >>> __version__
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- NameError: name '__version__' is not defined
其他导入包 / 模块的方式
如果我们想导入 cookFish 下的模块 cookBook 呢? 可以用下面的语法:
>>> import cookFish.cookBook
然后在 cookFish 的命名空间下又多了 cookBook.
- >>> dir(cookFish)
- ['__author__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'cookBook', 'cookFish_hello']
然后就能通过全名 cookFish.cookBook 访问 cookBook.py 中的对象了, 比如:
- >>> cookFish.cookBook.cookBook_hello()
- cookBook_hello() from cookBook.py
好长的名字啊, 能不能短一点啊? 当然可以:
>>> import cookFish.cookBook as cb
然后在当前命名空间下就多了对象 cb:
- >>> dir()
- ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cb', 'cookFish', 'os']
然后就能通过别名 cb 来访问 cookBook.py 中的对象了, 比如:
- >>> cb.cookBook_hello()
- cookBook_hello() from cookBook.py
那我能不能只导入 cookBook_hello()到当前命名空间? 当然可以
>>> from cookFish.cookBook import cookBook_hello
然后 cookBook_hello 就被导入到当前命名空间下了:
- >>> dir()
- ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cb', 'cookBook_hello', 'cookFish', 'os']
然后就能直接访问 cookBook_hello()了, 不用任何前缀:
- >>> cookBook_hello()
- cookBook_hello() from cookBook.py
"from 包 / 模块名 import *" 是导入所有对象吗?
那我可以一次性导入 cookFish 下的所有模块, 所有包吗? 可以也不可以.
Python 有一个条指令
from 包 / 模块名 import *
比如 from cookFish import *, 给我们的第一感觉是, 这条指令是遍历了 cookFish 下的所有文件, 找到这个包下面的所有包和模块, 把他们统统导入到当前命名空间.
但不幸的是, 这个操作在 Windows 和 Mac 系统上不能很好地实现. 因为它们的文件系统不能提供准确的文件名大小写信息. 在这两个平台上, Python 不知道应该把 ECHO.py 导入为模块 echo, Echo 还是 ECHO, 或者其他.(比如 Windows 95 上面, 所有文件名的首字母都会显示为大写). 如果 Python 把 ECHO.py 导入为 模块 Echo, 但实际 Python 代码中有时按照 echo 使用的, 那肯定会报错.[1]
Python 支持大小写, Echo 和 ECHO 是两个不一样的对象
Python 的唯一的解决办法是包的作者提供一个明确的包的索引, 告诉 Python 在 Python 代码中如何命名这个模块. import 语句定义下面一个约定, 如果在包的 __init__.py 中定义了一个 __all__ 列表, 在 from xxx import * 时, Python 就会把 __all__ 列表中的对象导入.
! 注意:
__all__ 只对 from xxx import * 有影响, 对其他 import 操作没有任何影响
在 cookFish/__init__.py 中, 我们只把函数 cookFish_hello 加入__all__ 中, 代码如下:
- __all__ = ['cookFish_hello', ] # added to support `from xxx import *`
- __version__ = '0.1'
- __author__ = 'XIE Byron'
- def cookFish_hello():
- print("cookFish_Hello() from cookFish/__init__.py")
重启 Python 解释器, 在导入之前, 先运行 dir()显示当前命名空间的对象.
- >>> dir()
- ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'os']
! 注意:
Python 解释器为了提高运行效率, 同一个模块只会导入一次. 一个模块被导入后, 再次运行导入命名不会重新导入. 为了显示 from xx import * 的特殊性, 所以需要重启 Python 解释器(就是关闭 Python 解释器, 然后重新进入).
然后运行如下:
>>> from cookFish import *
然后输入 dir() 查看 cookFish_Hello()是否被导入到了当前命名空间.
- >>> dir()
- ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cookFish_hello', 'os']
可以看到只有在__all__列表中的 cookFish_hello 被导入到当前命名空间, 其他什么都没有导入, 连 cookFish 本身也没有被导入.
所以问题 "可以一次性导入 cookFish 下的所有模块, 所有包吗?" 的答案是: 是否能一次导入, 取决于包的作者有没有把所有子模块 / 子包都加入到 __all__列表中.
参考
[1] Built-in Package Support in Python 1.5 https://www.python.org/doc/essays/packages/
版本
[1] version 1.0, released on 2019-04-21
[2] version 1.1, released on 2019-04-21
添加了 Python 命令的输出. 运行工具为 Windows 版本 Python(3.7)自带的命令行解释器.
来源: https://www.cnblogs.com/byronxie/p/10745292.html