大型项目中为了维护方便,通常使用模块化开发,模块化的过程中,就会涉及到各种包或者模块的相互导入,即使是对于有多个项目的Python开发者来说,
import
也会让人困惑!本文带你深入了解python中import
的内在机制,从而避免import导入引发的异常。
任何.py
文件都可以称为模块
可以将多个模块放入一个包中,就像电脑中的文件夹,但与文件夹的区别是,package包含__init__.py
文件
当我们执行python xx.py
时,python是如何帮我们正确定位包所在的目录呢?其实系统是按照以下顺序来寻找的:
1.系统内置模块,比如os, sys模块2.入口文件所在的目录,比如main.py所在的目录3.Python环境变量,也就是我们平时pip install后的包所在的目录,如Anaconda下的site-packages目录
在Python中,如果遇到了import错误,我们可以通过以下命令查看搜索路径:
import sys
print(sys.path)
结果:
sys.path: [
'/Users/root/Python/project',
'/Users/root/anaconda3/lib/python36.zip',
'/Users/root/anaconda3/lib/python3.6',
'/Users/root/anaconda3/lib/python3.6/lib-dynload',
'/Users/root/.local/lib/python3.6/site-packages',
'/Users/root/anaconda3/lib/python3.6/site-packages',
'/Users/root/anaconda3/lib/python3.6/site-packages/Sphinx-1.5.6-py3.6.egg',
'/Users/root/anaconda3/lib/python3.6/site-packages/aeosa',
'/Users/root/anaconda3/lib/python3.6/site-packages/mdr-0.0.1-py3.6-macosx-10.7-x86_64.egg'
]
可以看到,其中第一个目录是我们运行的文件所在目录,其他都是Python环境变量中的目录
python xx.py
发生了什么?直接被Python解释器运行的文件,称之为程序的入口文件,在Python中,入口文件有且仅有一个
我们经常使用以下语句:
# in main.py
if __name__=="__main__":
run()
__name__=="__main__"
这个语句就是检测当前脚本是否被当作入口文件使用,如果被当做入口文件使用,那么就运行if __name__=="__main__":
下面的代码块,如果只是当做模块被其他的模块import
进去使用,那就不应该运行这部分代码块。
因此,判断一个文件是否被python解释器当做入口文件对待,是可以打印脚本的__name__
变量的。
# in main.py
print(__main__)
入口文件和import
路径有什么关系呢?
如果我们把xx.py
当做入口文件,那么Python解释器,就会将xx.py
所在的目录,加入到sys.path中,作为import时搜索的根目录。简言之,你用python filename.py
执行哪个文件,Python解释器就会将那个文件所在目录加入import
的搜索目录。
新问题:在模块化的项目中,如何让Python解释器搜索到所有的模块呢?
这引入了一个项目结构合理性的问题:入口文件的目录层级应该是顶层的,也就是入口文件目录层级不应该低于任何模块或者包,一个常见的目录结构通常如下:
$ tree
./project
├── package
│ ├── __init__.py
│ ├── sub_pkg1
│ │ ├── __init__.py
│ │ ├── module_X.py
│ │ └── module_Y.py
│ └── sub_pkg2
│ ├── __init__.py
│ └── module_Z.py
└── main.py(入口文件)
如果我们执行python main.py
,那么main.py就会被当做入口文件,所在目录project
就会被加入import的搜索路径,那么我们将能够搜索package1
和package2
中的模块;现在假设我们直接运行python module_X.py
,此时入口文件变成了module_X.py
,那么我们import
搜索的根目录为sub_pkg1
,当我们试图去import sub_pkg2
中的文件时,就会引发异常。
上述问题引入了一个准则:除了入口文件之外,其他文件都不应该通过python xx.py来运行,那如果我们想运行某个模块怎么办?
答案是:通过python -m sub_pkg1.module_X
来运行!
python -m
的意思是将module当做模块来运行,同时sub_pkg1.module_X
是导入路径,这样子就不会找不到模块了。
上述解决方案引入一个比较好的实践:
如果在一个大型项目中,你经常使用cd
命令跳转到各种不同层级的目录中Python xx.py
运行某个模块,那你可能得尝试使用Python -m
了,也就是我们最好在项目的根目录下完成所有的模块的测试。当然,完善的项目,应该有专门的测试目录如tests
, 这个以后有机会再讲。
在Python中,存在相对导入和绝对导入两种import机制,但无论是绝对导入还是相对导入,都需要一个参照物,不然「绝对」与「相对」的概念就无从谈起。绝对导入的参照物是项目的根文件夹,相对导入参照物是当前模块,当我们使用相对导入时,需要给出相对于当前模块,想导入模块所在的位置。
# 相对导入
from . import fool
from .package import fool
from ..module import spam
# 绝对导入,项目根目录是app
from app.package import fool
相对导入可以避免硬编码带来的维护问题,例如我们改了某一顶层包的名字,那么其子包所有的导入就都不能用了。除非我们手动修改顶层包名。但是存在相对导入语句的模块,不能直接使用python xx.py
的方式运行,否则会有异常:
ValueError: Attempted relative import in non-package
•如果是绝对导入,一个模块只能导入自身的子模块或和它的顶层模块同级别的模块及其子模块•如果是相对导入,一个模块必须有包结构(意味着有__init__.py
)且只能导入它的顶层模块内部的模块 所以,如果一个模块被直接运行,Python会将该模块当做顶层模块(top level),不再当做一个包来对待,因此不存在层次结构,所以找不到其他的相对路径,所以如果直接运行python xx.py ,而xx.py有相对导入就会报错
看下面例子:
$ tree
./project
├── package
│ ├── __init__.py
│ ├── sub_pkg1
│ │ ├── __init__.py
│ │ ├── module_X.py
│ │ └── module_Y.py
│ └── sub_pkg2
│ ├── __init__.py
│ └── module_Z.py
└── main.py(入口文件)
module_X.py
from . import module_Y
print "X __name__", __name__
module_Y.py
print "Y __name__", __name__
当我们直接运行 python sub_pkg1/module_X.py
的时候,会报错
ValueError: Attempted relative import in non-package
当我们这样运行的时候 python -m sub_pkg1.module_X
, 才能正常运行
Y __name__ sub_pkg1.moduleY
X __name__ __main__
为什么会这样?简单地说,直接运行 .py 文件和 import 这个文件有很大区别。Python 解释器判断一个 py 文件属于哪个 package 时并不完全由该文件所在的文件夹决定。它还取决于这个文件是如何 load 进来的(直接运行 or import)。
有两种方式加载一个 py 文件:
•作为 top-level 脚本 作为 top-level 脚本指的是直接运行脚本,比如 python xx.py。有且只能有一个 top-level 脚本,就是最开始执行的那个(比如 python xx.py 中的 xx.py)。
•作为 module 作为 module 是指,执行 python -m xx,或者在其它 py 文件中用 import 语句来加载,那么它就会被当作一个 module。
当一个 py 文件被加载之后,它会被赋予一个名字,保存在 __name__
属性中。如果是 top-level 脚本,那么名字就是__main__
。如果是作为 module,名字就是把它所在的 packages/subpackages 和文件名用 . 连接起来。
例如,moduleX 被 import 进来,它的名字就是 package.subpackage1.moduleX。如果 import 了 moduleA,它的名字是 package.moduleA。如果直接运行 moduleX 或 moduleA,那么名字就都是__main__
了。
所以上面的module_X
的__name__
是__main__
, 因为他是直接运行的, module_Y
的__name__
是sub_pkg1.module_Y
,因为他是被import 来使用的。
module_X.py
# module_X导入module_Y
# 相对导入
from . import module_Y
# 绝对导入
from package.sub_pkg1 import module_Y
module_X.py
# module_X导入module_Z
# 相对导入
from ..sub_pkg2 import module_Z
# 绝对导入
from package.sub_pkg2 import module_Z
main.py
# main.py导入module_Y
from package.sub_pkg1 import module_Y
特别需要注意的是,虽然上述模块导入路径是对的,除了main.py
之外,都不可以通过python xx.py
的方式运行,而是通过前面讨论过个python -m
方式运行。
相对导入可以避免硬编码,对于包的维护是友好的。其缺点是可读性较差,让人很难清楚地了解到资源所在的位置。
绝对路径导入由于其直观往往是大家的首选。只要看一下导入语句你就能知道资源是从什么位置导入的。再者,就算当前import语句的位置发生了变化,此绝对路径导入的资源依然有效。实际上,官方也推荐使用绝对路径导入。
《Python之禅》中提到:
明确 优于 隐晦
纵观一些优秀的开源项目,绝对导入的使用也是更为普遍的。我个人通常以绝对导入为主,相对导入为辅,只会在同一个模块层级中使用一些相对导入,如:from .fool import spam
,很少会使用from ...fool import spam
这样让人迷惑的相对导入。
1.区分是文件夹还是包(有无__init__.py
)?
2.非入口文件是否是使用python -m运行的?
3.入口文件的层级,是否高于任何包或者模块?
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。