Python模块和包

模块

  • 什么是模块
  1. Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句。
  2. 模块让你能够有逻辑地组织你的 Python 代码段。
  3. 把相关的代码分配到一个模块里能让你的代码更好用,更易懂。
  4. 模块能定义函数,类和变量,模块里也能包含可执行的代码。
  • 为什么使用模块

    如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。

      随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用,

  • 如何使用模块

示例文件:自定义模块my_module.py,文件名my_module.py,模块名my_module

#my_module.py文件

print('from the my_module.py')

money=1000

def read1():

    print('my_module->read1->money',money)

def read2():

    print('my_module->read2 calling read1')

    read1()

def change():

    global money

    money=0

模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块很import多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载大内存中的模块对象增加了一次引用,不会重新执行模块内的语句),如下 

demo.py文件

import my_module

import my_module

import my_module

执行结果

from the my_module.py

我们可以从sys.modules中找到当前已经加载的模块,sys.modules是一个字典,内部包含模块名与模块对象的映射,该字典决定了导入模块时是否需要重新导入。

每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突

#测试一:money与my_module.money不冲突

#demo.py

import my_module

money=10

print(my_module.money)

#执行结果:

from the my_module.py
#测试二:read1与my_module.read1不冲突

#demo.py

import my_module

def read1():

    print('========')

my_module.read1()

#执行结果:

from the my_module.py

my_module->read1->money 1000
#测试三:执行my_module.change()操作的全局变量money仍然是my_module中的

#demo.py

import my_module

money=1

my_module.change()

print(money)

#执行结果:

from the my_module.py

总结:首次导入模块my_module时会做三件事:

1.为源文件(my_module模块)创建新的名称空间,在my_module中定义的函数和方法若是使用到了global时访问的就是这个名称空间。

2.在新创建的命名空间中执行模块中包含的代码,见初始导入import my_module

  • 为模块起别名
import my_module as a

print(a.money)

用法示范

有两中sql模块mysql和oracle,根据用户的输入,选择不同的sql功能

#mysql.py

def sqlparse():

    print('from mysql sqlparse')

#oracle.py

def sqlparse():

    print('from oracle sqlparse')

#test.py

db_type=input('>>: ')

if db_type == 'mysql':

    import mysql as db

elif db_type == 'oracle':

    import oracle as db

db.sqlparse()
  • 一行导入多个模块
import sys,os,re

一行导入多个模块并起别名

import sys as s,os as o,re as r

在我们写代码要导入多个模块时一般不建议一行导入多个模块,虽然是减少了代码量,如果后期要注释掉某个模块那就比较麻烦了。

单个模块导入虽然代码量增加了,但阅读性比单行导入的要高

  • from ... import ...导入方式 

对比import my_module,会将源文件的名称空间'my_module'带到当前名称空间中,使用时必须是my_module.名字的方式

而from 语句相当于import,也会创建新的名称空间,但是将my_module中的名字直接导入到当前的名称空间中,在当前名称空间中,直接使用名字就可以了、

from my_module import read1,read2

read1()

read2()

#执行结果

from the my_module.py

my_module->read1->money 1000

my_module->read2 calling read1

my_module->read1->money 1000

这样在当前位置直接使用read1和read2就好了,执行时,仍然以my_module.py文件全局名称空间#测试一:导入的函数read1,执行时仍然回到my_module.py中寻找全局变量money

#demo.py

from my_module import read1

money=1000

read1()

#执行结果:

from the my_module.py

spam->read1->money 1000

#测试二:导入的函数read2,执行时需要调用read1(),仍然回到my_module.py中找read1()

#demo.py

from my_module import read2

def read1():

    print('==========')

read2()

#执行结果:

from the my_module.py

my_module->read2 calling read1

my_module->read1->money 1000

如果当前有重名read1或者read2,那么会有覆盖效果。

#测试三:导入的函数read1,被当前位置定义的read1覆盖掉了

#demo.py

from my_module import read1

def read1():

    print('==========')

read1()

#执行结果:

from the my_module.py

from my_module import read1 as read 也是支持as别名的

from my_module import (read1,read2,money)也支持多行导入的

  • from my_module import *的用法

