第八章 Python 模块与包

 

第1章 import语句

1.1 什么是模块

#常见的场景:一个模块就是一个包含了python定义和声明的文件(文件名就是模块名字加上.py的后缀),模块可以被导入使用。

#但其实import加载的模块分为四个通用类别: 

 

  1 使用python编写的.py文件

 

  2 已被编译为共享库或DLL的C或C++扩展

 

  3 把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件夹称之为包)

 

  4 使用C编写并链接到python解释器的内置模块

 

1.2  如何引用模块

1.2.1 import 导入模块

示例文件:spam.py,文件名spam.py, 模块名spam

#spam.py

print('from the spam.py')

 

money=1000

 

def read1():

    print('spam模块:',money)

 

def read2():

    print('spam模块')

    read1()

 

def change():

    global money

    money=0

 

 

 

import spam
# 导入spam 模块  from the spam.py
# 导入模块干了哪些事情:
# 1、执行源文件
# 2、以源文件为基础产生一个全局名称空间
# 3、在当前位置拿到一个模块名,指向2创建的名称空间

money=1000000000000000000  # 这个是test.py里的money
def read1():        # test.py下的read1
    print('from test')

print(spam.money)  # test.py调用了spam.py名称空间下变量 money=1000
print(spam.read1)  # 调用的是spam下的read1   <function read1 at 0x00000074102498C8>
spam.read1()       #   print('spam模块:',money) 这个money 找的是 spam里的money
# spam.read1属于spam空间的东西
# 作用域关系:在定义的时候就固定了 跟调用位置没有关系 所以全局作用域要回spam去找

spam.read2()
# spam模块
# spam模块: 1000

spam.change()   # global money money=0
print(money)    # 访问的是test的money
spam.read1()    # spam模块: 0 这个结果就变了 因为读到是spam下的money 在上面change的时候做了修改

 

 

 

1.2.2 as 别名

# 别名 as
import spam  as s1 #s1就是spam的引用 可能源文件名非常长,可以用as 别名引用
print(s1.money)
s1.read1()

 

 

 

 

# 另外一种用途:根据模块的功能的不同,给启用一个相同的别名,统一调用的方式
# 有oracle和mysql两个模块 里面都有一个sqlparse sql解析功能 但是里面的方法实现不一样
# 要求用户输入 自己来选择使用哪种数据库

sql_type=input('sql_type: ' )
if sql_type == 'mysql':
    import mysql as sql
    # mysql.sqlparse()
elif sql_type == 'oracle':
    import oracle as sql
    # oracle.sqlparse()

# 启用sql相同的名字 只不过根据用户输入内容的不同 sql对应不同的模块
sql.sqlparse()

 

1.2.3 一行导入多个模块

# 在一行导入多个模块
import os,sys,spam # 不推荐这么写
import os
import sys
import spam

 

1.2.4 导入模块的种类

import sys
import spam
print(sys)      # <module 'sys' (built-in)> 内置模块
print(spam)     # <module 'spam' from 'D:\\Python_Learn\\练习\\3模块与包\\spam.py'> 自定义模块 
# 还可以有下载第三方模块 和 包

 

第2章  from...import的用法

import spam
# 好用一点的方式 可以直接引用spam里的money和函数 不用加前缀

from spam import money,read1 # 从源文件里拿到这两个名字
print(money)    # 1000
print(read1)    # <function read1 at 0x000000E64BB498C8>

# 导入模块3件事
# 1、执行源文件
# 2、形成一个源文件的全局名称空间
# 3、在当前位置拿到一个名字,来自于spam

# 唯一的区别就是:使用from...import...则是将spam中的名字直接导入到当前的名称空间中,所以在当前名称空间中,直接使用名字就可以了、无需加前缀:spam.
#from...import...的方式有好处也有坏处
    # 好处:使用起来方便了 
    # 坏处:容易与当前执行文件中的名字冲突

# 只是形式上不用加前缀

 

2.1 from...import 的as 别名

