模块与包

在了解 import 之前,有两个概念必须提一下:

  • 模块: 一个 .py 文件就是一个模块(module)
  • 包: __init__.py 文件所在目录就是包(package)

当然,这只是极简版的概念。实际上包是一种特殊的模块,而任何定义了 __path__ 属性的模块都被当做包。只不过,咱们日常使用中并不需要知道这些。

两种形式的 import

import 有两种形式:

  • import ...
  • from ... import ...

两者有着很细微的区别,先看几行代码。

from string import ascii_lowercase
import string
import string.ascii_lowercase

运行后发现最后一行代码报错:ImportError: No module named ascii_lowercase,意思是:“找不到叫 ascii_lowercase 的模块”。第 1 行和第 3 行的区别只在于有没有 from,翻翻语法定义发现有这样的规则:

  • import ... 后面只能是模块或包
  • from ... import ... 中,from 后面只能是模块或包,import 后面可以是任何变量

可以简单的记成:第一个空只能填模块或包,第二个空填啥都行。

import 的搜索路径

提问,下面这几行代码的输出结果是多少?

import string
print(string.ascii_lowercase)

是小写字母吗?那可不一定,如果目录树是这样的:

./
├── foo.py
└── string.py

foo.py 所在目录有叫 string.py 的文件,结果就不确定了。因为你不知道 import string 到底是 import 了 ./string.py 还是标准库的 string。为了回答这个问题,我们得了解一下 import 是怎么找到模块的,这个过程比较简单,只有两个步骤:

  1. 搜索「内置模块」(built-in module)
  2. 搜索 sys.path 中的路径

而 sys.path 在初始化时,又会按照顺序添加以下路径:

  1. foo.py 所在目录(如果是软链接,那么是真正的 foo.py 所在目录)或当前目录
  2. 环境变量 PYTHONPATH中列出的目录(类似环境变量 PATH,由用户定义,默认为空);
  3. site 模块被 import 时添加的路径1site 会在运行时被自动 import)。

import site 所添加的路径一般是 XXX/site-packages(Ubuntu 上是 XXX/dist-packages),比如在我的机器上是 /usr/local/lib/python2.7/site-packages。同时,通过 pip 安装的包也是保存在这个目录下的。如果懒得记 sys.path的初始化过程,可以简单的认为 import 的查找顺序是:

  1. 内置模块
  2. .py 文件所在目录
  3. pip 或 easy_install 安装的包

相对 import 与 绝对 import

相对 import

当项目规模变大,代码复杂度上升的时候,我们通常会把一个一个的 .py 文件组织成一个包,让项目结构更加清晰。这时候 import 又会出现一些问题,比如:一个典型包的目录结构是这样的:

 string/
├── __init__.py
├── find.py
└── foo.py

如果 string/foo.py 的代码如下:

# string/foo.py
from string import find
print(find)

那么 python string/foo.py 的运行结果会是下面的哪一个呢?

  • <module 'string.find' from 'string/find.py'>
  • <function find at 0x123456789>

按我们前面讲的各种规则来推导,因为 foo.py 所在目录 string/ 没有 string 模块(即 string.py),所以 import 的是标准库的 string,答案是后者。不过,如果你把 foo 当成 string 包中的模块运行,即 python -m string.foo,会发现运行结果是前者。同样的语句,却有着两种不同的语义,这无疑加重了咱们的心智负担,总不能每次咱们调试包里的模块时,都去检查一下执行的命令是 python string/foo.py 还是 python -m string.foo 吧?

相对 import 就是专为解决「包内导入」(intra-package import)而出现的。它的使用也很简单,from 的后面跟个 . 就行:

# from string/ import find.py
from . import find
# from string/find.py import *
from .find import *

我们再看个复杂点的例子,有个包的目录结构长这样:

one/
├── __init__.py
├── foo.py
└── two/
├── __init__.py
├── bar.py
└── three/
├── __init__.py
├── dull.py
└── run.py
from . import dull
from .. import bar
from ... import foo
print('Go, go, go!')

改成

from .dull import *
from ..bar import *
from ...foo import *
print('Go, go, go!')

结果是一样的。

那么 python string/foo.py 和 python -m string.foo 的运行结果又是怎样呢?运行一下发现,两者的输出分别是:

Traceback (most recent call last):
File "string/foo.py", line 1, in <module>
from . import find
ValueError: Attempted relative import in non-package
<module 'string.find' from 'string/find.py'>

原因在于 python string/foo.py 把 foo.py 当成一个单独的脚本来运行,认为 foo.py 不属于任何包,所以此时相对 import 就会报错。也就是说,无论命令行是怎么样的,运行时 import 的语义都统一了,不会再出现运行结果不一致的情况。

