Java程序猿学Python之Import语句

Import语句可以说在Python中是无处不在,非常类似于Java中的import的语句,但是又比Java的强大很多,也复杂的多。

首先本文章会讲解import语句的作用,然后讲解一下import语句的内部执行原理以及import语句是按什么样的顺序寻找文件;再者讲解from语句与import语句的使用,动态加载,包导入(又分为相对导入和绝对导入),命名空间包等与import相关的话题。本文章主要是我读《Learning Python FIFTH EDITION》之后整理出来的,自己也能做一个总结和分享,欢迎各位踊跃提问。

Import语句的作用

作用跟java中的import语句基本一致,将其他文件中编写的模块导入到当前文件以供使用。这对于实际的系统开发非常有用,可以将系统按照相应的功能拆分到不同的文件中,也可以将通用的功能独立出来以供复用。

>>> import os
>>> os.getcwd()
'D:\\Program\\Python35'
>>> 

Import语句内部执行机制

import语句在第一次执行的时候主要执行如下几个步骤:
1. 找到模块文件;
文件的定位过程及优先顺序如下:
(1)程序的工作目录 (自动导入,不需要手动添加。)
(2)PYTHONPATH环境变量配置的文件路径;
(3)Python安装包里面的标准库目录; (自动导入,不需要手动添加。)
(4)以上路径中指定的.pth文件 (该文件每一行为路径,会根据每行配置的路径进行搜索。)
(5)第三方扩展包; (自动导入)
这些搜索路径可以通过如下命令查询:

>>> sys.path
['', 'D:\\Program\\Python35\\Lib\\idlelib', 'D:\\Program\\Python35\\python35.zip', 'D:\\Program\\Python35\\DLLs', 'D:\\Program\\Python35\\lib', 'D:\\Program\\Python35', 'F:\\workspace\\python_workspace', 'D:\\Program\\Python35\\lib\\site-packages']
>>> 

其中的空目录为程序运行的当前目录,该目录的优先级最高,从左到右顺序扫描目录。

2. 编译成字节码;
在IDLE中第一次导入的时候会自动编译成字节码文件pyc,并缓存到文件所处的__pycache__目录中(Python 3.X)。在正是的项目中一般都会全部编译好,所以不会再进行编译。

3. 运行模块代码;
首次导入会运行处于模块顶层的代码:
script1.py:

X = 1
print(X)

def F():
    print('function')
>>> import script1
1
>>> 

注意,当第二次执行相同模块的导入的时候不会再执行这么多步骤,而是直接从缓存中获取模块对象而已。

From语句

from module1 import printer
从module1模块中只导入printer

from module1 import printer, pxd
从模块module1中导入printer和pxd
module1.py :

pxd = 10

def printer(X):
    print(X)
>>> from module1 import pxd, printer
>>> pxd
10
>>> printer(2)
2
>>> 

from module1 import * (只能使用在模块的顶级作用域,也就是模块的最外面。)
这样可以直接使用printer属性,而不需要模块名称。
例如:

>>> from script1 import *
>>> def a():
    from script1 import *

SyntaxError: import * only allowed at module level
>>> 

该语法只能在模块的最外层执行,而不能在方法内。谨慎使用*,因为这样会覆盖本地作用域同名的属性。

from M import func as mfunc
可以将导入的属性进行重命名,当有命名冲突的时候as语句非常好用。

注意点:当使用from导入进来的属性进行修改时,不可变对象的修改不会影响到模块中的属性。

module1.py

pxd = 10

def printer():
    print(pxd)
>>> import module1
>>> from module1 import pxd, printer
>>> pxd
10
>>> pxd = 11
>>> printer()
10
>>> module1.pxd
10
>>> 

单独的修改import进来的pxd并没有影响到module1模块中的属性,因为pxd是不可变对象,import进来只是复制了一份值,但是如果是可变对象则是引用,这时修改就会影响到module1模块中的属性:

module1.py :

pxd = 10
L = [1,2,4]

def printer():
    print(pxd)
>>> from module1 import L
>>> import module1
>>> L
[1, 2, 4]
>>> L.append(99)
>>> L
[1, 2, 4, 99]
>>> module1.L
[1, 2, 4, 99]
>>> 

因此,from语句的导入可以看成这样:

from module import name1, name2     
等价于:
import module               # Fetch the module object
name1 = module.name1        # Copy names out by assignment
name2 = module.name2
del module                  # Get rid of the module name

