Python 模块和包

Python 模块和包

【一】知识补充

【1】相对路径

  • 相对路径就是相对于当前目录的路径。

  • 常见的相对路径标记包括:

    • ”.“:表示当前目录
    • ”…“:表示上一层目录
  • 假设你当前的目录结果如下:

project/
|-- data/
|   |-- input.py
|   |-- print.py
|   |-- test.py
|-- main/
|   |-- main.py
  • 当你在test.py文件里想打开input.py文件
input_path = ".input.py"
  • 当你在main.py文件里想打开input.py文件
input_path = "..\data\input.py"

【2】绝对路径

  • 绝对路径就是文件或者目录在文件系统中的完整路径,不依赖于当前工作目录

  • 在windows系统中,绝对路径以盘符开始

    • abs_path = "C:\Users\Username\Documents\file.txt"
      
  • 在Unix/Linux系统中,绝对路径以根目录(“/”)开始

    • abs_path = "/home/username/documents/file.txt"
      

【二】模块

【1】定义

  • 在Python中,一个Py文件就是一个模块
  • 模块的名称就是文件的名称(不包括文件扩展名)

【2】分类

  • 内置模块:python解释器自带的模块,直接使用
  • 第三方模块:python社区大佬们开源提供的模块,需要下载使用
  • 自定义模块:自己编写的脚本文件

【3】优点

  • 提高开发效率、具有可维护性、可重用性、可读性等

【4】模块的使用

(1)导入模块的方式
# 方式一: import module_name, ...
# 方式二: from module_file import module1, module2, ...
# 方式三: from module_file import *
# 方式四: from module_file import module1 as m1
  • 方式一:直接导入,指名道姓的导入可以避免名字相同冲突,但是有点繁琐
  • 方式二:详细导入,可以直接导入模块内的变量名,易产生名字冲突
  • 方式三:一次性导入模块下所有内容,容易产生命名冲突问题
# 可以使用__all__变量(一般是列表)限制导入的内容 
# 在模块的最顶层
# _开头的名字不会被*导入
# model_file.py

__all__ = ["name", "func1"]
name = "tom"
age = "18"
def func1():
    ...
def func2():
    ...
from model_file import *

name 		# 可以使用
age			# 不可以使用
func1()		# 可以使用
func2()		# 不可以使用
  • 方式四: 起别名,简化模块名,可以避免名字冲突
(2)导入模块的过程
  • 第一次导入模块
# 1. 产生一个新的名称空间(名字是被导入的模块名)
# 2. 执行被导入模块文件代码, 将产生名字存放在的新的名称空间中
# 3. 将模块名称空间的名字添加到当前执行文件所在的名称空间
  • 再次导入
因为程序还没有结束,第一次导入的模块已经在内存中,导入产生的名称空间也在内存中
所以在此被导入时,不会在运行模块文件的程序代码,而是从内存中直接绑定引用
(3)import 和from import 的区别
# 通过导入的模块名,引用模块内的内容
import module1 
module.name
module.func()

# 只能访问module模块,不能访问module_file内其他的名字
from module_file import module
(4)循环导入
  • 循环导入问题指的是在一个模块加载/导入的过程中导入另外一个模块,而在另外一个模块中又返回来导入第一个模块中的名字,由于第一个模块尚未加载完毕,所以引用失败、抛出异常。

  • 究其根源就是在python中,同一个模块只会在第一次导入时执行其内部代码,再次导入该模块时,即便是该模块尚未完全加载完毕也不会去重复执行内部代码,只会在内存中找该模块,找不到就报错了。

  • 示例

# m1.py文件
print("正在导入m1")
from m2 import y
x = "m1"

# m2.py文件
print("正在导入m2")
from m1 import x
y = "m2"

# run.py文件
import m1
  • 分析 (执行run.py文件)
# 执行结果
"""
正在导入m1
正在导入m2
报错
"""

# 分析
"""
import m1 			   # 导入模块m1
print("正在导入m1")		# 打印"正在导入m1"
from m2 import y		# 导入模块m2
print("正在导入m2")		# 打印"正在导入m2"
from m1 import x		# 导入模块m1        此时m1已经导入过了,不会重新导入,直接去m1拿x,拿不到就报错了
"""
  • 分析 (执行m1.py文件)(执行并不是导入)
# 执行结果
"""
正在导入m1
正在导入m2
正在导入m1
报错
"""

# 分析
"""
print("正在导入m1")		# 打印"正在导入m1"
from m2 import y		# 导入模块m2
print("正在导入m2")		# 打印"正在导入m2"
from m1 import x		# 导入模块m1 
print("正在导入m1")		# 打印"正在导入m1"
from m2 import y		# 导入模块m2        此时m2已经导入过了,不会重新导入,直接去m2拿y,拿不到就报错了
"""
  • 解决方案(方案一)(导入语句放在后)
# m1.py文件
print("正在导入m1")
x = "m1"
from m2 import y

