Python中的import 到底在干啥?

一直好奇import ***这个语句到底是在干什么,有时候在PyCharm中运行好好的程序,跑道python原生环境中就会报错
在这里插入图片描述
例如像下面这样的一个项目结构:

Projetc_example
|-- A
   |-- alpha.py
   |-- beta.py
|-- B
    |-- theta.py
|-- main
    |-- main.py

假设要在main.py中导入theta.py:

# main/main.py
from B import theta

在原生环境中就会出现意想不到的问题,python不知道这个包在哪(包也是一种特殊的模块):

Traceback (most recent call last):
  File "main/main.py", line 1, in <module>
    from B import theta
ModuleNotFoundError: No module named 'B'

可是这就奇了怪了,为啥同样的代码,在PyCharm里运行就是好的了呢?

原来在Python中,import语句实际上封装了一系列过程。

import的查找路径

  1. 查找是否已导入同名模块
    首先,Python会按照import xxx中指定的包名,到sys.modules中查找当前环境中是否已经存在相应的包——不要奇怪为什么都没有导入sys这个模块就有sys.modules了。

sys是Python内置模块,也就是亲儿子,导入只是意思一下,让我们这样的外人在导入的环境中也可以使用相关接口而已,实际上相应的数据对Python而言从始至终都是透明的。

我们可以导入sys查看一下这个对象的具体内容(节省篇幅,做省略处理):

>>> import sys
>>> sys.modules
{'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>, ...'re': <module 're' from 'E:\\Anaconda\\Anaconda\\lib\\re.py'>, ...}

这些就都是Python一开始就已经加载好的模块,也就是安装好Python之后,只要一运行环境中就已经就绪的模块——只是作为外人的我们还不能直接拿过来用,得跟Python声明一下。

很容易可以发现,sys.modules 中列出来的已加载模块中存在明显的不同,前面的很多模块显得很干净,而后面的很多模块都带有from yyy’的字样,并且这个yyy看起来还像是一个路径。
这就关系到我们接下来要讲的步骤了。

  1. 在特定路径下查找对应模块
    前面我们讲到了,当我们导入某个模块时,Python先会去查询sys.modules,看其中是否存在同名模块,查到了那当然皆大欢喜,Python直接把这个模块给我们用就好了,但如果没找到呢?

这显然是一个很现实的问题。毕竟资源是有限的,Python不可能把你可能用到的所有模块全都一股脑给加载起来,这谁也顶不住啊

于是python就等你需要的时候,在去加载。

实际上,在Python中,sys.path维护的就是这样一个操作,当python遇到不认识的包时,就会去实地查找的路径。

打印出来具体内容就是:

>>> sys.path
['', 'E:\\Anaconda\\Anaconda\\python37.zip', 'E:\\Anaconda\\Anaconda\\DLLs', 'E:\\Anaconda\\Anaconda\\lib', 'E:\\Anaconda\\Anaconda',
 'E:\\Anaconda\\Anaconda\\lib\\site-packages']

大体上就是安装环境时配置的一些包所在路径,其中第一个元素代表当前所执行脚本所在的路径。

也正是因此,我们可以在同一个目录下,大大方方地调用其他模块。

  1. 将模块与名字绑定
    找到相应的非亲生模块还没完,加载了包还得为它分配一个指定的名字,我们才能在脚本中使用这个模块。
    当然多数时候我们感知不到这个过程,因为我们就是一个import走天下:
import sys
import os
import requests

这个时候我们指定的模块名,实际上也是指定的稍后用来调用相应模块的对象名称。
换个更明显的:

import requests as req

如果这个时候只使用了第二种方式来导入requests这个模块,那么很显然在之后的程序流程中,我们都不能使用requests这个名字来调用它而应当使用req。

这就是Python导入过程中的名称绑定,本质上与正常的赋值没有太大区别,加载好了一个对象之后,然后为这个对象赋一个指定的变量名。

当然即使是已经加载好的模块,我们也可以利用这个名称绑定的机制为它们取别名,比如:

>>> import sys
>>> import sys as sy
>>> sys.path
['', 'E:\\Anaconda\\Anaconda\\python37.zip', 'E:\\Anaconda\\Anaconda\\DLLs', 'E:\\Anaconda\\Anaconda\\lib', 'E:\\Anaconda\\Anaconda', 'E:\\Anaconda\\Anaconda\\lib\\site-packages']
>>> sy.path
['', 'E:\\Anaconda\\Anaconda\\python37.zip', 'E:\\Anaconda\\Anaconda\\DLLs', 'E:\\Anaconda\\Anaconda\\lib', 'E:\\Anaconda\\Anaconda', 'E:\\Anaconda\\Anaconda\\lib\\site-packages']
>>> sys == sy
True
  1. 问题解决
    好了,上面就是对Python导入机制的大致介绍,但是说了半天,我们的问题还没有解决:在项目中如何简洁地跨模块导入其他模块?
    在使用PyCharm的时候倒是一切顺遂,因为PyCharm会自动将项目的根目录加入到导入的搜索路径,也就是说像下面这样的项目结构,在任意模块中都可以很自然地通过import A导入模块A,用import B导入模块B。
Projetc_example
|-- A
   |-- alpha.py
   |-- beta.py
|-- B
    |-- theta.py
|-- main
    |-- main.py

但是在非IDE环境中呢?或者说就是原生的Python环境中呢?
很自然地我们就会想到:那就手动把项目根目录加入到sys.path中去嘛。说起来也跟PyCharm做的事没差呀
所以我们就通过sys和os两个模块

# Peoject_example/A/alpha.py
print("name: " + __name__)
print("file: " + __file__)

def al():
    print("Importing alpha succeeded.")

main.py中则加入一个逻辑,在sys.path中增加一个项目根目录:

import os
import sys

sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))

import A.alpha


A.alpha.al()

# name: A.alpha
# file: *\Project_example\A\alpha.py
# Importing alpha succeeded.

总结
本文借由一个易现问题引出对Python导入机制的介绍,实际上限于篇幅,导入机制只是做了一个概览,具体的内容还要更加复杂。本文讲到的这三步则适用于比较常见的情形,了解了这三步也足以应付很多问题了。

参考资料

https://docs.python.org/zh-cn/3/reference/import.html
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值