作为使用Python的开发者,我们一开始学习的内容之一就是如何导入Python的各种模块或库。但是我们注意到,那些经常使用Python的用户并不一定都知道Python的导入机制其实非常灵活。在本文中,我们将介绍以下话题:

常规导入(regular imports)

使用from语句导入

相对导入(relative imports)

可选导入(optional imports)

本地导入(local imports)

导入注意事项

常规导入

常规导入应该是最常使用的导入方式,比如:

import sys

我们只需要使用import语句,然后指定我们希望导入的模块或包即可。

通过这种方式导入的好处是可以一次性导入多个包或模块,例如:

import os, sys, time

虽然这节省了空间,但是却违背了Python风格指南。Python风格指南建议将每个导入语句单独成行。

有时在导入模块时,你想要重命名这个模块。这个功能很容易实现:

import numpy as np

然后我们可以调用这个库:

a = np.array([1,2,3])

上面的代码将我们导入的Numpy库重命名为np。我们可以按照和以前一样的方式调用模块的方法,但是可以用一个新的模块名。也有某些子模块必须要使用点标记法才能导入。

比如:

import urllib.error

这个情况虽不常见,但是我们也可以对其有所了解。

使用from语句导入

很多时候我们只想要导入一个模块或库中的某个部分。我们可以这样在Python中实现:

from functools import lru_cache

上面这行代码可以让我们直接调用lru_cache。如果我们按常规方式导入functools,那么我们就必须像这样调用lru_cache:

functools.lru_cache(*args)

根据我们实际的使用场景,上面的做法可能是更好的。在复杂的代码库中,能够看出某个函数是从哪里导入的这点是很有用的。不过,如果我们的代码维护得很好,模块化程度够高,那么只从某个模块中导入一部分内容也是非常方便和简洁的。

当然,我们还可以使用from方法导入模块的全部内容,就像这样:

from copy import *

这种做法在少数情况下是挺方便的,但是这样也可能打乱我们的命名空间。问题在于,我们可能定义了一个与导入模块中名称相同的变量或函数,这时如果我们试图使用copy模块中的同名变量或函数,实际使用的可能是我们自己定义的内容。因此,我们最后可能会碰到一个难以察觉的bug。了解了这个,我们遇到与命名空间有关的bug时也可以先从检查导入的模块入手。

如果我们正好要写自己的模块或包,有人会建议开发者在__init__.py文件中导入所有内容,让模块或者包使用起来更方便。当然很多人更喜欢显式地导入,而非隐式地导入。

我们也可以采取折中方案,从一个包中导入多个项:

from os import path, walk, unlink

from os import uname, remove

在上述代码中,我们从os模块中导入了5个函数。读者可能注意到了,我们是通过多次从同一个模块中导入实现的。当然,如果我们愿意的话,我们也可以使用圆括号一次性导入多个项:

from os import (path, walk, unlink, uname,

remove, rename)

这是一个有用的技巧,而且我们还可以换一种方式:

from os import path, walk, unlink, uname, \

remove, rename

上面的反斜杠是Python中的续行符,告诉解释器这行代码延续至下一行。

相对导入

PEP 328(Python增强建议书)介绍了引入相对导入的原因,以及选择了哪种语法。具体来说,是使用句点来决定如何相对导入其他包或模块。这么做的原因是为了避免偶然情况下导入标准库中的模块产生冲突。这里我们以PEP 328中给出的文件夹结构为例,看看相对导入是如何工作的:

my_package/

__init__.py

subpackage1/

__init__.py

module_x.py

module_y.py

subpackage2/

__init__.py

module_z.py

module_a.py

在本地磁盘上找个地方创建上述文件和文件夹。在顶层的__init__.py文件中,输入以下代码:

from . import subpackage1

from . import subpackage2

接下来进入subpackage1文件夹,编辑其中的__init__.py文件,输入以下代码:

from . import module_x

from . import module_y

现在编辑module_x.py文件,输入以下代码:

from .module_y import spam as ham

def main():

ham()

最后编辑module_y.py文件,输入以下代码:

def spam():

print('spam ' * 3)

打开终端,cd至my_package包所在的文件夹,但不要进入my_package。在这个文件夹下运行Python解释器:

相对导入适用于我们最终要放入包中的代码。如果我们编写了很多相关性强的代码,那么应该采用这种导入方式。用户也许会发现PyPI上有很多流行的包也是采用了相对导入。还要注意一点,如果我们想要跨越多个文件层级进行导入,只需要使用多个句点即可。

不过,PEP 328建议相对导入的层级不要超过两层。

还要注意一点,如果我们往module_x.py文件中添加了if __name__ == ‘__main__’,然后试图运行这个文件,我们会碰到一个很难理解的错误。我们可以编辑一下文件试试看:

from . module_y import spam as ham

def main():

ham()

if __name__ == '__main__':

# This won't work!

main()

