python------------------------模块化

模块化

一般来说,编程语言中,库,包,模块是同一概念,是代码组织方式.
Python中只有一种模块对象类型,但是为了模块化组织模块的便利,提供了"包"的概念.
模块module1,指的是Python的源代码文件
包package,指的是模块组织在一起的和报名同名的目录及其相关文件.
导入语句

语句含义
import 模块1[,模块2,…]完全导入
import … as …模块别名

import语句
1,找到指定的模块,加载和初始化它,生成模块对象.找不到,抛出异常
2,在import所在的作用域的局部命名空间中,增加名称和上一部创建的对象关联
单独运行下面的例子,体会区别

  • 导入模块
import functools
print(dir())# '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'functools']
print(functools)#<module 'functools' from 'C:\\Users\\sunzh\\AppData\\Local\\Programs\\Python\\Python36\\lib\\functools.py'>
print(functools.wraps)#<function wraps at 0x000002A9B4F9ED08>
  • 导入os.path, os加入当前名词空间(因为windows和linux的操作系统的区别,os.path导入的模块也不尽相同)
import os.path
print(dir()) #['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'os']
print(os) #<module 'os' from 'C:\\Users\\sunzh\\AppData\\Local\\Programs\\Python\\Python36\\lib\\os.py'>
print(os.path) #<module 'ntpath' from 'C:\\Users\\sunzh\\AppData\\Local\\Programs\\Python\\Python36\\lib\\ntpath.py'>
  • 导入os.path并赋给osp
import os.path as osp
print(dir()) #['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'os']
print(osp) #<module 'ntpath' from 'C:\\Users\\sunzh\\AppData\\Local\\Programs\\Python\\Python36\\lib\\ntpath.py'>
  • 在函数内导入
def testimport():
    import os.path #局部
    print(dir())

testimport()
print(globals().keys())

import os.stat 导入函数也是可以的,但是pycharm会标记

总结
导入等级模块,其名称会加入到本地名词空间中,并绑定到其模块对象.
导入非顶级模块,将其顶级模块名称加入到本地名词空间中,导入的模块必须使用完全限定名称来访问.
如果用了as,as后的名称直接绑定导入的模块对象,并将该名称加入到本地名称空间来

语句含义
from … import…部分导入
from … import … as …别名

from语句

  • 在当前名词空间导入该模块指定的成员
from pathlib import Path,PosixPath
print(dir())  #['Path', 'PosixPath', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
  • 在当前名词空间导入该模块所有公共成员(非下划线开头成员)或指定成员
from pathlib import *
print(dir())#['Path', 'PosixPath', 'PurePath', 'PurePosixPath', 'PureWindowsPath', 'WindowsPath', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
  • 别名
from functools import wraps as wr,partial
print(dir())  #['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'partial', 'wr']
  • 加载,初始化os,os.path模块,exists加入本地名词空间并绑定
from os.path import exists

if exists('o:/t'):
    print('Found')
else:
    print('Not Found')

print(dir())
print(exists)

import os
# 四种方法获得同一对象
print(os.path.exists)
print(exists)
print(os.path.__dict__['exists'])
print(getattr(os.path, 'exists'))

总结
找到from子句中指定的模块,加载并初始化它,注意不是导入
对于import子句后的名称
1,先查from子句导入的模块是否具有该名称的属性
2,如果不是,则尝试导入该名称的子模块
3,还没有找到,则抛出ImportError异常
4,这个名称保存到本地名词空间.如果有as子句,则使用as子句后的名称

from pathlib import Path
print(Path, id(Path))

import pathlib as p1
print(dir())
print(p1)
print(p1.Path, id(p1.Path))

可以看出导入的名词Path和p1.Path是同一个对象

自定义模块

自定义模块: .py就是一个模块

#test1
print("this is animal module")
class A:
    def showmodule(self):
        print(1, self.__module__, self)
        print(2, self.__dict__)
        print(3, self.__class__.__dict__)
        print(4, self.__class__.__name__)

a = A()
a.showmodule()

#test2
import test1
a = test1.A()
a.showmodule()

#test3
from test1 import A as cls
a = cls()
a.showmodule()

自定义模块命名规范
1,模块名就是文件名
2,模块名必须符合标识符的要求,是非数字开头的字母,数字和下划线的组合,test-module.py 这样的文件名,不能作为米快名.也不要使用中文.
3,不要使用系统模块名来避免冲突,除非你明确知道这个模块名的用途
4,通常模块名为全小写,下划线来分割