包导入

包导入就是按照文件夹的组织架构导入模块,Java中的import就是按照这种方式导入模块的。
首先来看一个例子,有一个模块文件的目录结构为:/dir0/dir1/dir2/mod.py,则代码:
import dir1.dir2.mod
导入mod模块

from dir1.dir2.mod import x
导入mod模块的x属性

这里的导入格式就是包导入,但是有如下要求:
1、根目录/dir0必须在python的搜索路径下,也就是sys.path必须存在该目录,但是sys.path中不能存在dir0目录的子目录(dir1, dir2等);
2、dir1和dir2必须要有__init__.py文件;
__init__.py文件就是一个普通的python程序文件,__init__.py文件用于在第一次import package的时候执行一些初始化动作,也相当于将目录声明成包目录。当导入了目录之后,__init__.py对象相当于导入的目录对象。

这里F:\workspace\python_workspace\处于搜索路径下。
F:\workspace\python_workspace\dir1\__init__.py

A = 1
print('dir1')

F:\workspace\python_workspace\dir1\dir2\__init__.py

B = 2
print('dir2')

F:\workspace\python_workspace\dir1\dir2\mod.py

C = 3
print('in mod.py')
>>> import dir1.dir2.mod
dir1
dir2
in mod.py
>>> dir1.A
1
>>> dir2.B
Traceback (most recent call last):
  File "<pyshell#18>", line 1, in <module>
    dir2.B
NameError: name 'dir2' is not defined
>>> dir1.dir2.B
2
>>> dir1.dir2.mod.C
3
>>> dir1
<module 'dir1' from 'F:\\workspace\\python_workspace\\dir1\\__init__.py'>
>>> 

通过执行这段代码可以看出,__init__.py代表了这个目录对象,包导入必须通过dir1才能访问到dir2 (dir1.dir2,当然也可以通过from语句直接访问:from dir1 import dir2),这个跟目录的结构一致。

通过以上可知,包导入有2个好处:

  1. 可以合理的规划模块,例如:org.spring.XX , org.mybatis.XX, org.hibernate.XX 等等,这样的话可以清楚的知道模块的用途,表述性更强;
  2. 可以避免模块命名冲突,如果没有包导入,在Python搜索路径如果存在同名模块将非常麻烦。

相对导入

在相对导入中通过使用.和..开头来指定当前目录和上一层目录,格式:
from . import m
from .. import n
要想使用相对导入,所在的py文件必须处在包路径里面,就是所在的文件夹下存在__init__.py文件,并且该目录的根目录必须处于Python的搜索路径下; 就相当如上一节介绍的/dir0/dir1/dir2目录结构,其中/dir0处于python的搜索路径下,而dir1和dir2不在python的搜索路径下,并且dir1和dir2都包含__init__.py文件;是否处于python的搜索路径可以通过sys.path查看。

F:\workspace\python_workspace\dir1\a.py

from .b import name         #包的相对导入:在当前目录下导入b模块的name属性
print('name:' + name)

F:\workspace\python_workspace\dir1\b.py

from . import a             #包的相对导入:在当前目录下导入a模块
print('b.')
name = 'b:pengxd'

其中路径F:\workspace\python_workspace\处于搜索路径下:

>>> import sys
>>> sys.path
['', 'D:\\Program\\Python35\\Lib\\idlelib', 'D:\\Program\\Python35\\python35.zip', 'D:\\Program\\Python35\\DLLs', 'D:\\Program\\Python35\\lib', 'D:\\Program\\Python35', 'F:\\workspace\\python_workspace', 'D:\\Program\\Python35\\lib\\site-packages']
>>> import dir1.a
b.
name:b:pengxd
>>> 

这里有一个问题需要注意,不能使用python a.py运行含有相对导入的模块,因为用python执行的时候当前目录会变成python的搜索目录,这样当前目录就不是包路径,无法使用包的相对导入。

PS F:\workspace\python_workspace\dir1> python .\a.py
Traceback (most recent call last):
  File ".\a.py", line 1, in <module>
    from .b import name
ImportError: attempted relative import with no known parent package

命名空间包 (Python 3.X)