现在从终端进入subpackage1文件夹,执行以下命令:

python module_x.py

如果我们使用的是Python 2,你应该会看到下面的错误信息:

Traceback (most recent call last):

File "module_x.py", line 1, in

from . module_y import spam as ham

ValueError: Attempted relative import in non-package

如果我们使用的是Python 3,错误信息大概是这样的:

Traceback (most recent call last):

File "module_x.py", line 1, in

from . module_y import spam as ham

SystemError: Parent module '' not loaded, cannot perform relative import

这指的是,module_x.py是某个包中的一个模块,而我们试图以脚本模式执行,但是这种模式不支持相对导入。

如果我们想在自己的代码中使用这个模块,那么我们必须将其添加至Python的导入检索路径(import search path)。最简单的做法如下:

import sys

sys.path.append('/path/to/folder/containing/my_package')

import my_package

注意,我们需要添加的是my_package的上一层文件夹路径,而不是my_package本身。原因是my_package就是我们想要使用的包,所以如果我们添加它的路径,那么将无法使用这个包。

我们接下来谈谈可选导入。

可选导入(Optional imports)

如果我们希望优先使用某个模块或包,但是同时也想在没有这个模块或包的情况下有备选,我们就可以使用可选导入这种方式。这样做可以导入支持某个软件的多种版本或者实现性能提升。以github2包中的代码为例:

lxml包也有使用可选导入方式:

try:

from urlparse import urljoin

from urllib2 import urlopen

except ImportError:

# Python 3

from urllib.parse import urljoin

from urllib.request import urlopen

正如以上示例所示,可选导入的使用很常见,是一个值得掌握的技巧。

局部导入

当我们在局部作用域中导入模块时,我们执行的就是局部导入。如果我们在Python脚本文件的顶部导入一个模块,那么我们就是在将该模块导入至全局作用域,这意味着之后的任何函数或方法都可能访问该模块。例如:

import sys # global scope 全局导入

def square_root(a):

# This import is into the square_root functions local scope

#这是一个局部导入

import math

return math.sqrt(a)

def my_pow(base_num, power):

return math.pow(base_num, power)

if __name__ == '__main__':

print(square_root(49))

print(my_pow(2, 3))

这里,我们将sys模块导入至全局作用域,但我们并没有使用这个模块。然后,在square_root函数中,我们将math模块导入至该函数的局部作用域,这意味着math模块只能在square_root函数内部使用。如果我们试图在my_pow函数中使用math,会引发NameError。试着执行这个脚本,看看会发生什么。

使用局部作用域的好处之一,是我们使用的模块可能需要很长时间才能导入,如果是这样的话,将其放在某个不经常调用的函数中或许更加合理,而不是直接在全局作用域中导入。不过很多用户几乎从没有使用过局部导入,主要是因为如果模块内部到处都有导入语句,会很难分辨出这样做的原因和用途。根据约定,所有的导入语句都应该位于模块的顶部。

导入注意事项

在导入模块方面,有几个程序员常犯的错误。这里我们介绍两个。

循环导入(circular imports)

覆盖导入(Shadowed imports)

先来看看循环导入。

循环导入

如果我们创建两个模块,二者相互导入对方,那么就会出现循环导入。例如:

# a.py

import b

def a_test():

print("in a_test")

b.b_test()

a_test()

然后在同个文件夹中创建另一个模块,将其命名为b.py。

import a

def b_test():

print('In test_b"')

a.a_test()

b_test()

如果我们运行任意一个模块,都会引发AttributeError。这是因为这两个模块都在试图导入对方。简单来说,模块a想要导入模块b,但是因为模块b也在试图导入模块a(这时正在执行),模块a将无法完成模块b的导入。我看过一些试图解决这个问题的方法,但是一般来说,我们应该做的是重构代码,避免发生这种情况。

覆盖导入

当我们创建的模块与标准库中的模块同名时,如果我们导入这个模块,就会出现覆盖导入。举个例子,创建一个名叫math.py的文件,在其中写入如下代码:

import math

def square_root(number):

return math.sqrt(number)

square_root(72)

现在打开编辑器,试着运行这个文件,我们会得到以下报错信息:

Traceback (most recent call last):

File "math.py", line 1, in

import math

File "/Users/Desktop/math.py", line 6, in

square_root(72)

File "/Users/Desktop/math.py", line 4, in square_root

return math.sqrt(number)

AttributeError: module 'math' has no attribute 'sqrt'

这到底是怎么回事呢?其实,我们运行这个脚本的时候,Python解释器首先在当前运行脚本所处的的文件夹中查找名叫math的模块。在这个例子中,解释器找到了我们正在执行的模块,试图导入它。但是我们的模块中并没有叫sqrt的函数或属性,所以就抛出了AttributeError。

总结

