- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议19:有节制地使用 from...import 语句
Python提供了3种方式来引入外部模块:import语句、from...import...及__import__函数。其中较为常见的为前面两种,而__import__函数与import语句类似,不同点在于前者显式地将模块的名称作为字符串传递并赋值给命名空间的变量。
在使用import的时候注意以下几点:
一般情况下尽量优先使用import a形式,如访问B时需要使用a.B的形式。
有节制地使用from a import B形式,可以直接访问B。
尽量避免使用from a import *,因为这会污染命名空间,并且无法清晰地表示导入了哪些对象。
为什么在使用import的时候要注意以上几点呢?在回答这个问题之前先来简单了解一下Python的import机制。Python在初始化运行环境的时候会预先加载一批内建模块到内存中,这些模块相关的信息被存放在sys.modules中。读者导入sys模块后在Python解释器中输入sys.modules.items()便可显示所有预加载模块的相关信息。当加载一个模块的时候,解释器实际上要完成以下动作:
1)在sys.modules中进行搜索看看该模块是否已经存在,如果存在,则将其导入到当前局部命名空间,加载结束。
2)如果在sys.modules中找不到对应模块的名称,则为需要导入的模块创建一个字典对象,并将该对象信息插入sys.modules中。
3)加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译。
4)执行动态加载,在当前模块的命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中。
我们以用户自定义的模块为例来看看sys.modules和当前局部命名空间发生的变化。在Python的安装目录下创建一个简单的模块testmodule.py:
a = 1 b = 'a' print "testing module import"
我们知道用户模块未加载之前,sys.modules中并不存在相关信息。那么进行import testmodule操作会发生什么情况呢?
>>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 'sys'] >>> import testmodule testing module import >>> dir() ①import testmodule 之后局部命名空间发生变化 ['__builtins__', '__doc__', '__name__', '__package__', 'sys', 'testmodule' ] >>> 'testmodule' in sys.modules.keys() True >>> id(testmodule) 35776304 >>> id(sys.modules['testmodule']) 35776304 >>> dir(testmodule) ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b'] >>> sys.modules['testmodule'].__dict__.keys() ['a', 'b', '__builtins__', '__file__', '__package__', '__name__', '__doc__',]
从输出结果可以看出,对于用户定义的模块,import机制会创建一个新的module将其加入当前的局部命名空间中,与此同时,sys.modules也加入了该模块的相关信息。但从它们的id输出结果可以看出,本质上是引用同一个对象。同时会发现testmodule.py所在的目录下多了一个.pyc的文件,该文件为解释器生成的模块相对应的字节码,从import之后的输出“testing module import”可以看出模块同时被执行,而a和b被写入testmodule所对应的字典信息中。
需要注意的是,直接使用import和使用from a import B形式这两者之间存在一定的差异,后者直接将B暴露于当前局部空间,而将a加载到sys.modules集合。
>>> import sys >>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 'sys'] >>> from testmodule import a testing module import >>> dir() ①使用from......import...... 之后命名空间发生的变化 ['__builtins__', '__doc__', '__name__', '__package__', 'a', 'sys'] >>> sys.modules['testmodule'] <module 'testmodule' from 'testmodule.pyc'> >>> id(sys.modules['testmodule']) 36562576 >>> id(a) 31697400 >>> id(sys.modules['a']) Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'a'
了解完import机制,我们再来看看对于from a import ...无节制的使用会带来什么问题。
(1)命名空间的冲突
来看一个例子。假设有如下3个文件:a.py,b.py及importtest.py,其中a和b都定义了add()函数,当在import test文件中同时采用from...import...的形式导入add的时候,import test中起作用的到底是哪一个函数呢?
文件a.py如下:
def add(): print "add in module A"
文件b.py如下:
def add(): print "math in module B"
文件importtest.py如下:
from a import add from b import add if __name__ == '__main__': math()
从程序的输出“add in module B”可以看出实际起作用的是最近导入的add(),它完全覆盖了当前命名空间之前从a中导入的add()。在项目中,特别是大型项目中频繁地使用from a import ...的形式会增加命名空间冲突的概率从而导致出现无法预料的问题。因此需要有节制地使用from...import语句。一般来说在非常明确不会造成命名冲突的前提下,以下几种情况下可以考虑使用from...import语句:
1)当只需要导入部分属性或方法时。
2)模块中的这些属性和方法访问频率较高导致使用“模块名.名称”的形式进行访问过于烦琐时。
3)模块的文档明确说明需要使用from...import形式,导入的是一个包下面的子模块,且使用from...import形式能够更为简单和便利时。如使用from io.drivers import zip要比使用import io.drivers.zip更方便。
(2)循环嵌套导入的问题
先来看下面的例子:
c1.py: from c2 import g def x(): Pass c2.py: from c1 import x def g(): Pass
无论运行上面哪一个文件都会抛出ImportError异常。这是因为在执行c1.py的加载过程中,需要创建新的模块对象c1然后执行c1.py所对应的字节码。此时遇到语句from c2 import g,而c2在sys.modules也不存在,故此时创建与c2对应的模块对象并执行c2.py所对应的字节码。当遇到c2中的语句from c1 import x时,由于c1已经存在,于是便去其对应的字典中查找g,但c1模块对象虽然创建但初始化的过程并未完成,因此其对应的字典中并不存在g对象,此时便抛出ImportError: cannot import name g异常。而解决循环嵌套导入问题的一个方法是直接使用import语句。读者可以自行验证。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论