首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Python】对绝对导入与相对导入的理解的补充

【Python】对绝对导入与相对导入的理解的补充

作者头像
明月AI
发布于 2021-10-28 06:58:41
发布于 2021-10-28 06:58:41
1.4K00
代码可运行
举报
文章被收录于专栏:野生AI架构师野生AI架构师
运行总次数:0
代码可运行

先把代码放出来,test.py:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import sys
from test_lib.lib1 import func1

print(f'\n__name__: {__name__} in ./test.py')
print(f'__package__: {__package__} in ./test.py')
print(sys.path)

test_lib/lib1.py:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import sys
from .lib2 import func2

def func1():
    print('func1')

print(f'\n__name__: {__name__} in ./test_lib/lib1.py')
print(f'__package__: {__package__} in ./test_lib/lib1.py')
print(sys.path)

test_lib/lib2.py:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import sys
print(f'\n__name__: {__name__} in ./test_lib/lib2.py')
print(f'__package__: {__package__} in ./test_lib/lib2.py')
print(sys.path)

def func2():
    print('func2')

另外,还有两个__init__.py文件(空文件)就省略了。

1. 相对导入


在前一篇文章对相对导入的“相对”其实已经讲得比较清楚了,关键的一点是,“相对”是相对package的意思。相对导入的常用语法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from .lib2 import func2
from . import lib2

另外还有直接导入上级的包或者模块,但是这很容易出错,建议不要使用。上面的两种语法,建议也只使用第一种。

当然,前面一篇文章已经说过了,使用相对导入是有缺陷的,没法直接运行该文件进行测试。

2. 绝对导入


前一篇已经提到,理解绝对导入,最重要的就是理解sys.path这个环境变量,绝对导入的时候,会按顺序在这个path指定的路径中查找。如果找到了,则加载进来,而如果对所有路径都查找完了还是没有找到,则会报错。

前面的代码已经写好,我们直接运行:python test.py,会得到输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
__name__: test_lib.lib2 in ./test_lib/lib2.py
__package__: test_lib in ./test_lib/lib2.py
['/home/deeao/test', 
 '/home/alex/.local/lib/python3.8/site-packages', 
 '/usr/local/lib/python3.8/dist-packages', 
 '/usr/lib/python3/dist-packages'
]

__name__: test_lib.lib1 in ./test_lib/lib1.py
__package__: test_lib in ./test_lib/lib1.py
['/home/deeao/test', 
 '/home/alex/.local/lib/python3.8/site-packages', 
 '/usr/local/lib/python3.8/dist-packages', 
 '/usr/lib/python3/dist-packages'
]

__name__: __main__ in ./test.py
__package__: None in ./test.py
['/home/deeao/test', 
 '/home/alex/.local/lib/python3.8/site-packages', 
 '/usr/local/lib/python3.8/dist-packages', 
 '/usr/lib/python3/dist-packages'
]

可以看到sys.path在三个脚本中的输出结果都是一样的,默认加入到path路径的只有直接被运行的文件所在的目录(如果在其他目录下,运行test.py文件结果也是一样的,例如在上级目录运行:python test/test.py),“/home/deeao/test”这个是test.py所在的目录。所以:

  1. 在test.py文件中调用lib1.py可以使用绝对引用:from test_lib.lib1 import func1;
  2. 而在lib1中引用lib2的时候,就要使用相对引用了:from .lib2 import func2,如果把lib2前面的点号去掉,则会报错,因为在sys.path的路径中找不到lib2这个模块。

如果在lib1中不想使用相对导入怎么办?

一种解决方法是改成:“from test_list.lib2 import func2”,这样在路径/home/deeao/test中就能找到对应的模块了。但是这样会有一个很大的弊端:这时如果直接运行lib1.py进行测试的时候,则会报错,因为在系统路径中没法找到“test_list.lib2”这个模块。但是直接运行文件测试又是非常常用的。

另一种解决方案是,在import之前将当前路径加入到sys.path中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import sys
from os.path import abspath, dirname
# 把当前文件所在的目录加入到sys.path中
# 相当于把 /home/deeao/test/test_lib 加入了路径中
sys.path.insert(0, abspath(dirname(__file__)))

from lib2 import func2

__file__这个内置变量是当前python文件的完整的路径。

