- 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章 性能剖析与优化
建议20:优先使用 absolute import 来导入模块
假设有如下文件结构,其中app/sub1/string.py中定义了一个lower()方法,那么当在mod1.py中import string之后再使用string.lower()方法时,到底引用的是sub1/string.py中的lower()方法,还是Python标准库中string里面的lower()方法呢?
app/ __init__.py sub1/ __init__.py mod1.py string.py sub2/ __init__.py mod2.py
从程序的输出会发现,它引用的是app/sub1/string.py中的lower()方法。显然解释器默认先从当前目录下搜索对应的模块,当搜到string.py的时候便停止搜索进行动态加载。那么,如果要使用Python自带的string模块中的方法,该怎么实现呢?这就涉及absolute import和relative import相关的话题了。
在Python2.4以前默认为隐式的relative import,局部范围的模块将覆盖同名的全局范围的模块。如果要使用标注库中同名的模块,你不得不去深入考察sys.modules一番,显然这并不是一种非常友好的做法。Python2.5中后虽然默认的仍然是relative import,但它为absolute import提供了一种新的机制,在模块中使用from __future__ import absolute_import 语句进行说明后再进行导入。同时它还通过点号提供了一种显式进行relative import的方法,“.”表示当前目录,“..”表示当前目录的上一层目录。例如想在mod1.py中导入string.py,可以使用from . import string,其中mod1所在的包层次结构为app.sub1.mod1,“.”表示app.sub1;如果想导入sub2/mo2.py可以使用from ..sub2 import mod2,“..”代表的是app。
但事情是不是就此结束了呢?远不止,使用显式relative import之后再运行程序一不小心你就有可能遇到这种错误“ValueError: Attempted relative import in non-package”。这是什么原因呢?这个问题产生的原因在于relative import使用模块的__name__属性来决定当前模块在包层次结构中的位置,如果当前的模块名称中不包含任何包的信息,那么它将默认为模块在包的顶层位置,而不管模块在文件系统中的实际位置。而在relative import的情形下,__name__会随着文件加载方式的不同而发生改变,上例中如在目录app/sub1/下运行Python mod1.py,会发现模块的__name__为__main__,但如果在目录app/sub1/下运行Python-m mod1.py,会发现__name__变为mod1。其中-m的作用是使得一个模块像脚本一样运行。而无论以何种方式加载,当在包的内部运行脚本的时候,包相关的结构信息都会丢失,默认当前脚本所在的位置为模块在包中的顶层位置,因此便会抛出异常。如果确实需要将模块当作脚本一样运行,解决方法之一是在包的顶层目录中加入参数-m运行该脚本,上例中如果要运行脚本mod1.py可以在app所在的目录的位置输入Python -m app.sub1.mod1。另一个解决这个问题的方法是利用Python2.6在模块中引入的__package__属性,设置__package__之后,解释器会根据__package__和__name__的值来确定包的层次结构。上面的例子中如果将mod1.py修改为以下形式便不会出现在包结构内运行模块对应的脚本时出错的情况了。
if __name__ == "__main__" and __package__ is None: import sys import os.path sys.path[0] = os.path.abspath("./../../") print sys.path[0] import app.sub1 __package__ = str('app.sub1') from . import string
相比于absolute import,relative import在实际应用中反馈的问题较多,因此推荐优先使用absolute import。absolute import可读性和出现问题后的可跟踪性都更好。当项目的包层次结构较为复杂的时候,显式relative import也是可以接受的,由于命名冲突的原因以及语义模糊等原因,不推荐使用隐式的relative import,并且它在Python3中已经被移除。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论