模块搜索顺序

使用sys.path查看收缩顺序

import sys
for p in sys.path:
    print(p)

显示结果为,Python模块的路径搜索顺序
当加载一个模块的时候,需要从这些搜索路径中从前到后依次查找,并不搜索这些目录的子目录.
搜索到模块就加载,搜索不到就抛异常
路径也可以为字典,zip文件,egg文件
.egg文件,由setptools库创建的包,第三方库常用的格式.添加了元数据(版本号,依赖项等)信息的zip文件路径顺序为
1,程序主目录,程序运行的主程序脚本所在的目录
2,PYTHONPATH目录,环境变量PYTHONPATH设置的目录也是收缩模块的路径
3,标准库目录,Python自带的库模块所在目录
sys.path可以被修改,增加新的目录

模块的重复导入

#test1
print("This in test module")
#test2
import test1
print("~~~~~~~~~~~~~")
import test1
import test1
import test1
import test1
import test1

从执行结果来看,不会产生重复导入的现象.
所有加载的模块都会记录在sysmodule中,sysmodule是存储已经加载过的所有模块的字典.
打印sys.modules可以看到os,os.path都已经加载了

模块运行

__name__,每个模块都会定义一个__name__特殊变量来存储当前模块的名称,如果不指定,则默认为源代码文件名,如果是包则有限定名
解释器初始化的时候,会初始化sys.modules字典(保存以加载的模块),加载builtins(全局函数,常量)模块,__main__模块,sys__模块,以及初始化模块搜索路径sys.path
Python是脚本语言,任何一个脚本都可以直接执行,也可以作为模块被导入.
当从标准输入(命令行方式敲代码),脚本($ python test.py)或交互读取的时候, 会将模块的__name__设置为__main
,模块的顶层代码就在__main__这个作用域中执行.顶层代码:模块中缩进最外层的代码.
如果是import导入的,其__name__默认就是模块名

#test1
print("This in test module")

#test2
import test1
if __name__ == '__main__':
    print('in __maile__')
else:
    print("nor in __mail__")

if __name__ == ‘__main__’:用途
1,对本模块的功能测试
对于非主模块,测试本模块内的函数,类
2,避免主模块变更的副作用
顶层代码,没有封装,主模块使用时没有问题,但是,一旦有了新的主模块,老的主模块成了被导入的模块,由于源来的代码没有封装,一并执行了

模块的属性

属性含义
__file__字符串,源文件路径
__cached__字符串,编译后的字节码文件路径
__spec__显示模块的规范
__name__模块名
__package__当模块是包,同__name__,否则可以设置为顶级模块的空字符串

包,特殊的模块
Python模块支持目录吗?
实验
项目中新建一个目录m,使用下面的代码

import m
print(m)
print(type(m))
print(dir(m))  #ModuleNotFoundError

竟然可以导入模块,目录也是文件,所以可以导入,不过问题是,目录模块怎么写入代码?
为了解决这个问题,Python要求在目录下建立一个特殊文件__init__.py,在其中写入代码
pythcharm中,创建Directory和创建Python package不同,前者是创建不同的目录,后者是创建一个带有__init__.py文件的目标即包.
Pyhton中,目录可以作为模块,这就是包,不过代码需要写在目录下__init__.py中.
子模块
包目录下的py文件,子目录都是其子模块

m
|--__init__.py
|--m1.py
|--m2
    |--__init__.py
    |--m21
        |--__init__.py
    |--m22.py

如上创建模块目录和文件,所有的py文件中就写一句话print(name)

import m
import m.m1
from m import m1
from m.m2 import m21
import m.m2.m21

print('------------')
print(dir())

print('-'*30)
import sys
print(sorted(filter(lambda x:x.startswith('m'), sys.modules.keys())))

删除__init__.py试一试,可以发现删除并不影响导入,但是这不是良好的习惯,请保留__init__.py文件

模块和包的总结