这样可以解决外部调用的问题,也可以解决直接运行该文件的问题。但是这并不完美,因为:

  1. 当我们的项目比较大的时候,子模块就会比较多,这时就会有好多的路径加入到了系统路径中,搜索效率还是小问题,最大的问题是不同目录下的文件名是可能有冲突的,这时可能就会加载到错误的模块了;
  2. 如果每个模块都加上这么一个代码也很不优雅(DRY),维护也不方便。

3. 建议的选择


前面已经看到了,无论是相对导入,还是绝对导入,都是有缺陷的,那我们已经怎么选择呢?

我的建议:优先使用相对导入

不要使用直接运行python文件的方式来测试,而是使用单元测试,例如对于lib1.py的测试应该是单独建立一个单元测试文件:lib1_test.py,由这个文件来进行(单元)测试

这是我能想到的最优雅的方式了。

4. __all__变量与__init__.py文件


关于包和模块还有两点是值得说道说道的:

4.1 __all__变量

直接看代码,lib2.py:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
__all__ = ['func2']

def func2():
    print('func2')

def func3():
    print('func3')

lib1.py:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from .lib2 import *

func2()
func3()

这时运行python lib1.py,会报错:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
NameError: name 'func3' is not defined

而如果把import语句改成“from .lib2 import func2, func3”,就能正常运行。

也就是说,通过星号导入的只能是__all__变量定义的对象。

不过建议在导入的时候,不要使用星号。

4.2 __init__.py文件

__init__.py也是个神奇的文件,很多比较初级的工程师可能都比较疑惑,这个文件有什么用。前一篇文章已经说过,这个文件是用来定义一个package的,有这个文件,就表示当前目录是一个package了。

还是先看代码,test_lib/__init__.py:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from .lib1 import func1
from .lib2 import func2

./test.py:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from test_lib import func1, func2

这样是可以正常运行的,也就是说,在__init__.py定义的变量函数什么的,或者引用其他模块的,在其他package可以通过包名直接导入。

不过,如果没有特别的,通常保持一个空文件__init__.py就好了,没必要操这个心。

5. 小结

我的建议:

  1. 同一个package的,优先使用相对导入;
  2. 需要对模块文件测试时,除非是单一的文件,否则不建议使用"if __name__ == '__main__'",而是直接使用一个对应的单元测试文件来测试,这最优雅;
  3. __all__这个变量作用不大,通常不要使用,import的时候不要使用星号;
  4. __init__.py定义的是一个package,通常保持空文件就好了。