在全局的搜索路径下存在多个相同名称的文件夹(不需要__init__.py文件),可以使用import语句将这些文件夹挂载到一个命名空间包下,例如,有2个文件:
F:\workspace\python_workspace\ns\dir1\sub\mod1.py
F:\workspace\python_workspace\ns\dir2\sub\mod2.py
其中F:\workspace\python_workspace\ns\dir1\ 和 F:\workspace\python_workspace\ns\dir2\ 处于全局的搜索路径下,当import sub的时候就会创建命名空间包,代码如下:
这里写图片描述

这样sub模块下面就存在2个子模块:mod1,mod2。此时,sub为命名空间包module ‘sub’ (namespace),它的__path__属性有所包含的路径。这样就可以通过sub模块访问到mod1和mod2模块了,这2个模块就像是在同一个目录下面一样。

注意:在命名空间包下可以使用相对导入,例如可以在mod1文件中使用语句: from . import mod2

命名空间包中不要包含__init__.py文件,这样会打乱命名空间包的搜索路径,导致命名空间包无法正常工作。例如:
F:\workspace\python_workspace\ns4\dir1\sub\mod1.py
F:\workspace\python_workspace\ns4\dir2\sub\mod2.py
如果dir2/sub/下存在一个__init__.py文件,那么在导入sub模块的时候会优先使用普通包,也就是dir2下的sub模块。
这里写图片描述
这个时候sub模块只是一个普通包(__init__.py)

import语句的封装

1、在使用import * 的时候默认不会导入已下划线开头的变量;
unders.py:

print('init...')
__a__ = 10
b = 20
>>> from unders import *
init...
>>> b
20
>>> __a__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '__a__' is not defined
>>>

这里可以看出并没有导入 __a__变量。

2、如果导入的模块中存在变量__all__,该变量为一个list列表,其中每一个元素为import * 默认导入的内容。
例如模块unders.py:

print('init...')
__a__ = 10
c = 30
b = 20      #该变量不在__all__列表中,因此不会被import * 自动导入
d = 40
__all__ = ['__a__', 'c', 'd']   #其中的变量会被import * 自动导入
>>> from unders import *
init...
>>> __a__
10
>>> c
30
>>> b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined
>>> d
40
>>>

使用import语句体验新功能

from __future__ import featurename
使用该语句可以把新版本的内容导入到当前版本中使用,因此使用__future__过渡到新版本会是一个很好的办法。
例如,在Python 2.X中使用Python 3.X的除法功能:

from __future__ import division

print '10 / 3 =', 10 / 3
print '10.0 / 3 =', 10.0 / 3
print '10 // 3 =', 10 // 3

模块属性:__name__ 和 __main__

如果最高级别的模块(也就是main函数模块),则属性__name__ = ‘__main__’, 类似于java中的main函数一样。
若果是导入的模块,则 __name__ 属性为import为模块名称。
这2个属性用于判断当前模块是否为main模块然后执行相应的特殊操作非常有用。

Import重命名

import modulename as name
from modulename import attrname as name
import dir1.dir2.mod as mod
可以使用as语句将模块重命名

动态导入

运行时动态的加载模块

>>> modname = 'string'
>>> string = __import__(modname)
>>> string
<module 'string' from 'C:\\Python33\\lib\\string.py'>

>>> import importlib
>>> modname = 'string'
>>> string = importlib.import_module(modname)   
>>> string
<module 'string' from 'C:\\Python33\\lib\\string.py'>

重新导入修改后的模块

我们知道,一个模块只能导入一次,如果再次导入则直接从缓存中直接获取,并不会重新导入模块。如果想在修改模块之后再次导入,可以使用reload方法:

>>> from imp  #方法1
>>> imp.reload(module)

>>> from imp import reload #方法2
>>> reload(module)

注意:
1、只有Python语言编写的模块才可以动态的加载,而第三方扩展模块,如用C编写的则不能动态加载。
2、使用reload之前必须先用import把相应的模块导入进行才能执行reload方法。
3、reload只影响import语句导入进来的模块,而不响应from语句导入的模块,因此如果想重新导入模块请不要使用from语句。

总结

Python的Import语句就介绍到这里,可以看出其复杂性还是很多的,要注意的点也非常多,不像Java中的import语句这么简单,但也丰富了Python的功能。在后续的学习过程中我也会总结这里经验,也会不断的完善和总结,争取能将最准确的信息传递给大家,也非常希望大家的反馈,大家可以一起学习交流。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shadon178

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值