from my_module import * 把my_module中所有的不是以下划线(_)开头的名字都导入到当前位置,大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题。

from my_module import *

print(money)

print(read1())

print(read2())

print(change())

在my_module.py中新增一行

__all__=['money','read1'] #这样在另外一个文件中用from my_module import *就这能导入列表中规定的两个名字

*如果my_module.py中的名字前加_,即_money,则from my_module import *,则_money不能被导入

  • 模块的加载于修改

考虑到性能的原因,每个模块只被导入一次,放入字典sys.modules中,如果你改变了模块的内容,你必须重启程序,python不支持重新加载或卸载之前导入的模块,

特别的对于我们引用了这个模块中的一个类,用这个类产生了很多对象,因而这些对象都有关于这个模块的引用。

def func1():

print('func1')
import time,importlib

import aa

time.sleep(20)

# importlib.reload(aa)

aa.func1()

在20秒的等待时间里,修改aa.py中func1的内容,等待test.py的结果。

打开importlib注释,重新测试

  • 模块当脚本执行

我们可以通过模块的全局变量__name__来查看模块名:
当做脚本运行:
__name__ 等于'__main__'

当做模块导入:
__name__= 模块名

作用:用来控制.py文件在不同的应用场景下执行不同的逻辑
if __name__ == '__main__':

def fib(n):   

    a, b = 0, 1

    while b < n:

        print(b, end=' ')

        a, b = b, a+b

    print()

if __name__ == "__main__":

    print(__name__)

    num = input('num :')

fib(int(num))
  • 模块搜索路径

python解释器在启动时会自动加载一些模块,可以使用sys.modules查看

在第一次导入某个模块时(比如my_module),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用

如果没有,解释器则会查找同名的内建模块,如果还没有找到就从sys.path给出的目录列表中依次寻找my_module.py文件。

所以总结模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块

需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名

  • 什么是包

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

1. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法

2. 包是目录级的(文件夹级),文件夹是用来组成py文件(包的本质就是一个包含__init__.py文件的目录)

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

强调:

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

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

创建一个包

import os

os.makedirs('glance/api')

os.makedirs('glance/cmd')

os.makedirs('glance/db')

l = []

l.append(open('glance/__init__.py','w'))

l.append(open('glance/api/__init__.py','w'))

l.append(open('glance/api/policy.py','w'))

l.append(open('glance/api/versions.py','w'))

l.append(open('glance/cmd/__init__.py','w'))

l.append(open('glance/cmd/manage.py','w'))

l.append(open('glance/db/models.py','w'))

map(lambda f:f.close() ,l)

包目录结构

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

注意事项

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

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

3.对比import item 和from item import name的应用场景:
如果我们想直接使用name那必须使用后者。

我们在与包glance同级别的文件中测试

import glance.db.models

glance.db.models.register_models('mysql') 
  • from ... import ...

需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c是错误语法

我们在与包glance同级别的文件中测试 

from glance.db import models

models.register_models('mysql')

from glance.db.models import register_models

register_models('mysql')
  • __init__.py文件

不管是哪种方式,只要是第一次导入包或者是包的任何其他部分,都会依次执行包下的__init__.py文件(我们可以在每个包的文件内都打印一行内容来验证一下),这个文件可以为空,但是也可以存放一些初始化包的代码。

from glance.api import *

此处是想从包api中导入所有,实际上该语句只会导入包api下__init__.py文件中定义的名字,我们可以在这个文件中定义__all___:

#在__init__.py中定义

x=10

def func():

    print('from api.__init.py')

__all__=['x','func','policy']

 此时我们在于glance同级的文件中执行from glance.api import *就导入__all__中的内容(versions仍然不能导入)。

glance/                   

├── __init__.py      

├── api                  

│   ├── __init__.py   __all__ = ['policy','versions']

│   ├── policy.py

│   └── versions.py

├── cmd               __all__ = ['manage']    

│   ├── __init__.py

│   └── manage.py    

└── db                __all__ = ['models']              

    ├── __init__.py
 
    └── models.py

from glance.api import *

policy.get()
posted @ 2018-07-27 21:30  杨灏  阅读(178)  评论(0编辑  收藏  举报