在本文中,我们讲了很多有关导入的内容,但是还有部分内容没有涉及。PEP 302中介绍了导入钩子(import hooks),支持实现一些非常方便的导入功能,比如说直接从github导入。Python标准库中还有一个importlib模块,值得查看学习。当然,我们还可以多看看别人写的代码,不断挖掘更多好用的写法。

— — — — — — E N D — — — — — —

真格量化可访问:

真格量化微信公众号,长按关注:

遇到了技术问题?欢迎加入真格量化Python技术交流QQ群 726895887

往期文章:

python中导入模块用什么命令_Python导入模块的技巧相关推荐

  1. python获取命令行参数_【整理】Python中如何获得并处理命令行参数

    运行Python脚本时,时常需要从命令行中传递一些参数到Python程序中,但是如何获得相应的传递进来的参数,以及如何解析这些参数,是很多人,包括最开始的我,所遇到的问题. 下面,就对此总结一下: 先 ...

  2. Python中的文本处理(一)str 模块完全解析

    前言 字符串处理是编程中常用到的操作,本系列总结的目标是通过系统的介绍不同的方法来完成不同复杂度的字符串处理操作. 旨在方便大家遇到不同的需求时,可以快速找到合适的处理方式,从而使代码开发快速,简洁, ...

  3. python中文件的打开与关闭_python中的文件打开与关闭操作命令介绍

    python中的文件打开与关闭操作命令介绍 1.文件打开与关闭 在python,使用open函数,可以打开一个已经存在的文件,或者创建一个新文件 open(文件名,访问模式). f = open('t ...

  4. python json模块有什么用_python json模块如何使用

    JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式.JSON的数据格式其实就是python里面的字典格式,里面可以包含方括号括起来的数组,也 ...

  5. python中sys模块有什么用_Python sys模块用法详解

    sys 模块代表了 Python 解释器,主要用于获取和 Python 解释器相关的信息. 在 Python 的交互式解释器中先导入 sys 模块,然后输入 [e for e in dir(sys) ...

  6. python导入自己写的函数_Python导入模块的几种方法

    Python 模块 Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句. 模块让你能够有逻辑地组织你的 Python 代 ...

  7. python中的ls是什么意思_Python学习之旅:使用Python实现Linux中的ls命令

    一.写在前面 在 Linux 中 ls 是一个使用频率非常高的命令了,可选的参数也有很多, 算是一条不得不掌握的命令.Python 作为一门简单易学的语言,被很多人认为是不需要认真学的,或者只是随便调 ...

  8. python中def main是什么意思_Python中’__main__’模块的作用

    Python不同于C/C++,程序执行并不需要主程序,如main(),而是文件自上而下的执行. 但很多Python程序中都有 1 if __name__ == '__main__':2 stateme ...

  9. python中heapq的库是什么_Python中heapq模块的用法

    heapq 模块提供了堆算法.heapq是一种子节点和父节点排序的树形数据结构.这个模块提供heap[k] <= heap[2*k+1] and heap[k] <= heap[2*k+2 ...

最新文章

  1. VMware安装Win7虚拟机
  2. dnslog 在 sql注入中的应用
  3. 张新波 | 十面埋伏 - 论大数据风控技术体系的构建
  4. mysql 查询有 float 类型的字段查不到
  5. codeblocks主题修改(vim)
  6. 在spark-shell中编写wordcont
  7. MemCache详细解读(转)
  8. 2021-08-09
  9. 图片打散存储JAVA_通过java的i/o机制进行图片流的存储以及对网络图片的存储
  10. Java代码生成器——基于模板快速生成web项目结构
  11. php字符串指定长度截取,php 截取指定长度字符串的代码分享
  12. C# 操作word之在表格中插入新行、删除指定行
  13. 老式计算机如何设置u盘启动,有谁清楚老式电脑怎么用u盘启动
  14. Scala入门小纸条(3)
  15. Git创建版本库及git init 、add 和 commit -m 的基本使用
  16. ❤️❤️马上安排!闺女想在游戏里成为【超人】,Python游戏开发模块Pygame系列之【跳跃的小球】❤️❤️源码
  17. Google Chrome 中的高性能网络
  18. 香港中文大学推荐的书单~
  19. 论文笔记2:Deep Attention Recurrent Q-Network
  20. 数据库系统概念 引言(一)

热门文章

  1. SingleR包注释单细胞数据
  2. R语言_高级数据管理
  3. Linux课2021
  4. 最小二乘法MSE 梯度下降法
  5. minimap2 长reads比对工具
  6. 右键点“工作空间”窗口内空白部分,在弹出的菜单上勾选“Docking View / 停靠式”。然后双击程序窗口的窗棱,就是最上面那条蓝色边框
  7. 树莓派开发4-串口通讯wiringpi库
  8. ajax请求json和xml数据及对json和xml格式数据的解析
  9. 黯然微信小程序杂记(三):微信小程序实现倒计时功能 附讲解教学 附源码
  10. Tensorflow 源码安装成功,导入报错 ImportError: cannot import name 'build_info'