模块化和包

模块化

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

导入语句

语句含义
import 模块1 [,模块2,…]完全导入(后面只能跟模块,不能跟类和函数)
import… as …模块别名
  • import语句
  1. 找到指定的模块,加载和初始化它,生成模块对象.找不到,抛出异常(加载)
  2. 在import所在的作用域的局部命名空间中,增加名称和上一步创建的对象关联(关联->可调用)
import  functools #导入模块
print (dir())   ## 返回模块特殊属性以及新增了变量名"functools"
print(functools)  ##<module 'functools' from 'C:\\Programs\\Python\\Python36\\lib\\functools.py'>
print(functools.wraps) #<function wraps at 0x000001708C2AA048>

import os.path
print(dir())   ##多了一个"os"(这里os.path比较特殊)
print(os)  #'C:\\Programs\\Python\\Python36\\lib\\os.py'>
print(os.path) #   <module 'ntpath' from 'C:Programs\\Python\\Python36\\lib\\ntpath.py'>

import os.path as osp  ##导入os.path并复制给osp
print(dir())  ##多了一个"osp"
print(osp)   ##和os.path打印一样
def testimport():
   import os.path #导入到局部的作用域中
   print(dir())   ##显示本地的变量名

testimport()  # ['os']
print(globals().keys())  ##没有出现os变量名
  • 总结
  1. 导入顶级模块,其名称会加入到本地名词空间中,并绑定到其模块对象.
  2. 导入非顶级模块,纸将其顶级模块名称加入到本地名词空间中.导入的模块必须使用完全限定名称来访问
    完全限定名称访问(例如:os.path)
  3. 如果使用了as,as后的名称直接绑定到导入的模块对象,并将该名称加入到本地名词空间中
语句含义
from … import …部分导入
from … import …别名
  • 与单独import区别在于本地命名空间中添加的名称是import后的名称,而非模块名
  • 且from…import.后可以跟类函数等变量名
from pathlib import Path, PosixPath# 在当前名词空间导入该模块指定的成员
print(dir())#['Path', 'PosixPath', '__annotations__', '__builtins__'...]
from pathlib import *  #在当前名词空间导入该模块的所有公共成员,即不带下划线的
print(dir())
# out ['Path', 'PosixPath', 'PurePath', 'PurePosixPath', 'PureWindowsPath', 'WindowsPath', '__annotations__']
from functools import wraps as wr, partial# 别名
print(dir()) # [..., 'wr', 'partial']
from os.path import exists# 加载、初始化os、os.path模块,exists加入本地名词空间并绑定
if exists('o:/t'):
    print('Found')
else:
    print('Not Found')
    print(dir())
    print(exists)
    
import os
# 4种方式获得同一个对象exists
print(os.path.exists) #<function exists at 0x000001CCFA5A9D08>
print(exists)         #<function exists at 0x000001CCFA5A9D08>
print(os.path.__dict__["exists"])#<function exists at 0x000001CCFA5A9D08>
print(getattr(os.path,"exists"))#<function exists at 0x000001CCFA5A9D08>

总结

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

import pathlib as p1 ##导入模块使用别名
print(dir())  ##包含path 和 p1
print(p1)# <module 'pathlib' from 'C:\\Programs\\Python\\Python36\\lib\\pathlib.py'>
print(p1.Path, id(p1.Path)) #<class 'pathlib.Path'> 1485470051272
  • 可以看出导入的名词Path 和p1.path是同一个对象,名称不同时其名称都会记录
