Python--模块化

概念

  • 一般来说,变成语言中,库、包、模块是同一种概念,是代码组织方式
  • Python中只有一种模块对象类型,但是为了模块化组织模块的便利,提供了“包”的概念

模块module,指的是Python的源代码文件
包package,指的是模块组织在一起的包名同名的目录及其相关文件

导入语句

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

import语句

  1. 找到指定的模块,加载和初始化它,生成模块对象。找不到,抛出异常
  2. 在import所在的作用域的局部命名空间中,增加名称和上一步创建的对象关联
impoer functools #导入模块
print(die()) # [..., 'functools']
print(functools) # <module 'functools' from ''path/to/functools.py'>
print(functools.wraps) # <function wraps at 0x0000018ECFA1AF28>
import os.path # 导入os.path,os加入当前名词空间
print(dir()) #[..., 'os']
print(os) # # <module 'os' from ''path/to/os.py'>
print(os.path) # 完全限定名词访问path
import os.path as osp # 导入os.path并赋给osp
print(dir()) # [..., 'osp']
print(osp) # <mdule 'ntpath' form 'path/to/path.py'>
def testimport():
	import os.path # 局部
	print(dir())

testimport() # ['os']
print(globals().keys()) # 没有‘os’,但有'testimport'
# dict_keys(['__name__', '__doc__', '__package__',\
'__loader__', '__spec__', '__annotations__', \
'__builtins__', '__file__', '__cached__', 'testimport'])
import os.stat # 不可导入
# ModuleNotFoundError: No module named 'os.stat'; 'os' is not a package

总结

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

导入语句

语句含义
from…import…含义
from…import…as…别名

from语句

from pathlib import Path, PosixPath # 在当前名词空间导入该模块指定的成员
print(dir()) # [..., 'Path', 'PosixPath']
from pathlib import * # 在当前名词空间导入该模块所有公共成员(非下划线开头成员)或指定成员
print(dir()) # [...,'Path', 'PosixPath', 'PurePath', 'PurePosixPath', 'PureWindowsPath', 'WindowsPath']
from functools import wraps as wr,partial # 别名
print(dir)) # [..., 'wr', 'partial]
from os.path import exists

if exists('c://'):
	print('Found') # Found
else:
	print('Not Found')

print(dir()) # [..., 'exists']
print(exists) # <function exists at 0x000001C57FF9AD08>

4中方式获得同一个对象exists

import os

print(os.path.exists)
print(exists)
print(os.path.__dict__['exists']) # 字符串
print(getattr(os.path, 'exists')) # 字符串
# 打印结果都为
<function exists at 0x0000022CC806AD08>

总结

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

import pathlib as p1 # 导入模块使用别名
print(dir()) # [..., 'Path','p1']
print(p1) # <module 'pathlib' from 'path/to/pathlib.py'>
print(p1.Path, id(p1.Path)) # <class 'pathlib.Path'> 2089961619096

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

自定义模块

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