包能够更好的组织模块,尤其是大的模块,其代码行数很多,可以把它拆成很多子模块,便于使用某些功能就加载相应的子模块.
包目录中__init__.py是在包第一次导入的时候就会自行,能容可以为空,也可以适用于该报初始化工作的代码.
最好不要删除它(低版本不可删除__init__.py文件)
导入子模块一定为加载父模块,但是带入付模块,一定不会导入子模块
包目录之间只能使用.号作为间隔符,表示模块及其中模块的成绩关系
模块也是封装,如同类,函数,不过它能够封装变量,类,函数.
模块就是命名空间,其内部的顶层标识符,都是它的属性,可以通过__dict__或者dir(moudle)查看.
包也是模块,但模块不一定是包,包是特殊的模块,是一种组织方式,它包含__path__属性

绝对导入,相对导入

绝对导入
在import语句或者from导入模块,模块名称最前面不是以.点开头的
绝对导入总是去模块搜索路径中找,当然会超看以下该模块是否应景加载
相对导入

  • 只在包内使用,且只能用在from语句中
    使用.点号,表示当前目录内
    …表示上一级目录
  • 不要在顶层模块中使用相对导入
    举例a.b.c模块,a,b是目录,c是模块c.py,c的代码中,使用
    from.import d# importts a.b.d
    …三点表示上上一级

使用下面结构的包,体会相对导入的使用

m
|--__init__.py
|--m1.py
|--m2
    |--__init__.py
    |--m21
        |--__init__.py
    |--m22.py

测试下有相对导入语句的模块,能偶直接运行吗?
不能了,很好理解,使用相对导入的模块就是为了内部互相的引用资源,不是为了直接运行的,对于包来说,正确的使用方式还是在顶级模块使用这些包.
相对导入,更像是目录操作

访问控制

下划线开头的模块名
_或者__开头的模块能否被导入
创建文件名为_xyz.py或者__xyz.py测试
都可以成功的导入,因为他们都是合法的标识符,就可以用作模块名
模块内的标识符


from m import m1
import sys
print(m1.A, m1._A, m1.__A, m1.__my__)
print(sorted(sys.modules.keys()))
print('------------')
print(dir())

普通变量,保护变量,私有变量,特殊变量,都没有被隐藏,也就是说模块内没有私有的变量,在模块中定义不做特殊处理.

# from语句
from m.m1 import A,_B as B, __my__,__C as C

import sys
print(sorted(sys.modules.keys()))
print(dir())

print(A, B, __my__, C)

依然可以使用from语句,访问所有变量
from … import* 和__all__
使用from…import* 导入

# m.m1
print(__name__)
A = 5
_B = 6
__C = 7

__my__ = 8

# test1.py
# from语句
from m.m1 import *

import sys
print(sorted(sys.modules.keys()))
print(dir())
print(locals())

结果是只导入A,下划线开头的都没有导入
使用__all__
__all__是一个列表,元素是字符串,每一个元素都是一个模块内的变量名

# m.m1中
__all__ = ['X', 'Y']
print(__name__)
A = 5
_B = 6
__C = 7

__my__ = 8

X = 10
Y = 20

# test1.py
from m.m1 import *

import sys
print(sorted(sys.modules.keys()))
print(dir())
print(locals())

修改__all__列表,加入下划线开头变量,看看什么效果
最后我们可以看到使用from xyz import * 导入__all__列表中的名称

总结

一,使用from xyz import * 导入
1,如果模块没有__all__.from xyzimport *.只导入非下划线开头的该模块的变量,如果是包,之米快也不会导入,除非在__all_中设置,或者__init__.py中导入他们
2,如果模块有__all__,from xyz inport *知道如__all__类表中指定的名称, 哪怕这个名词是下划线开头的,或者子模块
3,from xyz import *放肆到日,使用简单,但是其副作用是导入大量不需要使用的变量,甚至有可能造成名称的冲突.而__all__可以控制被导入模块在这种导入试下能够提供变量名称,就是为了组织from xyz import * 导入过多的模块变量,从而避免冲突.因此,编写模块是,应该尽量加入__all__
二,from module import name1,name2 导入
这种方式的导入时明确的,哪怕是导入子模块,或者导入下划线开头的名称
程序员可以有控制的导入名称和其对象

模块变量的修改

#yxz.py
pirnt(__name__)
X = 10


# test.py
import xyz

print(xyz.X)



#test.py
import xyz

print(xyz.X)
xyz.X = 50

import test2

模块对象是同一个,因此模块的变量也是同一个,对模块变量的修改,会影响所有的使用者.
除非万不得已,或明确知道自己在做什么,否则不要修改模块的变量
前面学习过的猴子补丁,也是通过打补丁的方式,修改模块的变量,类.函数等信息

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值