# t1
print("this is test1 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
import t1
a = t1.A()
a.showmodule()

# t3
from t1 import A as cls
a = cls()
a.showmodule()
  • 在t1中运行时,主模块是t1,此时实例的模块返回的是__ main__
  • 在t2,t3中运行时,主模块为所在模块,此时会先初始化并加载ti模块,执行t1中的打印操作
  • 实例的模块返回的是t1

自定义模块命名规范

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

模块搜索顺序

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

模块的重复导入

  • 模块不会产生重复导入的现象

  • 因为所有加载模块都记录在sys.modules中,sys.modules是存储已经记载过的所有模块的字典

  • 打印sys.mofules可以看到已经加载的模块

  • ‘builtins’, ‘sys’, ‘_frozen_importlib’, ‘_imp’, ‘_warnings’, ‘_thread’, ‘_weakref’, ‘_frozen_importlib_external’, ‘_io’, ‘marshal’, ‘nt’, ‘winreg’, ‘zipimport’, ‘encodings’, ‘codecs’, ‘_codecs’, ‘encodings.aliases’, ‘encodings.utf_8’, ‘_signal’, ‘main’, ‘encodings.latin_1’, ‘io’, ‘abc’, ‘_weakrefset’, ‘site’, ‘os’, ‘errno’, ‘stat’, ‘_stat’, ‘ntpath’, ‘genericpath’, ‘os.path’, ‘_collections_abc’, ‘_sitebuiltins’, ‘_bootlocale’, ‘_locale’, ‘encodings.gbk’, ‘_codecs_cn’, ‘_multibytecodec’, ‘types’, ‘functools’, ‘_functools’, ‘collections’, ‘operator’, ‘_operator’, ‘keyword’, ‘heapq’, ‘_heapq’, ‘itertools’, ‘reprlib’, ‘_collections’, ‘weakref’, ‘collections.abc’, ‘importlib’, ‘importlib._bootstrap’, ‘importlib._bootstrap_external’, ‘warnings’, ‘importlib.util’, ‘importlib.abc’, ‘importlib.machinery’, ‘contextlib’, ‘mpl_toolkits’, ‘sysconfig’, ‘encodings.cp437’])

  • sys模块已经在打开文件时就已经在字典中了,是python帮我们自动加载的一些必须的模块

模块运行

  • __ name__ ,每个模块都会定义一个__ name__特殊变脸来存储当前模块的名称,
    如果不指定,则默认为源代码文件名,如果是包则有限定名.

  • 解释器初始化的时候,会初始化sys.modules字典(保存已加载的模块),加载builtins(全局函数,常量)模块
    __ main__模块,sys模块已经初始化模块搜索路劲sys.path

  • Python是脚本语言,任何一个脚本都可以直接执行,也可以作为模块被导入

  • 从标准输入(命令行方式敲代码)、脚本(python test.py)或交互式读取的时候,
    会将模块的__ name__设置为__ main__,模块的顶层代码就在__ main__这个作用域中执行。
    顶层代码:模块中缩进最外层的代码。如果是import导入的,其__ name__默认就是模块名

if __ name__ == ‘__ main__’:用途

  • 利用了模块在被导入时模块名不再是__ main__:
  1. 本模块的功能测试
    对于非主模块,测试本模块内的函数,类
  2. 避免主模块变更的副作用
    顶层代码,没有封装,主模块使用时没有问题。但是,一旦有了新的主模块,老的主模块成了被导入模块,
    由于原来代码没有封装,一并执行了。
# test1.py文件
import test2
# test2.py文件
# 判断模块是否以程序的方式运行 $python test.py
if__name__ == '__main__':
   print('in __main__') # 程序的方式运行的代码
   else:
       print('in imported module') # 模块导入的方式运行的代码

模块的属性

属性含义
__ file__字符串,源文件路径
__cached __字符串,编译后的字节码文件路径
__ spec__显示模块的规范
__ name__模块名
__ pachage__当模块是包,同__name__;否则,可以设置为顶级模块的空字符串
import t1

for k,v in t1.__dict__.items():
    print(k,str(v)[:80])

print(dir(t1))
for name in dir(t1):
    print(getattr(t1,name))
# 打印结果
# in imported module
# __name__ t1
# __doc__ None
# __package__ 
# __loader__ <_frozen_importlib_external.SourceFileLoader object at 0x0000028F044390F0>
# __spec__ ModuleSpec(name='t1', loader=<_frozen_importlib_external.SourceFileLoader object
# __file__ C:\Users\17849\PycharmProjects\untitled\练习\模块化\t1.py
# __cached__ C:\Users\17849\PycharmProjects\untitled\练习\模块化\__pycache__\t1.cpython-36.pyc
# __builtins__ {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other o

