学了半天,import 到底在干啥?

简介: Python凭什么就那么好用呢?毫无疑问,大量现成又好用的内置/第三方库功不可没。那我们是怎么使用它们的呢?噢,对了~是用的import xxx这个语句。之所以会有此一问,也是之前有一次使用PyCharm进行开发时(又)踩了个坑……

废话少说,先讲问题

像下面这样一个项目结构:

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不知道要到哪里去找这个名为B的模块(包是一种特殊的模块):

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

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

8.jpg


import的查找路径

于是我们不辞艰辛,上下求索,原来在Python中,import语句实际上封装了一系列过程。

1. 查找是否已导入同名模块

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

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


9.jpg

我们可以导入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看起来还像是一个路径。

这就关系到我们接下来要讲的步骤了。

2. 在特定路径下查找对应模块

前面我们讲到了,当我们导入某个模块时,Python先会去查询sys.modules,看其中是否存在同名模块,查到了那当然皆大欢喜,Python直接把这个模块给我们用就好了,毕竟儿子那么多,借出去赚点外快也是好事儿不是?

可问题在于:那要是没找到呢?

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

于是乎就有人给Python出了个主意:那你等到要用的时候,再去找他说他是你儿子呗

Python:妙哇~

10.jpg

有了这个思路,Python就指定了几家特定的酒楼,说:“凡是去消费的各位,都可以给我当儿子。”

就这样,一些本来不是Python亲儿子的人,出于各种原因聚集到了这几家酒楼,以雇佣兵的身份随时准备临时称为Python的儿子。

这可就比周文王开局就收100个义子优雅多了,养家糊口的压力也就没那么大了(Python:什么?我的亲儿子都不止100个?你说什么?听不见啊——


回到正经的画风来——

实际上,在Python中,sys.path维护的就是这样一个py交易的结果~~(诶?好像莫名发现了什么),其中保存的内容就是这几家“指定酒楼”,也就是当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', 'E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32', 'E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32\\lib', 'E:\\Anaconda\\Anaconda\\lib\\site-packages\\Pythonwin']

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

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

3. 将模块与名字绑定

找到相应的非亲生模块还没完,加载了包还得为它分配一个指定的名字,我们才能在脚本中使用这个模块。

当然多数时候我们感知不到这个过程,因为我们就是一个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', 'E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32', 'E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32\\lib', 'E:\\Anaconda\\Anaconda\\lib\\site-packages\\Pythonwin']
>>> sy.path
['', 'E:\\Anaconda\\Anaconda\\python37.zip', 'E:\\Anaconda\\Anaconda\\DLLs', 'E:\\Anaconda\\Anaconda\\lib', 'E:\\Anaconda\\Anaconda', 'E:\\Anaconda\\Anaconda\\lib\\site-packages', 'E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32', 'E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32\\lib', 'E:\\Anaconda\\Anaconda\\lib\\site-packages\\Pythonwin']
>>> sys == sy
True

问题解决

好了,上面就是对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做的事没差呀

可以,贫道看你很有悟性,不如跟我去学修仙吧

所以我们就通过sysos两个模块七搞八搞(这两个模块以前有过介绍,不再赘述)——

噔噔噔噔——好使了

# 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导入机制的介绍,实际上限于篇幅,导入机制只是做了一个概览,具体的内容还要更加复杂。本文讲到的这三步则适用于比较常见的情形,了解了这三步也足以应付很多问题了。更多内容还是留待大家自行探索,当然后续也可能会有文章进一步讲解——谁知道呢哈哈~~(又挖坑了)~~

目录
相关文章
|
存储 Kubernetes 算法
MinIO 分布式集群搭建
MinIO 分布式集群搭建 分布式 Minio 可以让你将多块硬盘(甚至在不同的机器上)组成一个对象存储服务。由于硬盘分布在不同的节点上,分布式 Minio 避免了单点故障。 Minio 分布式模式可以搭建一个高可用的对象存储服务,你可以使用这些存储设备,而不用考虑其真实物理位置。
7228 0
|
JSON 前端开发 安全
layui框架实战案例(22):多附件上传实战开发实录(php后端、文件删除、数据库删除)
layui框架实战案例(22):多附件上传实战开发实录(php后端、文件删除、数据库删除)
972 0
|
关系型数据库 MySQL Linux
LAMP和LNMP区别--详解
LAMP和LNMP区别--详解
783 0
|
传感器 边缘计算 监控
探索未来网络:物联网技术的革新与挑战
在21世纪的科技浪潮中,物联网(IoT)正迅速成为连接实体世界与数字世界的关键桥梁。本文旨在深入探讨物联网技术的最新进展、面临的主要挑战以及未来的发展趋势。通过分析当前物联网的核心技术,如传感器技术、低功耗广域网(LPWAN)、边缘计算等,本文揭示了这些技术创新如何推动智能城市、智能家居、工业4.0等领域的快速发展。同时,针对数据安全、隐私保护、标准化等关键问题,提出了相应的解决策略和建议。本文最后展望了物联网技术在未来可能带来的社会变革和商业模式创新,强调了跨学科合作的重要性,以实现物联网技术的可持续发展。
|
JSON 前端开发 Java
SpringCloud怎么搭建GateWay网关&统一登录模块
本文来分享一下,最近我在自己的项目中实现的认证服务,目前比较简单,就是可以提供一个公共的服务,专门来处理登录请求,然后我还在API网关处实现了登录拦截的效果,因为在一个博客系统中,有一些地址是可以不登录的,比方说首页;也有一些是必须登录的,比如发布文章、评论等。所以,在网关处可以支持自定义一些不需要登录的地址,一些需要登录的地址,也可以在网关处进行校验,如果未登录,可以返回JSON格式的出参,前端可以进行相关处理,比如跳转到登录页面等。
551 4
|
Python
Python 下载 html 中的 图片
Python 下载 html 中的 图片
124 2
|
设计模式 关系型数据库 UED
软件工程之设计分析(1)
软件工程之设计分析(1)
177 0
|
数据格式
vue-element怎么给select下拉框赋值?
vue-element怎么给select下拉框赋值?
299 0
vue-element怎么给select下拉框赋值?
|
Java 测试技术 数据库
基于 SpringBoot+Vue+Java 的大学生体质测试管理系统
基于 SpringBoot+Vue+Java 的大学生体质测试管理系统
|
JavaScript
Vue框架的element组件table文字居中
Vue框架的element组件table文字居中