Python的import机制的更多相关文章

  1. python 的import机制2

    http://blog.csdn.net/sirodeng/article/details/17095591   python 的import机制,以备忘: python中,每个py文件被称之为模块, ...

  2. 关于Python的import机制原理

    很多人用过python,不假思索地在脚本前面加上import module_name,但是关于import的原理和机制,恐怕没有多少人真正的理解.本文整理了Python的import机制,一方面自己总 ...

  3. 深入探讨 Python 的 import 机制:实现远程导入模块

        深入探讨 Python 的 import 机制:实现远程导入模块 所谓的模块导入( import ),是指在一个模块中使用另一个模块的代码的操作,它有利于代码的复用. 在 Python 中使用 ...

  4. 初窥 Python 的 import 机制

    本文适合有 Python 基础的小伙伴进阶学习 作者:pwwang 一.前言 本文基于开源项目: https://github.com/pwwang/python-import-system 补充扩展 ...

  5. python之import机制

    1. 标准 import        Python 中所有加载到内存的模块都放在 sys.modules .当 import 一个模块时首先会在这个列表中查找是否已经加载了此模块,如果加载了则只是将 ...

  6. Python中import机制

    Python语言中import的使用很简单,直接使用import module_name语句导入即可.这里我主要写一下"import"的本质. Python官方定义:Python ...

  7. [转] Python的import初探

    转载自:http://www.lingcc.com/2011/12/15/11902/#sec-1 日常使用python编程时,为了用某个代码模块,通常需要在代码中先import相应的module.那 ...

  8. Python中import的使用方法

    源文出处: "import"的本质参照: Python中import机制 python导入自定义模块和包

  9. python 的 from import 机制

    [A.py] from B import D class C:pass [B.py] from A import C class D:pass 为什么执行A的时候不能加载D呢? 如果将A.py改为:i ...

随机推荐

  1. Java8系列 (五) Optional类

    概述 在Java8之前, 如果需要对一个变量做一次 null 检查, 通常会像下面这样写 T t = service1.query(); if (t != null) { K k = service2 ...

  2. 第一篇 Flask初识

    一. Python 现阶段三大主流Web框架 Django Tornado Flask 对比 1.Django 主要特点是大而全,集成了很多组件,例如: Models Admin Form 等等, 不 ...

  3. Android原生PDF功能实现

    1.背景 近期,公司希望实现安卓原生端的PDF功能,要求:高效.实用. 经过两天的调研.编码,实现了一个简单Demo,如上图所示. 关于安卓原生端的PDF功能实现,技术点还是很多的,为了咱们安卓开发的 ...

  4. [Nginx]Ubuntu18.04 安装配置Nginx1.13.6+FastDFS

    一. 安装fastdfs依赖包 1. 解压缩libfastcommon-master.zip 2. 进入到libfastcommon-master的目录中 3. 执行 ./make.sh 4. 执行 ...

  5. python设置环境变量(临时和永久)

    设置临时环境变量 import os # 设置环境变量 os.environ['WORKON_HOME']="value" # 获取环境变量方法1 os.environ.get(' ...

  6. 5. SOFAJRaft源码分析— RheaKV中如何存放数据?

    概述 上一篇讲了RheaKV是如何进行初始化的,因为RheaKV主要是用来做KV存储的,RheaKV读写的是相当的复杂,一起写会篇幅太长,所以这一篇主要来讲一下RheaKV中如何存放数据. 我们这里使 ...

  7. CSPS模拟 71

    全程傻眼 T1 毛衣衬 meet_in_middle.. 不再使用二分查找,而是直接枚举对面状态,虽然底数爆炸但是指数减半,复杂度是对的. T2 猫儿嗔 逆序关系有支配关系? $DAG$树.. 把逆序 ...

  8. 带你上手一款下载超 10 万次的 IDEA 插件

    作者 | 倪超(银时) 阿里云开发者工具产品专家 本文整理自 11 月 7 日社群分享,每月 2 场高质量分享,点击加入社群. 导读:Cloud Toolkit 是本地 IDE 插件,帮助开发者更高效 ...

  9. 「Usaco2012 Dec」第一(字典树+拓扑排序)

    (我恨字符串) 惯例化简题目:给定n个字符串,可以改变字符的相对大小(在字典序中的大小),问:字符串i是否能成为最小的字符串(字典序) 解题过程: 首先你可以预处理出来26的全排列然后暴力然后你只要用 ...

  10. 如何把链表以k个结点为一组进行翻转

    [MT笔试题] 题目描述: K 链表翻转是指把每K个相邻的结点看成一组进行翻转,如果剩余结点不足 K 个,则保持不变.假设给定链表 1 -> 2 -> 3 -> 4 -> 5 ...