from spam import money as m # 给money取了别名m
print(m)  # 1000

                                                    

2.2 批量增加名字

# 批量增加名字
from spam import  *
# 并不推荐所有 只有当源文件中的名字确实太多的时候 并且在当前位置都要用到
print(money)    # 1000

 

2.3 * 与 _ 的对应

### 如果一定要用*的话 可以这么区分:

源文件中变量与函数前面加下划线_

_money=1000      # 变量名

def _read1():    # 函数名  涉及到名称空间
   
print('spam模块:',money)

 

*默认是导入所有 除下滑线开头 隐藏起来 :

print(money)    # 1000
print(_money)    # 1000

这两种使用都会找不到,说明被隐藏起来了,只针对*

 


from spam import _money   # 这样可以找到
print(_money)    # 1000

 

 

2.4 __all__[] 与 *

# 在源文件中增加 会控制 * 的范围 ,只能调用到money

__all__=['money','read1']
money=1000      # 变量名

...

 

print(money)    # 1000
print(read1)    # <function read1 at 0x00000090C8BC98C8>
print(read2)    # name 'read2' is not defined

 

 

2.5 模块加载到内存中

# 模块只在第一次导入时才会执行,之后的导入都是直接引用中已经存在的结果
import sys
print(sys.modules)   # key value字典形式 这个字典中存放着 已经加载到内存的模块
print('spam' in sys.modules)  # False 还没有加载
import spam # from the spam.py 只打印一次
print('spam' in sys.modules)  # True 已经加载了
import spam
import spam
import spam
import spam
# 第一次导入的时候 就已经加入到内存中 第二次开始就无需读取 所以就不打印了

 

第3章 模块的搜索路径

3.1 模块的重载(了解)

import time
import spam
time.sleep(10)
import spam # 优先读取内存
print(spam.money)
# 执行文件和被导入文件的路径
# 你修改源文件后 一定要重启程序 才能让修改生效 重新加载一次

import importlib
importlib.reload() # 重新加载 生成环境不能这么用 只能在测试环境下

 

 

3.2 模块搜索路径

# 注意:自定义的模块名 一定不要与python自带的模块名重名
# 内存中已经加载的模块->内置模块->硬盘中(sys.path路径中包含的模块)

 

3.3 sys.path路径

# sys.path 路径 硬盘中
# spam 并不是和执行脚本放在同一路径
# import sys
# print(sys.path) # 列表类型 # 第一个值是 执行文件的当前目录 'D:\\Python_Learn\\练习\\3模块与包' :这个就是sys.path

import sys
# import spam   # 不在内存 也不是内置模块
# 如果不和执行文件在同一路径 No module named 'spam'  sys.path找不到
# 将模块加入到sys.path中 找到spam.py上级目录的路径 D:\Python_Learn\练习\3模块与包\模块的搜索路径\a

sys.path.append(r'D:\Python_Learn\练习\3模块与包\模块的搜索路径\a')
# 加到最前面的方法:
# sys.path.insert(0,r'D:\Python_Learn\练习\3模块与包\模块的搜索路径\a')
import spam         # from the spam.py 找到了

 

 

