Python之import的⽤法
Python⽤了快两年了吧,其中有些东西⼀直是稀⾥糊涂地⽤,import便是我⼀直没有明⽩的东西。曾经有过三次解决它的机会,我都因得过且过、⼀拖再拖⽽没能化敌为友。今天下午,它⼜给了我⼀次机会,我想我还是从了它的⼼愿吧。
故事是从这篇台湾同胞的博客()开始的,然后⼜跳到了Python社区的PEP 328提案(),再结合过去的经验以及⼀些测试,我想我⼤概懂了吧。下⾯是我的总结,希望内容能够⾔简意赅、易于理解。
import语句有什么⽤?import语句⽤来导⼊其他python⽂件(称为模块module),使⽤该模块⾥定义的类、⽅法或者变量,从⽽达到代码复⽤的⽬的。为了⽅便说明,我们⽤实例来说明import的⽤法,读者朋友可以跟着尝试(尝试时建议使⽤python3,python2和python3在import 的表现有差异,之后会提到)。
将要建⽴⽂件的结构为:
Tree
|____ m1.py
|____ m2.py
|____ Branch
|____m3.py
|____m4.py
⾸先,先建⽴⼀个⽂件夹Tree作为⼯作⽬录,并在其内建⽴两个⽂件m1.py和m2.py,在m1.py写⼊代码:
import os
import m2
m2.printSelf()
在m2.py写⼊代码:
def printSelf():
print('In m2')
打开命令⾏,进⼊到Tree⽬录下,敲下python m1.py运⾏,发现没有报错,且打印出In m2,说明这样使⽤import没有问题。由此我们总结出import语句的第⼀种⽤法。
import module_name。即import后直接接模块名。在这种情况下,Python会在两个地⽅寻这个模块,第⼀是sys.path(通过运⾏代码import sys; print(sys.path)查看),os这个模块所在的⽬录就在列表sys.path中,⼀般安装的Python库的⽬录都可以在sys.path中到(前提是要将Python的安装⽬录添加到电脑的环境变量),所以对于安装好的库,我们直接import即可。第⼆个地⽅就是运⾏⽂件(这⾥是m1.py)所在的⽬录,因为m2.py和运⾏⽂件在同⼀⽬录下,所以上述写法没有问题。
⽤上述⽅法导⼊原有的sys.path中的库没有问题。但是,最好不要⽤上述⽅法导⼊同⽬录下的⽂件!因为这可能会出错。演⽰这个错误需要⽤到import语句的第⼆种写法,所以先来学⼀学import的第⼆种写法。在Tree⽬录下新建⼀个⽬录Branch,在Branch中新建⽂件
m3.py,m3.py的内容如下:
def printSelf():
print('In m3')
如何在m1中导⼊m3.py呢,请看更改后的m1.py:
from Branch import m3
m3.printSelf()
总结import语句的第⼆种⽤法:
from package_name import module_name。⼀般把模块组成的集合称为包(package)。与第⼀种写法类似,Python会在sys.path和运⾏⽂件⽬录这两个地⽅寻包,然后导⼊包中名为module_name的模块。
numpy库需要安装吗
现在我们来说明为什么不要⽤import的第⼀种写法来导⼊同⽬录下的⽂件。在Branch⽬录下新建m4.py⽂件,m4.py的内容如下:
def printSelf():
print('In m4')
然后我们在m3.py中直接导⼊m4,m3.py变为:
import m4
def printSelf():
print('In m3')
这时候运⾏m1.py就会报错了,说没法导⼊m4模块。为什么呢?我们来看⼀下导⼊流程:m1使⽤from Branch import m3导⼊m3,然后在m3.py 中⽤import m4导⼊m4。看出问题了吗?m4.py和m1.py不在同⼀⽬录,怎么能直接使⽤import m4导⼊m4呢。(读者可以试试直接在Tree⽬录下新建另⼀个m4.py⽂件,你会发现再运⾏m1.py就不会出错了,只不过导⼊的是第⼆个m4.py了)
⾯对上⾯的错误,使⽤python2运⾏m1.py就不会报错,因为在python2中,上⾯提到的import的两种写法都属于相对导⼊,⽽在python3中,却属于绝对导⼊。话说到了这⾥,就要牵扯到import中最关键的部分了——相对导⼊和绝对导⼊。
我们还是谈论python3的import⽤法。上⾯提到的两种写法属于绝对导⼊,即⽤于导⼊sys.path中的包和运⾏⽂件所在⽬录下的包。对于
sys.path中的包,这种写法毫⽆问题;导⼊⾃⼰写的⽂件,如果是⾮运⾏⼊⼝⽂件(上⾯的m1.py是运⾏⼊⼝⽂件,可以使⽤绝对导⼊),则需要相对导⼊。
⽐如对于⾮运⾏⼊⼝⽂件m3.py,其导⼊m4.py需要使⽤相对导⼊:
from . import m4
def printSelf():
print('In m3')
这时候再运⾏m1.py就ok了。列举⼀下相对导⼊的写法:
from . import module_name。导⼊和⾃⼰同⽬录下的模块。
from .package_name import module_name。导⼊和⾃⼰同⽬录的包的模块。
from .. import module_name。导⼊上级⽬录的模块。
from ..package_name import module_name。导⼊位于上级⽬录下的包的模块。
当然还可以有更多的.,每多⼀个点就多往上⼀层⽬录。
不知道你有没有留神上⾯的⼀句话——“上⾯的m1.py是运⾏⼊⼝⽂件,可以使⽤绝对导⼊”,这句话是没问题的,也和我平时的做法⼀致。那么,运⾏⼊⼝⽂件可不可以使⽤相对导⼊呢?⽐如m1.py内容改成:
from .Branch import m3
m3.printSelf()
答案是可以,但不能⽤python m1.py命令,⽽是需要进⼊到Tree所在的⽬录,使⽤python -m Tree.m1来运⾏。为什么?关于前者,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函数。⽐
如对于上述更改后的m1.py,执⾏python m1.py命令后,会报如下错误:Traceback (most recent call last): File "m1.py", line 1, in from .Branch import m3 ModuleNotFoundError: No module named
'_main_.Branch'; '__main__' is not a package
据此我猜测执⾏python m1.py命令后,当前⽬录所代表的包'.'变成了__main__。
那为什么python -m Tree.m1就可以呢?那位台湾⽼师给出了解释:
执⾏指令中的-m是为了让Python预先import你要的package或module给你,然后再执⾏script。
即不把m1.py当作运⾏⼊⼝⽂件,⽽是也把它当作被导⼊的模块,这就和⾮运⾏⼊⼝⽂件有⼀样的表现了。
注意,在Tree⽬录下运⾏python -m m1是不可以的,会报 ImportError: attempted relative import with no known parent package的错误。因为m1.py中的from .Branch import m3中的.,解释器并不知道是哪⼀个package。使⽤python -m Tree.m1,解释器就知道.对应的是Tree这个package。
那反过来,如果m1.py使⽤绝对导⼊(from Branch import m3),能使⽤python -m m1运⾏吗?我试了⼀下,如果当前⽬录是Tree就可以。如果在其他⽬录下运⾏,⽐如在Tree所在的⽬录(使⽤python -m Tree.m1运⾏),就不可以。这可能还是与绝对导⼊相关。
(之前看到了⼀个⼤型项⽬,其运⾏⼊⼝⽂件有⼀⼤堆的相对导⼊,我还傻乎乎地⽤python直接运⾏它。之后看到他给的样例运⾏命令是带了-m参数的。现在才恍然⼤悟。)
理解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) # 括号换⾏(推荐)
说到这感觉import的核⼼已经说完了。再跟着上⾯的博客说⼀说使⽤import可能碰到的问题吧。
问题1描述:ValueError: attempted relative import beyond top-level package。直⾯问题的第⼀步是去了解熟悉它,最好是能复现它,将错
误暴露在阳光之下。仍然是上⾯四个⽂件,稍作修改,四个⽂件如下:
# m1.py
from Branch import m3
m3.printSelf()
# m2.py
def printSelf():
print('module2')
# m3.py
from .. import m2 # 复现的关键在这 #
print(__name__)
def printSelf():
print('In m3')
# m4.py
def printSelf():
print('In m4')
运⾏python m1.py,就会出现该问题。问题何在?我猜测,运⾏m1.py后,m1代表的模块就是顶层模块(参见上⾯PEP 328的引⽤),⽽
m3.py中尝试导⼊的m2模块所在的包(即Tree⽬录代表的包)⽐m1的层级更⾼,所以会报出这样的错误。怎么解决呢?将m1.py的所有导⼊改为相对导⼊,然后进⼊m1.py的上层⽬录,运⾏python -m Tree.m1即可。