在编码过程中,如果想要通过 from .. import package_name 的方式来引入父级目录中的包,如果处理不得当,经常会得到这样的报错信息:
ValueError: Attempted relative import beyond toplevel package
查阅大部分资料,得出的结论都是在采用相对路径时, python 不能引入父级的包。原因是引入者本身在包路径中已经处在顶层,自然程序不知道这顶层之外的信息。我们可以通过打印 name 的方式,来查看当前文件的包信息。比如我们有这样一个文件结构:
files_structures_inner.png在这个文件夹结构中,我们期望将所有配置方面的信息都放在 conf 目录下。所有业务逻辑代码放在 controllers 目录下。那么 controllers 中的文件,势必需要引入 conf 中的包。比如:controllers 中的 collect_and_save.py 的代码是这样的:
#encoding:utf-8
from ..conf import db
print 'collect_and_save.py',__name__
def work():
print 'this is collect_and_save\' work()'
print 'our db name is %s' % db.DB_NAME
然后,start.py 中的代码是这样的:
#encoding:utf-8
from controllers import collect_and_save
def start():
collect_and_save.work()
def main():
start()
if __name__ == '__main__':
main()
此时,如果我们在 top_wrapper 目录下执行:python start.py,得到的信息就是:
error01.png这就是我们熟悉的报错信息。看到一些讨论中说,那么我们就将 conf/db.py 加入到系统的绝对路径中去——这样肯定是可行的,但是我认为这样做对于代码的维护并不好。这相当于在局部程序中添加了全局变量,未来可能在新建其它工程时产生全局变量的冲突。所以,解决问题的方向还是要在如何使用相对路径的引入这个方向上找方法。
首先,我们将 start.py 和 collect_and_save.py 的代码稍加修改,再次执行来看看:
start.py:
#encoding:utf-8
# from controllers import collect_and_save
# def start():
# collect_and_save.work()
def main():
# start()
from conf import db
print db.DB_NAME
if __name__ == '__main__':
main()
collect_and_save.py:
#encoding:utf-8
# from ..conf import db
print 'collect_and_save.py',__name__
def work():
print 'this is collect_and_save\' work()'
print 'our db name is %s' % db.DB_NAME
执行结果是:
# 这是collect_and_save.py 中 print 'collect_and_save.py',__name__ 产生的结果
collect_and_save.py controllers.collect_and_save
# 这是 db.py 中的print 'db.py',__name__产生的执行结果
db.py conf.db
# 这是 start.py 中 print db.DB_NAME 产生的结果
WHATEVER
可以看到 collect_and_save.py 的 name 是:controllers.collect_and_save,这就是当前系统所知道 collect_and_save 包的信息,系统不知道在 controllers 之外还有 conf 包的存在!
所以,解决办法很简单,在整个工程外面再包一层,比如将文件结构变成这样:
files_structures.png如此一来,我们在最外层——即 top_wrapper 目录的上一层执行 python manage.py 就能达到目的。我们看看,manage.py 的代码是:
#encoding:utf8
from top_wrapper import start
def main():
start.start()
if __name__ == '__main__':
main()
再将 start.py 和 collect_and_save.py 改回原样:
start.py
#encoding:utf-8
from controllers import collect_and_save
def start():
collect_and_save.work()
def main():
start()
if __name__ == '__main__':
main()
collect_and_save.py
#encoding:utf-8
from ..conf import db
print 'collect_and_save.py',__name__
def work():
print 'this is collect_and_save\' work()'
print 'our db name is %s' % db.DB_NAME
执行结果变成:
db.py top_wrapper.conf.db
# 注意看,collect_and_save.py 的包名变成了这样
# 系统知道 collect_and_save 包之上还有 top_wrapper这个包的存在
# 根据 top_wrapper 包的信息,当然也就会知道 conf 包的存在
collect_and_save.py top_wrapper.controllers.collect_and_save
this is collect_and_save' work()
our db name is WHATEVER
至此,大功告成!
网友评论