1.举例说明

**import**语句用来导入其他 python文件(称为模块 module),使用该模块里定义的类、方法或者变量,从而达到代码复用的目的。为了方便说明,我们用实例来说明 import 的用法,读者朋友可以跟着尝试(尝试时建议使用 python3,python2 和 python3 在 import 的表现有差异,之后会提到)。

首先,先建立一个文件夹Pythonproject作为工作目录,并在其内建立两个文件 main.py 和 aa.py,忽略.ieda和venv,虚拟环境和pycharm工程配置,不用管。
image.png
在main.py 写入代码:

  1. import os
  2. import aa
  3. aa.print_Self()

在aa.py 写入代码:

  1. def print_Self():
  2. print('aa true')

运行main.py,发现没有报错,且打印出aa true,说明这样使用 import 没有问题。由此我们总结出 import 语句的第一种用法。

2.第一种用法:import module_name

import module_name。即 import 后直接接模块名。在这种情况下,Python 会在两个地方寻找这个模块,

  • 第一是 sys.path(通过运行代码import sys; print(sys.path)查看),os 这个模块所在的目录就在列表 sys.path 中,一般安装的 Python 库的目录都可以在 sys.path 中找到(前提是要将 Python 的安装目录添加到电脑的环境变量),所以对于安装好的库,我们直接 import 即可。
  • 第二个地方就是运行文件(这里是 main.py)所在的目录,因为 aa.py 和运行文件在同一目录下,所以上述写法没有问题。

在工程文件下新建文件test.py并写入:

  1. import sys
  2. print(sys.path)

输出所有环境变量文件夹

  1. ['C:\\Users\\cheng\\PycharmProjects\\pythonProject',
  2. # 工程文件夹
  3. 'C:\\Program Files\\JetBrains\\PyCharm 2021.1.2\\plugins\\python\\helpers\\pycharm_display',
  4. # pycharm_display文件夹,一些测试显示文件
  5. 'C:\\Users\\cheng\\AppData\\Local\\Programs\\Python\\Python38\\python38.zip',
  6. 'C:\\Users\\cheng\\AppData\\Local\\Programs\\Python\\Python38\\DLLs',
  7. 'C:\\Users\\cheng\\AppData\\Local\\Programs\\Python\\Python38\\lib',
  8. 'C:\\Users\\cheng\\AppData\\Local\\Programs\\Python\\Python38',
  9. # 以上四个是python3.8的路径
  10. 'C:\\Users\\cheng\\PycharmProjects\\pythonProject\\venv',
  11. 'C:\\Users\\cheng\\PycharmProjects\\pythonProject\\venv\\lib\\site-packages',
  12. # 工程虚拟环境路径
  13. 'C:\\Program Files\\JetBrains\\PyCharm 2021.1.2\\plugins\\python\\helpers\\pycharm_matplotlib_backend'
  14. # 画图
  15. ]

用上述方法导入原有的 sys.path 中的库没有问题。但是,最好不要用上述方法导入同目录下的文件!

3.第二种方法:

from package_name import module_name

在 Pythonproject目录下新建一个目录 branch,在 branch 中新建文件 bb.py并写入:

  1. def print_Self():
  2. print('bb true')

如何在main.py中导入bb.py 呢,请看更改后的main.py:

  1. import aa
  2. from branch import bb
  3. aa.print_Self()
  4. bb.print_Self()

from package_name import module_name。一般把模块组成的集合称为包(package)。与第一种写法类似,Python 会在 sys.path 和运行文件目录这两个地方寻找包,然后导入包中名为 module_name 的模块。

现在我们来说明为什么不要用 import 的第一种写法来导入同目录下的文件。在 Branch 目录下新建 cc.py 文件,内容如下:

  1. def print_Self():
  2. print('cc true')

然后我们在 bb.py 中直接导入 cc,bb.py 变为:

import cc
def print_Self():
    print('bb true')

这时候运行 main.py 就会报错了:
image.png
看一下导入流程:
image.png
main使用from Branch import bb导入bb,然后在 bb.py 中用import cc导入cc入。
看出问题了吗?cc.py 和 main.py 不在同一目录,怎么能直接使用import cc导入 cc 呢。

(可以试试直接在Pythonproject目录下新建另一个 cc.py 文件,你会发现再运行 main.py 就不会出错了,只不过导入的是Pythonproject目录下的 cc.py 了)

面对上面的错误,使用 python2 运行 main.py 就不会报错,因为在 python2 中,上面提到的 import 的两种写法都属于相对导入,而在 python3 中,却属于绝对导入。话说到了这里,就要牵扯到 import 中最关键的部分了——相对导入和绝对导入。

上面提到的两种写法属于绝对导入,即用于导入 sys.path 中的包和运行文件所在目录下的包。对于 sys.path 中的包,这种写法毫无问题;导入自己写的文件,如果是非运行入口文件(上面的 main.py 是运行入口文件,可以使用绝对导入),则需要相对导入。

比如对于非运行入口文件 bb.py,其导入 cc.py 需要使用相对导入:

from . import cc
def print_Self():
    print('bb true')

这时候再运行 main.py 就 ok 了。列举一下相对导入的写法:

  • from . import module_name。导入和自己同目录下的模块。
  • from .package_name import module_name。导入和自己同目录的包的模块。
  • from .. import module_name。导入上级目录的模块。
  • from ..package_name import module_name。导入位于上级目录下的包的模块。
  • 当然还可以有更多的.,每多一个点就多往上一层目录。