# m2.py文件
print("正在导入m2")
y = "m2"
from m1 import x

# run.py文件
import m1
print(m1.x)
print(m1.y)
  • 解决方案(方案二)( 导入语句放在函数里)
# m1.py文件
print('正在导入m1')
def f1():
    from m2 import y
    print(x,y)
x = 'm1'

# m2.py文件
print('正在导入m2')
def f2():
    from m1 import x
    print(x,y)
y = 'm2'

# run.py文件
import m1
m1.f1()
  • 循环导入问题大多数情况是因为程序设计失误导致,上述解决方案也只是在烂设计之上的无奈之举,在我们的程序中应该尽量避免出现循环/嵌套导入,如果多个模块确实都需要共享某些数据,可以将共享的数据集中存放到某一个地方,然后进行导入
(4)导入模块规范
  • 多次import导入比单次import多模块,更规范,可读性强
  • 不同类别导入模块顺序
    • python内置模块----第三方模块----自定义模块
    • 每一类模块之间一空行隔开
  • 通常在开头导入
(5)查找模块优先级
# 先后顺序
# 先内存----然后内置----最后sys.path路径

import sys
sys.path	# 是一个列表,存放各种文件夹的路径,当内存和内置不存在需要导入的模块时,会在sys.path中查找
sys.modules	# 是一个字典,存放当前内存中已经存在的模块信息,键时模块名,值时模块绝对路径
补充:py文件的两种用途
# 1. 当脚本文件被执行
# 2. 当模块被导入文件

# 区别:
# 当脚本执行时,产生的名称空间在程序运行结束后失效回收
# 当模块时,导入运行的文件产生的名称空间在引用计数为0时被回收

# 每个py文件都有一个__name__属性
# 当py文件被当脚本执行时, __name__ == "__main__"
# 当py文件被当模块导入时, __name__ == "模块名"

【三】包

【1】定义

  • 包(Package)是一种组织和管理模块的方式,它将相关的模块组织到一个目录结构中。
  • 一个包是一个包含一个特殊的文件 __init__.py 的目录,该文件可以为空,也可以包含Python代码。这个文件告诉Python解释器该目录应被视为一个包,并且可以包含子模块或子包。
  • 包是一个文件夹,不能是普通模块那样被执行代码,所以给包提供了一个__init__.py文件,导入包就会执行__init__.py文件,这也是__init__.py文件存在的意义。

【2】包的目录结构

my_package/
|-- __init__.py
|-- module1.py
|-- module2.py
|-- subpackage/
|   |-- __init__.py
|   |-- module3.py
|   |-- module4.py
  • my_package 是一个包,因为它包含了 __init__.py 文件。
  • module1.pymodule2.pymy_package 的直接模块。
  • subpackage 是一个子包,因为它包含了 __init__.py 文件。
  • module3.pymodule4.pysubpackage 的模块。

【3】使用包

(1)直接使用(只能到模块名)
import 包名.包名.模块名
(2)详细使用包(还可以到模块内的名字)
from 包名.模块名 import 模块中的方法
(3)__init__.py文件
  • __init__.py为空时
# 详细导入
from 包名.模块名 import 模块中的方法
  • __init__.py为不为空时,有注册模块的方法或者变量名
# 详细导入
from 包名 import 模块中的方法
  • 使用包是通过导入的方式使用,使用包和使用普通模块的方式一样,首次导入包也会发生三件事
# 1. 执行包的__init__.py文件
# 2. 产生一个名称空间,即__init__.py的名称空间,用于存放__init__.py文件执行过程中产生的名字
# 3. 在当前执行文件所在的名称空间中放一个包的名字,该名字指向__init__.py的名称空间

# import 包 :会将包的__init__.py文件执行一遍,得到的名字都是该__init__.py文件中存在的名字。
# 也就是说包里面的子包或者子模块如果不在__init__.py中,是不会被执行的,就不会导入

【4】创建设计包

  • 方便自己组织管理程序
  • 给包的使用者提供便利
# 包结构文件
my_package/
|-- __init__.py
|-- module1.py
|-- subpackage/
|   |-- __init__.py
|   |-- module2.py
(1)包内模块的绝对导入
  • 绝对导入:以顶级包作为起始位置
# module1.py
from my_package.subpackage import module2
print("Executing module1.py")

# module2.py
from my_package import module1
print("Executing module2.py")
(2)包内模块的相对导入
  • 使用点号 . 来表示相对于当前模块所在的包的路径
  • 使用..代表当前目录的上一级目录
  • 不能直接运行模块文件,需要在模块根目录的终端中运行python - m mypackage.module1
# module1.py
from .subpackage import module2
print("Executing module1.py")

# module2.py
from .. import module1
print("Executing module2.py")

  • 包内模块相互导入只能使用from … import … ,import … 不能使用,语法错误
补充:包的设计可以在文件顶部添加__all__来控制使用者from包import*

你可能感兴趣的:(python,开发语言)