20210724

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 野生AI架构师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Rust语法入门
Rust 是一种系统级编程语言,它的设计目标是提供高性能、安全性和并发性。Rust 的主要优势包括:
码客说
2023/04/17
1.4K0
一网打尽 Rust 语法
大家好,我是「柒八九」。一个「专注于前端开发技术/Rust及AI应用知识分享」的Coder
前端柒八九
2024/04/30
2250
一网打尽 Rust 语法
🌱 Rust内存管理黑魔法:从入门到"放弃"再到真香
内存管理是程序世界的"隐形战场",而 Rust 用一套所有权系统直接重构规则——没有 GC、没有手动 malloc/free,却能在编译期拦截 90% 的内存错误!
Jimaks
2025/04/27
2650
rust闭包(Closure)
闭包在现代化的编程语言中普遍存在。闭包是一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值。Rust 闭包在形式上借鉴了 Smalltalk 和 Ruby 语言,与函数最大的不同就是它的参数是通过 |parm1| 的形式进行声明,如果是多个参数就 |param1, param2,…|, 下面给出闭包的形式定义:
zy010101
2023/04/27
7480
rust闭包(Closure)
Rust到底值不值得学--Rust对比、特色和理念
其实我一直弄不明白一点,那就是计算机技术的发展,是让这个世界变得简单了,还是变得更复杂了。 当然这只是一个玩笑,可别把这个问题当真。
俺踏月色而来
2019/10/14
2.8K0
Rust闭包的虫洞穿梭
闭包(Closure)的概念由来已久。无论哪种语言,闭包的概念都被以下几个特征共同约束:
袁承兴
2020/09/19
1.4K0
rust 上手很难?搞懂这些知识,前端开发能快速成为 rust 高手
在我的交流群里有许多人在讨论 rust。所以陆续有人开始尝试学习 rust,不过大家的一致共识就是:rust 上手很困难。当然,这样的共识在网上也普遍存在。
用户6901603
2024/03/20
1.6K1
rust 上手很难?搞懂这些知识,前端开发能快速成为 rust 高手
Rust 入门 (Rust Rocks)
做区块链的基本几乎没有人不知道 Rust 这门编程语言,它非常受区块链底层开发人员的青睐。说来也奇怪,Rust 起源于 Mazilla,唯一大规模应用就是 Firefox,作为小众语言却在区块链圈子里火了。这其中应该和以太坊的发起人 Govin Wood 创建的 Parity 项目有关,Parity 是一款用 Rust 编写的以太坊客户端。
lambeta
2019/09/24
2.5K0
Rust学习:如何解读函数签名?
在Rust中,函数签名类似“讲故事”。经验丰富的Rust程序员,只需浏览一个函数的签名,就可以知道该函数大部分的行为。
MikeLoveRust
2019/09/03
2.3K0
Rust入坑指南:智能指针
在了解了Rust中的所有权、所有权借用、生命周期这些概念后,相信各位坑友对Rust已经有了比较深刻的认识了,今天又是一个连环坑,我们一起来把智能指针刨出来,一探究竟。
Jackeyzhe
2020/03/12
9480
rust的高级特性
rust中的表达式是什么{}包围的部分,函数,impl,match里面,if else表达式,通过这些功能分割系统
李子健
2022/05/10
6870
rust的内存管理
内存管理是rust最有意思的事情了。rust的内存管理有三条准则。 let分配资源 分配会转移所有权,比如赋值直接move了 值和变量在作用域末尾会被清理,释放 drop方法会在释放前调用 rust支持移动语义和复制语义,为此抽象出了两个trait,clone和copy 非堆内存可以使用copy,隐式转化,clone需要显示调用 关于借用的规则,使用& 一个引用的生命周期不能超过其被引用的时间 如果存在一个可变借用,不允许存在其他值 如果不存在可变借用,允许存在多个不可变借用 借用规则方法类型 &self
李子健
2022/05/08
8040
【翻译】Rust生命周期常见误区
我曾经有过的所有这些对生命周期的误解,现在有很多初学者也深陷于此。我用到的术语可能不是标准的,所以下面列了一个表格来解释它们的用意。
MikeLoveRust
2020/07/28
1.7K0
听GPT 讲Rust源代码--library/alloc(2)
在Rust源代码中,rust/library/alloc/src/vec/mod.rs这个文件是Rust标准库中的Vec类型的实现文件。Vec是一个动态大小的数组类型,在内存中以连续的方式存储其元素。
fliter
2024/02/26
2560
听GPT 讲Rust源代码--library/alloc(2)
Rust中的关键字
原始标识符(Raw identifiers)允许你使用通常不能使用的关键字,其带有 r# 前缀
fliter
2023/10/05
2740
Rust中的关键字
【Rust】005-Rust 结构体
在Rust中,元组结构体是一种特殊的结构体形式,它结合了元组和结构体的特性。元组结构体类似于普通的结构体,但它没有字段名称,只有字段类型。这种结构体更像是带标签的元组,通常用于需要对某些数据进行简单封装而不需要命名每个字段的场景。
訾博ZiBo
2025/01/06
1790
《Rust避坑式入门》第1章:挖数据竞争大坑的滥用可变性
赵可菲是一名Java程序员,一直在维护一个有十多年历史的老旧系统。这个系统即将被淘汰,代码质量也很差,每次上线都会出现很多bug,她不得不加班修复。公司给了她3个月的内部转岗期,如果转不出去就会被裁员。她得知公司可能会用Rust重写很多系统,于是就报名参加了公司的Rust培训,希望能够转型。
程序员吾真本
2024/08/29
6960
《Rust避坑式入门》第1章:挖数据竞争大坑的滥用可变性
【译】为 嵌入式 C 程序员编写的 Rust 指南
这是来自 Google OpenTitan 团队,给嵌入式 C 程序员专门打造的一份 Rust 指南。
张汉东
2021/10/13
5.5K0
Rust 总结
所有权是用来管理堆上内存的一种方式,在编译阶段就可以追踪堆内存的分配和释放,不会对程序的运行期造成任何性能上的损失。
谛听
2022/06/04
1.8K0
Rust入门之严谨如你
Rust作为一门快速发展的编程语言,已经在很多知名项目中使用,如firecracker、libra、tikv,包括Windows和Linux都在考虑Rust【1】。其中很重要的因素便是它的安全性和性能,这方面特性使Rust非常适合于系统编程。
Radar3
2020/11/25
1.8K2
相关推荐
Rust语法入门
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档