python的多层Import机制

前言

经常会遇到报错ImportError: attempted relative import with no known parent package
需要搞明白是怎么回事。

一、多级引用在不同目录层级的调用权限

主目录就叫main吧

main/
----main.py
----pkg1/
--------a1.py
--------a2.py
--------a3.py
----pkg2/
--------b1.py

a1.py中

from .a2 import usefunc
 
def qqfunc():
	usefunc()

a3.py中

from a1 import qqfunc
qqfunc()

main.py中

from pkg1.a1 import qqfunc
qqfunc()

可以在main下执行main.py

但是不能在pkg1目录下执行a3.py
因为此时cwd在pkg1,不能解析.a2文件。
相对引用要求cwd在相对引用的文件的上级目录(上多少级就无所谓了)。

相反的我们把a1略作修改
a1.py中

from a2 import usefunc
 
def qqfunc():
	usefunc()

去掉了.
此时可以在pkg1目录下执行a3.py
但不能在main下执行main.py
因为去掉.之后采用的是module名直接检索,
只能在cwd=pkg1时才能搜到a2这个文件。
cwd=main时搜不到。

然后是重名的情况。
重名时候优先满足cwd下的,因为cwd是sys.path[0]

二、多级引用包装成属性的形式

核心需求

怎么才能自己写一个package。比如文件夹是/home/aa/bb/cc/dd.py。
/home下有一个t.py。
在t.py里面import aa。
就可以直接像访问property一样,调用 aa.bb.cc.dd.somefunc()。

实验

t.py内容

import aa
aa.bb.cc.dd.somefunc()

dd.py内容

def somefunc():
	print('hello')

aa/__init__.py 添加

import bb
报错ModuleNotFoundError: No module named 'bb'

改为添加

import .bb
报错ModuleNotFoundError: No module named 'bb'

改为

from . import *
报错ModuleNotFoundError: No module named 'bb'

改为

from . import bb
t.py中成功找到了aa.bb,但是报错 module 'aa.bb' has no attribute 'cc'

由是可知

  1. 相对引用package需要采用from 相对位置 import package_name的方式。因为相对位置只能写在from和import中间
  2. from . import * 只会检索当前目录下的module,而不会导入package。

继续实验

同样地在/aa/bb/__init__.py里添加

from . import cc
t.py中成功找到了aa.bb.cc,但是报错
AttributeError: module 'aa.bb.cc' has no attribute 'dd'

同样地在/aa/bb/cc/__init__.py里添加

import .dd
报错SyntaxError: invalid syntax

改成

import dd
报错ModuleNotFoundError: No module named 'dd'

改成

from . import dd
t.py成功执行。

总之一句话,相对引用,无论import后面接package还是module,都得用
from … import的格式。

一级目录下可以直接相对引用深层内容

如果在/aa/__init__.py里面这样写

from .bb.cc.dd import somefunc

在t.py里面

import aa
aa.somefunc()
aa.bb.cc.dd.somefunc()

依然可以执行成功

但是显然,子目录每多一个函数,都需要在aa/init.py里面加,也太累了。

重名情况

假设/aa/hh.py下

def somefunc():
    print('nono')

/aa/__init__.py

from .bb.cc.dd import somefunc
from .hh import somefunc

t.py下

import aa
aa.somefunc()
aa.bb.cc.dd.somefunc()

会输出

nonono
hello

调换init里面的调用顺序,两行都是hello。
可以看出,同一个__init__.py 中的重名情况,会按照引用顺序,
后引用的覆盖前者

相对引用的翻译原理

PEP-0328中详细描述了相对引用的原理

https://peps.python.org/pep-0328/

在这里插入图片描述

Relative imports use a module’s __name__ attribute to determine that module’s position in the package hierarchy. If the module’s name does not contain any package information (e.g. it is set to __main__) then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.

相对引用采用属性 __name__ 实现。

这就是为什么,如果你在一个 a.py 中 from .b import aa
python a.py 会报错
ImportError: Impoe attempted relative import with no known parent package

因为点符号.总是被翻译为parent(__name__)
即根据 __name__ 属性的值,找到它的parent package。
显然这种情况下, __name__= '__main__'
寻找parent('__main__') ,不可能找到 '__main__'的父包。
因此报错 no known parent package

但是在高一级的地方导入的话,
a的__name__ = 'packagename.a'
from .b import aa 就会被翻译成 from parent(packagename.a).b import aa
能找到父包 packagename
因此可以正常使用相对导入。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值