不知道你有没有留神上面的一句话——“上面的 main.py 是运行入口文件,可以使用绝对导入”,这句话是没问题的,也和我平时的做法一致。那么,运行入口文件可不可以使用相对导入呢?比如 main.py 内容改成:

from .Branch import m3
m3.printSelf()

答案是可以,但不能用python main.py命令,而是需要进入到 Pythonproject 所在的目录,使用python -m Pythonproject.main来运行。为什么?关于前者,PEP 328 提案中的一段文字好像给出了原因:

Relative imports use a module’s name** attribute to determine that module’s position in the package hierarchy. If the module’s name does not contain any package information (e.g. it is set to ‘__main__**’) then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.

我不太懂,但是又有一点明白。我们应该见过下面一段代码:

if __name__ == '__main__':
    main()

意思是如果运行了当前文件,则name变量会置为main,然后会执行 main 函数,如果当前文件是被其他文件作为模块导入的话,则name为模块名,不等于main,就不会执行 main 函数。比如对于上述更改后的 main.py,执行python main.py命令后,会报如下错误:

Traceback (most recent call last): File “main.py”, line 1, in from .Branch import bb ModuleNotFoundError: No module named ‘main.Branch’;’main‘ is not a package

据此我猜测执行python main.py命令后,当前目录所代表的包’.’变成了main

那为什么python -m Pythonproject.main就可以呢?那位台湾老师给出了解释:

执行指令中的 - m 是为了让 Python 预先 import 你要的 package 或 module 给你,然后再执行 script。

即不把 main.py 当作运行入口文件,而是也把它当作被导入的模块,这就和非运行入口文件有一样的表现了。

注意,在 Pythonproject 目录下运行python -m main是不可以的,会报 ImportError: attempted relative import with no known parent package 的错误。因为 main.py 中的from .Branch import bb中的. ,解释器并不知道是哪一个 package。使用python -m Pythonproject.main,解释器就知道.对应的是 Pythonproject 这个 package。

那反过来,如果 main.py 使用绝对导入(from Branch import bb),能使用python -m main运行吗?我试了一下,如果当前目录是 Pythonproject 就可以。如果在其他目录下运行,比如在 Pythonproject 所在的目录(使用python -m Pythonproject.main运行),就不可以。这可能还是与绝对导入相关。

(之前看到了一个大型项目,其运行入口文件有一大堆的相对导入,我还傻乎乎地用 python 直接运行它。之后看到他给的样例运行命令是带了 - m 参数的。现在才恍然大悟。)

4.同级不同文件夹问题

image.png
bb和cc在两个不同的文件夹
bb.py

import cc
def print_Self():
    print('bb true')

运行main.py显然报错。没有cc
先来一个
更改main.py

import aa
import os
import sys


BASE_DIR = os.path.dirname(os.path.abspath(__file__))# 获取main.py路径
sys.path.append(os.path.join(BASE_DIR, 'teach'))# 将teach加入环境变量
from branch import bb

aa.print_Self()
bb.print_Self()

注意:一定要先将teach加入环境变量,再from branch import bb,
如果将branch也加入环境变量,只用import bb即可。但是这么做运行虽然没问题,IDE里面会一直报提醒
image.png
所以还是用from branch import bb
如果此时回到bb.py,发现import cc也是报错提醒,因为环境变量是运行时候才加上,IDE目前找不到,所以会提示。用下面这个解决,

from teach import cc # teach在初始环境变量里

所以不推荐用加入环境变量的方式,绝对导入优先,尽量尝试使用相对导入

5.import其他简单但实用的用法

理解 import 的难点差不多就这样了。下面说一说 import 的其他简单但实用的用法。

  • import moudle_name as alias。有些 module_name 比较长,之后写它时较为麻烦,或者 module_name 会出现名字冲突,可以用 as 来给它改名,如import numpy as np
  • from module_name import function_name, variable_name, class_name。上面导入的都是整个模块,有时候我们只想使用模块中的某些函数、某些变量、某些类,用这种写法就可以了。使用逗号可以导入模块中的多个元素。
  • 有时候导入的元素很多,可以使用反斜杠来换行,官方推荐使用括号。
from Tkinter import Tk, Frame, Button, Entry, Canvas, Text, \
    LEFT, DISABLED, NORMAL, RIDGE, END    # 反斜杠换行
from Tkinter import (Tk, Frame, Button, Entry, Canvas, Text,
    LEFT, DISABLED, NORMAL, RIDGE, END)    # 括号换行(推荐)

6.可能碰到的问题

说到这感觉 import 的核心已经说完了。再跟着上面的博客说一说使用 import 可能碰到的问题吧。

问题 1 描述:ValueError: attempted relative import beyond top-level package。直面问题的第一步是去了解熟悉它,最好是能复现它,让它躺在两跨之间任我们去践踏蹂躏。仍然是上面四个文件,稍作修改,四个文件如下:

# main.py
from Branch import bb
bb.printSelf()
# aa.py
def printSelf():
    print('module2')
# bb.py
from .. import aa # 复现的关键在这 #
def printSelf():
    print('In bb')

运行python main.py,就会出现该问题。问题何在?我猜测,运行 main.py 后,main 代表的模块就是顶层模块(参见上面 PEP 328 的引用),而 bb.py 中尝试导入的aa模块所在的包(即 Pythonproject 目录代表的包)比 main 的层级更高,所以会报出这样的错误。
怎么解决呢?
直接import aa(aa在初始环境变量里)