python学习之路--包的导入

本文深入解析Python中常见的包导入错误:ValueError: attempted relative import beyond top-level package,探讨其原因并提供三种解决策略:固定包名结构及程序入口、设置环境变量PYTHONPATH以及采用相对路径(不推荐)。通过实例展示如何在复杂目录结构中正确导入模块。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 luffy最近在用python做一个项目. 一开始只针对各个功能进行实现, 没有什么问题. 后来出现某个模块需要调用另外一个模块的方法, 这时候就出现问题了, 频繁报错:
ValueError: attempted relative import beyond top-level package
假设项目的目录结构是这样的:
Fruit文件夹下有A文件夹, A文件夹下有B, C文件夹

Fruit|
	f.py			
	A|
 		__init__.py
		a.py		
		B|
			__init__.py
			b.py		
		C|
			__init.py
			c.py		

原因分析

有三个主要原因, 导致Python比较容易出现导入包的错误, 不像java或者c/c++, 很少出现导入包或者库的问题;

  1. py文件允许单独执行, 也就是说你可以单独以 f.py, a.py或者b.py作为入口来执行, 但是此文件会作为main模块, 就是top-level package.

  2. 在终端执行py文件的时候, 会将终端当前的路径cur_path作为参数传递给python程序, 在python程序中的所有相对路径代码./ ../ .../都是基于cur_path的(可以理解为进程所在路径为基准).

  3. python导入包的时候, 会从程序入口所在py文件的当前目录下进行查找, 如果找不到, 会从python环境变量列表里面找(实际上是遍历sys.path, 你可以print(sys.path)查看这些路径)

出现错误的情况千变万化, 这里不去做分析, 只提供靠谱的做法, 如下.

方式1 固定包名结构, 固定程序入口

 此种方式结构最清晰, 容易理解. 有以下几点要求.

  1. 确定程序结构, 在任意模块中调用其他模块, 严格遵守包的层次结构;
  2. 执行py文件的时候
#F/A/a.py
def a():
    pass
#F/A/B/b.py
def b():
    pass

在c.py中导入b模块, 严格按照包层次命名: import A.B.b

#F/A/C/c.py
import A.B.b as b 
def c():
    print('This is {}, I imported {}'.format(__name__, b.__name__))
#F/f.py
#F/f.py
import A.a as a 
import A.B.b as b
import A.C.c as c

print('This is {}, I imported {}, {} and {}'.format(__name__, a.__name__, b.__name__, c.__name__))
c.c()

 在F下执行python f.py, 得到输出结果

This is __main__, I imported A.a, A.B.b and A.C.c
This is A.C.c, I imported A.B.b

如果要单独执行包内的某个模块, 比如c.py, 在F目录下, python -m A.C.c即可

方式2 设置环境变量

 环境变量PYTHONPATH, 如果你把你需要调用的包所在路径'home/username/F/A'写入PYTHONPATH, 那么python解释器会将其添加的’sys.path’的开头, 也就是说会首先从home/username/F/A查找包.
设置PYTHONPATH:
 那么, 如果你要在c.py中调用b.py, 你需要把包B所在路径添加到环境变量, 有永久和临时两种方式, 这里只介绍临时.

方法一:命令窗口添加路径

export PYTHONPATH=$PYTHONPATH:/home/username/F

 注意:此方法只在当前命令窗口生效,即如果打开一个新的Terminal 窗口,定位到当前目录, 打印PYTHONPATH 是没有刚才加入的路径的.

方法二:在c.py 中添加:

#F/A/C/c.py
PROJECT_PATH = "/home/ubuntu/F/"
import sys
sys.path.append(PROJECT_PATH)
print(sys.path)
import A.B.b as b 
def c():
    print('This is {}, I imported {}'.format(__name__, b.__name__))
c()

OK, 在任意位置运行c.py文件, 都可以得到正确的结果,

This is __main__, I imported A.B.b

方式3 采用相对路径(强烈不推荐)

 这种方式就是产生混乱的源泉, 所以建议不要使用.
有时候临时需要单独调试某个模块, 又不想切换到顶级目录, 便使用这种. 比如还是在c.py中调用b模块, 代码如下:

#F/A/C/c.py
import sys
sys.path.append('../')
import B.b as b 

def c():
    print('This is {}, I imported {}'.format(__name__, b.__name__))

c()

将终端cd到c.py所在路径, 然后执行python c.py, OK, 可以得到正确的结果.!!!但是, 一旦在其他目录执行c.py, 或者在顶级目录执行程序入口, 用到了c模块, 就会出错. 比如在c的上一级目录A下执行python C/c.py

Traceback (most recent call last):
  File "C/c.py", line 4, in <module>
    import B.b as b 
ModuleNotFoundError: No module named 'B'

参考博客

python引入模块报错ValueError: attempted relative import beyond top-level package
Python 相对导入
python3文档
Python中的PYTHONPATH环境变量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值