Python—包介绍

包(Package)

当你的模块文件越来越多,就需要对模块文件进行划分,比如把负责跟数据库交互的都放一个文件夹,把与页面交互相关的放一个文件夹,

.
└── my_proj
    ├── crm #代码目录
    │   ├── admin.py
    │   ├── apps.py
    │   ├── models.py
    │   ├── tests.py
    │   └── views.py
    ├── manage.py
    └── my_proj #配置文件目录
        ├── settings.py
        ├── urls.py
        └── wsgi.py

像上面这样,一个文件夹管理多个模块文件,这个文件夹就被称为包

那不同包之间的模块互相导入呢?

crm/views.py内容

def sayhi():
    print('hello world!')

通过manage.py调用

from crm import views

views.sayhi()

执行manage.py (注意这里用python2)

Alexs-MacBook-Pro:my_proj alex$ ls
crm        manage.py    my_proj
Alexs-MacBook-Pro:my_proj alex$ python manage.py 
Traceback (most recent call last):
  File "manage.py", line 6, in <module>
    from crm import views
ImportError: No module named crm

竟然说找不到模块,为什么呢?

包就是文件夹,但该文件夹下必须存在 __init__.py 文件, 该文件的内容可以为空。__int__.py用于标识当前文件夹是一个包。

在crm目录下创建一个空文件__int__.py ,再执行一次就可以了

Alexs-MacBook-Pro:my_proj alex$ touch crm/__init__.py #创建一个空文件
Alexs-MacBook-Pro:my_proj alex$ 
Alexs-MacBook-Pro:my_proj alex$ ls crm/
__init__.py    admin.py    models.py    views.py
__pycache__    apps.py        tests.py    views.pyc
Alexs-MacBook-Pro:my_proj alex$ python manage.py 
hello world!

注意,在python3里,即使目录下没__int__.py文件也能创建成功,猜应该是解释器优化所致,但创建包还是要记得加上这个文件 吧。

跨模块导入

目录结构如下

.
├── __init__.py
├── crm
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   ├── views.py  
├── manage.py   
└── proj
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

根据上面的结构,如何实现在crm/views.py里导入proj/settings.py模块?

直接导入的话,会报错,说找到不模块

$ python3 views.py
Traceback (most recent call last):
  File "views.py", line 2, in <module>
    from proj import settings
ModuleNotFoundError: No module named 'proj'

是因为路径找不到,proj/settings.py 相当于是crm/views.py的父亲(crm)的兄弟(proj)的儿子(settings.py),settings.py算是views.py的表弟啦,在views.py里只能导入同级别兄弟模块代码,或者子级别包里的模块,根本不知道表弟表哥的存在。这可怎么办呢?

答案是添加环境变量,把父亲级的路径添加到sys.path中,就可以了,这样导入 就相当于从父亲级开始找模块了。

crm/views.py中添加环境变量

import sys ,os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #__file__的是打印当前被执行的模块.py文件相对路径,注意是相对路径
print(BASE_DIR)

sys.path.append(BASE_DIR)  

from proj import settings

def sayhi():
    print('hello world!')

print(settings.DATABASES)

输出

$ python3 views.py
/Users/alex/Documents/work/PyProjects/luffy_课件/21天入门/chapter4-常用模块/packages/my_proj
---my proj init---  #proj/__init__.py输出
in proj/settings.py #proj/settings.py输出
{'host': 'localhost'}

*注意;此时在proj/settings.py写上import urls会有问题么?

DATABASES= {
    'host':'localhost'
}

import urls  #这行刚加的 
print('in proj/settings.py')

结果报错了

ModuleNotFoundError: No module named 'urls'

为什么呢? 因为现在的程序入口是views.py , 你在settings.py导入import urls,其实相当于在crm目录找urls.py,而不是proj目录,若想正常导入,要改成如下

DATABASES= {
    'host':'localhost'
}

from proj import urls  #proj这一层目录已经添加到sys.path里,可以直接找到
print('in proj/settings.py')

绝对导入&相对导入

在linux里可以通过cd ..回到上一层目录 ,cd ../.. 往上回2层,这个..就是指相对路径,在python里,导入也可以通过..

例如:

.
├── __init__.py
├── crm
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   ├── views.py  #from ..proj import settings 
├── manage.py   
└── proj
    ├── __init__.py
    ├── settings.py #from .import urls  
    ├── urls.py
    └── wsgi.py

views.py里代码

from ..proj import settings
def sayhi():
    print('hello world!')

print(settings.DATABASES)

执行结果报错了

Traceback (most recent call last):
File "my_proj/crm/views.py", line 4, in <module>
 from ..proj import settings
SystemError: Parent module '' not loaded, cannot perform relative import

或者有人会看到这个错

ValueError: attempted relative import beyond top-level package

其实这两个错误的原因归根结底是一样的:在涉及到相对导入时,package所对应的文件夹必须正确的被python解释器视作package,而不是普通文件夹。否则由于不被视作package,无法利用package之间的嵌套关系实现python中包的相对导入。

文件夹被python解释器视作package需要满足两个条件:

  1. 文件夹中必须有__init__.py文件,该文件可以为空,但必须存在该文件。
  2. 不能作为顶层模块来执行该文件夹中的py文件(即不能作为主函数的入口)。

所以这个问题的解决办法就是,既然你在views.py里执行了相对导入,那就不要把views.py当作入口程序,可以通过上一级的manage.py调用views.py

.
├── __init__.py
├── crm
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   ├── views.py  #from ..proj import settings 
├── manage.py  #from crm import views 
└── proj
    ├── __init__.py
    ├── settings.py #from .import urls  
    ├── urls.py
    └── wsgi.py

事实证明还是不行,报错

ValueError: attempted relative import beyond top-level package

但把from ..proj import settings 改成from . import models 后却执行成功了,为什么呢?

from .. import models会报错的原因是,这句代码会把manage.py所在的这一层视作package,但实际上它不是,因为package不能是顶层入口代码,若想不出错,只能把manage.py往上再移一层。

正确的代码目录结构如下

    packages/
    ├── __init__.py
    ├── manage.py #from my_proj.crm  import views
    └── my_proj
        ├── crm
        │   ├── admin.py
        │   ├── apps.py
        │   ├── models.py
        │   ├── tests.py
        │   ├── views.py  #from . import models;  from ..proj import settings 
        └── proj
            ├── __init__.py
            ├── settings.py
            ├── urls.py
            └── wsgi.py

再执行manage.py就不会报错了。

posted @ 2018-09-09 22:14  ZivLi  阅读(731)  评论(0编辑  收藏  举报