# t1.py文件
print('This is t1 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()

# t2.py文件
import t1

a = t1.A()
a.showmodule()

# t3.py文件
from t1 import A as cls

a = cls()
a.showmodule()

自定义模块命名规范

  1. 模块名就是文件名
  2. 模块名必须符合标识符的要求,是非数字开头的字母、数字和下划线的组合。
  3. test-module.py这样的文件名不能作为模块名,也不要使用中文
  4. 不要使用系统模块名来命名,避免冲突
  5. 通常模块名为全小写,下划线来分割单词

模块搜索顺序

使用sys.path查看搜索顺序

import sys

# print(*sys.path, sep='\n')
for p in sys.path:
    print(p) 

显示结果为,Python模块的路径搜索顺序

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

sys.path虽可以被修改,增加新的目录,但不建议修改

模块的重复导入

# t1.py文件
print('This is t1 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()

# t2.py文件
import t1

print('local module')
import t1
import t1

# t2.py文件中执行结果
This is t1 module
1 t1 <t1.A object at 0x0000022070CD9668>
2 {}
3 {'__module__': 't1', 'showmodule': <function A.showmodule at 0x0000022070CD5268>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
4 A
local module

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

模块运行

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

# 执行结果
in __main__

# t2.py
import t1

# 执行结果
in imported module

if __name__ == '__main__':用途

  1. 本模块的功能测试
    对于非主模块,测试本模块内的函数、类
  2. 避免主模块变更的副作用
    顶层代码,没有封装,主模块使用时没有问题。但是,一旦有了新的主模块,老的主模块成了被导入模块,由于原来代码没有封装,就会一并执行

模块的属性

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

# 执行结果
C:/Users/mayn/PycharmProjects/mypython/new/t3.py
__main__

  • 包,特殊的模块
  • Python模块支持目录

实验

  • 新建一个目录m
import m
print(m)
print(type(m))
print(dir(m)) # 没有__file__
print(m.__path__)

# 执行结果
<module 'm' (namespace)>
<class 'module'>
['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__']
_NamespacePath(['C:\\Users\\mayn\\PycharmProjects\\mypython\\m'])
  • 一个目录文件没有__file__,但是有__path__
  • 上例中可以看出,可以导入目录m,目录也是文件,所以可以导入。
  • Python为了解决能在目录模块中写代码,建立了一个特殊文件__init__.py

在这里插入图片描述
pycharm中,创建Directory和创建Pyhon pcakage不同,前者是创建普通的目录,后者是创建一个带有__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('-' * 30)
print(dir())

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

# 执行结果
m
m.m2
m.m2.m21
------------------------------
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'm']
------------------------------
['m', 'm.m2', 'm.m2.m21', 'marshal']

删除__init__.py文件,并不影响导入,但不建议这样操作。

模块和包的总结

  • 包能够更好的组织模块,尤其是大的模块,其代码行数很多,可以把它拆分成很多子模块,便于使用某些功能就加载相应的子模块

  • 包目录中__init__.py是在包第一次导入的时候就会执行,内容可以为空,也可以是用于该包初始化工作的代码,不要删除(低版本不可删除__init__.py文件)

  • 导入子模块一定会加载父模块,但导入父模块一定不会导入子模块

  • 包目录之间只能使用.点号作为间隔符,表示模块及其子模块的层级关系

  • 模块也是封装,如同类、函数,不过它能够封装变量、类、函数

  • 模块就是命名空间,其内部的顶层标识符,都是它的属性,可以通过__dict__dir(module)查看

  • 包也是模块,但模块不一定是包,包是特殊的模块,是一种阻止方式,它包含__path__属性

绝对导入、相对导入

绝对导入

在import语句或者from导入模块,模块名称最前面不是以.点开头的
绝对导入总是去模块搜索路径中找

相对导入

只在包内使用,且只能用在from语句中
使用.点号,表示当前目录内
使用..表示上一级目录
不要在顶层模块中使用相对导入

例:

  • a.b.c模块,a、b是目录,c是模块c.py,c的代码中,使用以下代码
from . import d # imports a.b.d
from .. import e # imports a.e
from .d import x # a.b.d.x
from ..e import x # a.e.x

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

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

注意

一旦一个模块中使用相对导入,就i不可以作为主模块运行了

访问控制

下划线开头的模块名

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

模块内的标识符

# xyz.py
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8

# abc.py
import xyz

import sys
print(sorted(sys.modules.keys())) # [..., 'xyz']
print(dir()) # [..., 'xyz']

print(xyz.A, xyz._B, xyz.__C, xyz.__my__) # 5 6 7 8 

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

from xyz import A, _B as B, __my__, __C as C

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

print(A, B, C, __my__)

使用from语句依然可以访问所有变量

from … import * 和__all__

使用from … import * 导入

# xyz.py
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8

# abc.py
from xyz import *

import sys
print(sorted(sys.modules.keys()))
print(dir()) # [..., 'A']
print(locals()['A']) # 5

A = 55
print(locals()['A']) # 55

结果只导入了A,下划线开偷的都没有导入

使用__all__

  • __all__是一个列表,元素是字符串,每一个元素都是一个模块内的变量名
# xyz.py
__all__ = ["X", "Y"]

print(__name__)
A = 5
_B = 6
__C = 7

__my__ = 8

X = 10
Y = 20

# abc.py
from xyz import *

import sys
print(sorted(sys.modules.keys()))
print(dir()) # [...,'X', 'Y']

print(locals()['X']) # 10
  • 修改__all__列表,加入下划线开头变量
# xyz.py
__all__ = ["X", "Y", "_B", "__C"]

print(__name__)
A = 5
_B = 6
__C = 7

__my__ = 8

X = 10
Y = 20

# abc.py
from xyz import *

import sys
print(sorted(sys.modules.keys()))
print(dir()) # [..., 'X', 'Y', '_B', '__C']

print(locals()['X']) # 10
print(locals()['Y']) # 20
print(locals()['_B']) # 6
print(locals()['__C']) # 7

可以看到使用from xyz import * 导入__all__列表中的名称

包和子模块

m 
|-- __init__.py
|-- m1.py
# __init__.py
print(__name__)
x = 1

# m1.py中
print(__name__)
y = 5

test.py如何访问到m1.py中的变量y

  • 访问到m.m1变量y的几种实现:
# 方法1,直接导入m1模块
import m.m1
print(m.m1.y)

# 方法2,直接导入m.m1的属性y
from m.m1 import y
print(y)

# 方法3, from m import *
# print(dir())
# 该方法导入后,无法看到子模块m1,无法访问y
# 在__init__.py增加__all_- = ['X','m1'],使用__all__提供导出的名称
from m import *
print(m1.y)

# 方法4,不适用__all__
# 在__init__.py增加from . import m1
from m import *
print(m1.y)

__init__.py中有什么变量,则使用from m inport *加载什么变量,这依然符合模块的访问控制

# __init__.py文件
print(__name__)
x = 1

from .m1 import y as _z
print(dir())

总结

  • 使用from xyz import *导入
    1.如果模块没有__all__from xyz import *只导入非下划线开头的该模块的变量。如果是包,子模块也不会导入,除非在__all__中设置,或__init__.py中导入它们
    2.如果模块有__all__from xyz import *只导入__all__列表中指定的名称,哪怕这个名词是下划线开头的,或者是子模块
    3.from xyz import *方式导入,使用简单,但是其副作用是导入大量不需要使用的变量,甚至有可能造成名词的冲突。而__all__可以控制被导入模块在这种导入方式下能够提供的变量名词,就是为了复制from xyz import *导入过多的模块变量,从而避免冲突。因此,编写模块时,应该尽量加入__all__
  • from module import name1,name2导入
    这种方式打导入是明确的,哪怕是导入子模块,或者导入下划线开头的名称,可以有控制的导入名称和其对应的对象

模块变量的修改

# xyz.py
print(__name__)
X = 10

# test2.py
import xyz

print(xyz.X)

# test.py
import xyz

print(xyz.X)
xyz.X = 50

import test2

模块对象是同一个,因此模块的变量也是同一个,对模块变量的修改,会影响所有使用者

  • 建议不要修改模块的变量
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值