['D:\\Python_Learn\\练习\\3模块与包\\模块的搜索路径', 'D:\\Python_Learn', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages']

 

['D:\\Python_Learn\\练习\\3模块与包\\模块的搜索路径', 'D:\\Python_Learn', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages', 'D:\\Python_Learn\\练习\\3模块与包\\模块的搜索路径\\a']

 

from the spam.py

 

 

3.4 区分Python文件的两种用途

def func1():
    print('from m1')
def func2():
    print('from m2')
def func3():
    print('from m3')

# 被当做脚本的时候 才执行测试代码
# 当做模块的时候 不想执行
# print(__name__)

# 文件当做脚本运行时,__name__等于__man__
# 文件当做模块被加载运行时,__name__ 等于模块名

if __name__ == '__main__':
    # 当做脚本使用
   
func1()
    func2()
    func3()

 

 

### 调用时不会执行func1,func2,func3

import m1
# 如何m1从头到尾执行一遍
m1.func2()

 

第4章 包

4.1 包的定义

如果在Python2里 不写 __init__.py 就不算一个包 也不能被导入 只是一个普通的文件夹

*** 包含__init__.py的文件夹 就是一个包

# 官网解释

Packages are a way of structuring Python’s module namespace by using “dotted module names”

包是一种通过使用‘.模块名’来组织python模块名称空间的方式。

 

#具体的:包就是一个包含有__init__.py文件的文件夹,所以其实我们创建包的目的就是为了用文件夹将文件/模块组织起来

 

#需要强调的是:

  1. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错

 

  2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包的本质就是一种模块

 

# 为何要使用包

包的本质就是一个文件夹,那么文件夹唯一的功能就是将文件组织起来

随着功能越写越多,我们无法将所以功能都放到一个文件中,于是我们使用模块去组织功能,而随着模块越来越多,我们就需要用文件夹将模块文件组织起来,以此来提高程序的结构性和可维护性

 

注意事项

 

#1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。

但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。

 

#2、import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件

 

#3、包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间

 

4.2 包的使用

# 在import导入过程中 带 . 的都跟 包 有关
# import spam.x  # 不可以这么导入 虽然可以带点. 但这是导入包的语法 

# No module named 'spam.x'; 'spam' is not a package
import spam
print(spam.x) # 1

 

4.2.1 导入包

# 在执行文件中导入包 其实就是导入包下面的__init__.py文件,产生的名称空间也是__init__.py的,而产生的名字aaa 指向的就是这个文件的名称空间。

 

import aaa
print(aaa)   # <module 'aaa' from 'D:\\Python_Learn\\练习\\3模块与包\\包\\aaa\\__init__.py'>  包就是个模块 来自于包下的init文件
# __init___文件是默认文件 导入一个文件夹不能执行 只能给文件夹定制一个__init___.py的文件,产生的名称空间也是 __init___ 但是名字是aaa
# print(aaa.x)
# print(aaa.y)    # 都是来自于init文件

 

4.2.2 使用包下的模块

在包aaa下建立m1和m2两个模块,想在run.py执行文件中调用

# run.py下的内容
import sys
print(sys.path)

import aaa   # 在这里导入了aaa 也运行了  print(sys.path) 获取的应该是aaa的sys.path

 

# __init__.py下的内容


import sys
print(sys.path)
# import m1

 

# 执行文件run.py的sys.path:
# ['D:\\Python_Learn\\练习\\3模块与包\\包', 'D:\\Python_Learn', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages']

#  在执行文件 调用aaa包 得到的__init__.py的sys.path

# ['D:\\Python_Learn\\练习\\3模块与包\\包', 'D:\\Python_Learn', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages']

 

执行文件是run.py sys.path就是run的路径 'D:\\Python_Learn\\练习\\3模块与包\\包

导入其他的模块 也以run的sys.path为准

 

4.2.3 为什么找不到包下的模块

所以:想要调用aaa包下的m1模块的过程是

1、 运行run.py 执行文件 导入 aaa 这个包,执行的是 __init__.py这个文件

2、 __init__.py 执行了 import m1这个模块

3、 这个时候__init__.py要找m1.py这个文件  

4、 先从内存和内置中查找m1并没有

5、 从sys.path查找,现在以执行文件  run.py 的sys.path为准

 'D:\\Python_Learn\\练习\\3模块与包\\包

 

按照这个过程查找m1 无法找到的  所以直接报错 没有这个模块

No module named 'm1'

 

如果执行 __init__.py 就不会出现问题

import sys
print(sys.path)
import m1

 

# 问题的关键 sys.path以执行文件为准

这是因为 运行__init__.py 导入m1的时候 是以__init__.py的sys.path路径为准:

'D:\\Python_Learn\\练习\\3模块与包\\包\\aaa'

是可以找到m1这个模块的

 

 

 

4.2.4 如何解决sys.path的问题

问题就在于 以run.py为执行文件 sys.path的路径是 'D:\\Python_Learn\\练习\\3模块与包\\包

__init__.py 的导入 就要从aaa里查找:

 

# import sys    # 报错

from aaa import m1
# 执行文件是run

# sys.path是'D:\\Python_Learn\\练习\\3模块与包\\包'
# 导入流程是 从aaa下导入m1 先要找到aaa   aaa就在包下面 所以可以找到 也就可以找到m1

 

 

 

import aaa   # 在这里导入了aaa 也运行了  print(sys.path) 获取的应该是aaa的sys.path
print(aaa.m1) # 找到模块 <module 'aaa.m1' from 'D:\\Python_Learn\\练习\\3模块与包\\包\\aaa\\m1.py'>
aaa.m1.func1() # f1 可以执行这个功能

 

 

4.3 二层包的导入

在aaa包下建立bbb包

 

 

 

# 调用aaa.bbb

run.py:

aaa.bbb  # bbb是从aaa里拿出来的 aaa指向aaa的__init___.py , bbb来自于aaa的__init___.py
print(aaa.bbb)  # 333

 

__init__.py:

from aaa import m1

bbb=333

 

 

# 调用 aaa.bbb的包

# 和拿m1 和 m2 一样

 

import aaa   # 在这里导入了aaa 也运行了  print(sys.path) 获取的应该是aaa的sys.path

print(aaa.bbb)

# aaa.bbb  # bbb是从aaa里拿出来的 aaa指向aaa的__init___.py , bbb来自于aaa的__init___.py

# 取到bbb的__init__.py <module 'aaa.bbb' from 'D:\\Python_Learn\\练习\\3模块与包\\包\\aaa\\bbb\\__init__.py'>

 

from aaa import bbb # 我导入bbb其实就是拿到 bbb的_init_.py文件

# aaa.bbb # 这样就会触发 bbb下的___init__.py的运行

 

bbb 下面有一个模块m3

def func3():
    print('f3')

 

 

如何在run中调用m3

aaa.bbb.m3  # bbb下面的m3  m3来自于bbb的名称空间 bbb的名称空间来自于bbb.__init__.py

print(aaa.bbb.m3) # 拿到的就是aaa下bbb的m3的值

 

拿到m3.py模块

# 还是sys.path的问题 先看执行文件run.py的路径:

D:\\Python_Learn\\练习\\3模块与包\\包

这个路径就是起始路径,m3.py在包/aaa/bbb下所以要在bbb的__init__.py下导入:

from aaa.bbb import m3

# 凡是在导入时带点的,点的左边都必须是一个包

然后run就可以找到m3.py

 

print(aaa.bbb.m3) # 拿到的就是aaa下bbb的m3的值 <module 'aaa.bbb.m3' from 'D:\\Python_Learn\\练习\\3模块与包\\包\\aaa\\bbb\\m3.py'>

aaa.bbb.m3.func3() #f3

 

4.4 灵活调用

aaa.func1()
aaa.func2()
aaa.func3()
# aaa. 也就是调用 aaa下的__init__.py

 

from aaa.m1 import func1
from aaa.m2 import func2
from aaa.bbb.m3 import func3

 

这样使用 就是开发者通过init模块 给使用者提供了便利

 

4.5 包的练习

glance/                   #Top-level package

 

├── __init__.py      #Initialize the glance package

 

├── api                  #Subpackage for api

 

│   ├── __init__.py

 

│   ├── policy.py

 

│   └── versions.py

 

├── cmd                #Subpackage for cmd

 

│   ├── __init__.py

 

│   └── manage.py

 

└── db                  #Subpackage for db

 

    ├── __init__.py

 

    └── models.py

 

#文件内容

 

#policy.py

def get():

    print('from policy.py')

 

#versions.py

def create_resource(conf):

    print('from version.py: ',conf)

 

#manage.py

def main():

    print('from manage.py')

 

#models.py

def register_models(engine):

    print('from models.py: ',engine)

 

执行文件与示范文件在同级目录下

 

 

 

4.5.1 包的使用之import

包的使用之import 

1 import glance.db.models

2 glance.db.models.register_models('mysql') 

 

import glance.db.models
glance.db.models.register_models('mysql')  # from models.py:  mysql

 

4.5.2 单独导入包名称时

# 单独导入包名称时不会导入包中所有包含的所有子模块,如
#在与glance同级的test.py中
# import glance
# glance.cmd.manage.main()  #  module 'glance' has no attribute 'cmd'

# 思考
# 1、import 的是glance  所以要去glanced里找 glance 的__init__.py 文件
# 2、执行print(glance.cmd) 可以找到glance的cmd了
# 3、然后要找到cmd下的manage.main() 、manage是模块 需要在cmd 的__init__.py找到 from glance.cmd import manage


import glance

print(glance.cmd)

# <module 'glance.cmd' from 'D:\\Python_Learn\\练习\\3模块与包\\练习\\glance\\cmd\\__init__.py'>

print(glance.cmd.manage)

# <module 'glance.cmd.manage' from 'D:\\Python_Learn\\练习\\3模块与包\\练习\\glance\\cmd\\manage.py'>

glance.cmd.manage.main()        # from manage.py

 

 

4.5.3 简化成glance.方法的模式

glance.get()

glance.create_resource(conf)

glance.main()

glance.register_models(engine)

 

减轻使用者烦恼:直接调用方法

 

# run.py 执行文件

import glance
glance.get()    # from policy.py
glance.create_resource('create.conf')   # from version.py:  create.conf
glance.main()       #from manage.py
glance.register_models('mysql')         #from models.py:  mysql

 

# glance的__init__.py

from glance.api.policy import get
from glance.api.versions import create_resource
from glance.cmd.manage import main
from glance.db.models import  register_models

 

使用者可以直接glance.方法

 

第5章 绝对导入与相对导入

# 绝对导入 :包所在的顶级 就是根目录 在这里根就是glance

from glance.api.policy import get
from glance.api.versions import create_resource
from glance.cmd.manage import main
from glance.db.models import  register_models

 

# 绝对导入的问题:如果根目录名称改变 里面的都要改 ,比如glance改为glance_v1 那么对应的导入语句都要跟着修改,比较麻烦。

 

5.1 使用相对导入

import glance
glance.get()    # from policy.py
glance.create_resource('create.conf')   # from version.py:  create.conf
glance.main()       #from manage.py
glance.register_models('mysql')         #from models.py:  mysql

 

 

# 相对导入
# from . import get   # .代表当前目录 也就是__init__.py的当前目录 并不是sys.path的
#  . 就是这个文件的当前目录 .api就是当前目录下的aip

from .api.policy import get
from .api.versions import create_resource
from .cmd.manage import main
from .db.models import register_models

 

这样以后如果glance的名称改变了 只需要在执行文件中修改即可,__init__.py就无需修改。

 

5.2 包内部之间的引用

例如 api下的policy 要导入 db下models的register_models方法

# 绝对导入

from glance.db.models import register_models
def get():
    print('from policy.py')
    register_models('mysql')

 

 

import glance
glance.get()

>>> from policy.py

>>> from models.py:  mysql

 

# 相对导入 ..上一级目录 到glance目录 再找db
from ..db.models import register_models 
def get():
    print('from policy.py')
    register_models('mysql')

 

5.3 执行文件找到其他路径下的包

将glance整体移动到其他目录 run.py还留在原始目录下:

 

 

 

import sys
sys.path.append(r'D:\Python_Learn\练习\3模块与包\a'#加入glance包的路径
import glance

>>> from policy.py

>>> from models.py:  mysql

 

第6章 软件开发规范

 

转载于:https://www.cnblogs.com/touchlixiang/p/7702997.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值