定义: 特殊的模块

Python模块支持目录吗?

import 练习 as l
print(l)
print(type(l))
print(dir(l))
##<module '练习' (namespace)>
#<class 'module'>
#['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__']

没有__file__,但是多了一个__path__.

可以导入目录,目录也是文件,但是目录模块怎么写代码呢?
为了解决这个问题,python要求目录下建立一个特殊文件__init__.py,在其中写入代码

pytcharm中,创建Directory和创建Python package不同,前者是创建普通的目录,后者是创建一个带有__init__.py文件的目录即包

子模块

  • 包目录下的py文件,子目录都是其子模块
  • 删除__init__.py 试一试.可以发现删除并不影响导入,但是有时候一些工具发现包里没有__init__文件,就不认这个包

模块和包的总结

包能够更好的组织模块, 尤其是大的模块, 其代码行数很多, 可以拆成很多子模块,便于想要单独使用某个功能对应的子模块

包目录__init__.py是在包第一次导入的时候就会执行,内容可以为空,也可以是用于该包初始化工作的代码,最好不要删除它
导入子模块一定会加载父模块,但是导入父模块一定不会导入子模块

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

模块也是封装,如同函数和类一样.

模块就是命名空间,其内部的顶层标识符都是他的属性,可以通过__dict__或者dir(module)查看

包也是模块,包是特殊的模块,是一种组织方式, 它包含__path__属性

  • 问题
    from json import encoder之后,json.dump函数用不了,为什么?
    import json.encoder之后呢?json.dump函数能用吗?

  • 原因是

    from json import encoder之后,当前名词空间没有json,但是json模块已经加载过了,没有json的引用,无法使用dump函数。
    import json.encoder也加载json模块,但是当前名词空间有json,因此可以调用json.dump。

绝对导入,相对导入

绝对导入

在mport语句或者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 … 三点表示上上一级

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

相对导入,更像是目录操作。

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

访问控制

下划线开头的模块名

_ 或者 __ 开头的模块是否能够被导入呢?

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

  • 普通变量、保护变量、私有变量、特殊变量,都没有被隐藏,也就是说模块内没有私有的变量,在模块中定义不做特殊处理。
  • 依然可以使用from语句,访问所有变量

from … import * 和__all__

使用from … import * 导入

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

__my__ = 8
# test.py中
from xyz import* 
import sys print(sorted(sys.modules.keys())) 
print(dir()) 
print(locals()['A']) 
A = 55 
print(locals()['A']) # 思考这个A是谁的A了

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

使用__all__
__all__是一个列表,元素是字符串,每一个元素都是一个模块内的变量名

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

__my__ = 8
# test.py中
from xyz import* 
import sys print(sorted(sys.modules.keys())) 
print(dir()) 
# print(locals()['A']) 
print(local()['X'])
print(local()['Y'])
print(local()['_B'])
print(local()['__C'])

使用from xyz import *只能导入__all__列表中的名称

包 和子模块

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

总结

  • 一. 使用from xyz imort *导入
  1. 如果模块没有__all__.from xyz imort *只导入非下划线开头的该模块的变量,如果是包,子模块也不会导入,除非在__all__中设置,或者__init__.py中导入他们
  2. 如果模块中有__all__,from xyz imort *只导入__all__列表中指定的名称,哪怕是下划线开头后者子模块
  3. from xyz imort *方式简单,但是副作用是导入大量不需要的变量,甚至造成名称冲突, __ all__就是为了阻止其导入过多的模块变量,因此,编写模块时,应该尽量加入__all__
  • 二. form module import name1, name2 导入
    这种方式的导入是明确的,哪怕是导入子模块,或者导入下划线开头的名称
    程序员可以有控制的导入名称和其对应的对象

模块变量的修改

  1. 模块对象是同一个,因此模块的变量也是同一个,对模块变量的修改,会影响所有使用者。
  2. 除非万不得已,或明确知道自己在做什么,否则不要修改模块的变量。
  3. 前面学习过的猴子补丁,也可以通过打补丁的方式,修改模块的变量、类、函数等内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值