python模块(module)

发布时间 2023-03-22 19:08:11作者: ThankCAT

模块化(module)程序设计理念

模块和包概念的进化史

“量变引起质变”是哲学中一个重要的理论。量变为什么会引起质变呢?本质上理解,随着数量的增加,管理方式会发生本质的变化;旧的管理方式完全不适合,必须采用新的管理方式。

程序越来越复杂,语句多了,怎么管理?很自然的,我们会将实现同一个功能的语句封装到函数中,统一管理和调用,于是函数诞生了。

程序更加复杂,函数和变量多了,怎么管理?同样的思路,“物以类聚”,我们将同一类型对象的“数据和行为”,也就是“变量和函数”,放到一起统一管理和调用,于是“类和对象”诞生了。

程序继续复杂,函数和类更加多了,怎么办?好,我们将实现类似功能的函数和类统统放到一个模块中,于是“模块”诞生了。

程序还要复杂,模块多了,怎么办? 于是,我们将实现类似功能的模块放到一起,于是“包”就诞生了。

标准库模块(standard library)

与函数类似,模块也分为标准库模块和用户自定义模块。

Python 标准库提供了操作系统功能、网络通信、文本处理、文件处理、数学运算等基本的功能。比如:random(随机数)、math(数学运算)、time(时间处理)、file(文件处理)、os(和操作系统交互)、sys(和解释器交互)等。

为什么需要模块化编程

模块(module)对应于 Python 源代码文件(.py 文件)。模块中可以定义变量、函数、类、普通语句。 这样,我们可以将一个 Python 程序分解成多个模块,便于后期的重复应用

模块化编程(Modular Programming)将一个任务分解成多个模块。每个模块就像一个积木一样,便于后期的反复使用、反复搭建

模块化编程的流

模块化编程的一般流程:

  1. 设计 API,进行功能描述。
  2. 编码实现 API 中描述的功能。
  3. 在模块中编写测试代码,并消除全局代码。
  4. 使用私有函数实现不被外部客户端调用的模块函数。

模块的 API 和功能描述要点

API(Application Programming Interface 应用程序编程接口)是用于描述模块中提供的函数和类的功能描述和使用方式描述。

模块化编程中,首先设计的就是模块的 API(即要实现的功能描述),然后开始编码实现 API 中描述的功能。最后,在其他模块中导入本模块进行调用。

我们可以通过help(模块名)查看模块的API。一般使用时先导入模块 然后通过help函数查看。

【示例】导入 math 模块,并通过 help()查看 math 模块的 API

import math

help(math)

""" Help on built-in module math:

NAME
    math

DESCRIPTION
    This module provides access to the mathematical functions
    defined by the C standard.

FUNCTIONS
    acos(x, /)
        Return the arc cosine (measured in radians) of x.

        The result is between 0 and pi.

    acosh(x, /)
        Return the inverse hyperbolic cosine of x.

    asin(x, /)
        Return the arc sine (measured in radians) of x.

        The result is between -pi/2 and pi/2.
-- More  -- """

【示例】设计计算薪水模块的 AP

"""
用于计算公司员工信息的模块
"""
company = "WINCET"


def yesrsalary(monthsalary):
    """通过传入的月薪,计算年薪"""
    return monthsalary * 12


def daysalary(monthsalary):
    """通过传入的月薪,计算日新,法定月工作日22.5天"""
    return monthsalary / 22.5

我们可以通过__doc__可以获得模块的文档字符串的内容。

import salary

print(salary.__doc__)
print(salary.company)
print(salary.daysalary.__doc__)
print(salary.yesrsalary.__doc__)
print(salary.daysalary(12000))
print(salary.yesrsalary(12000))

""" 
用于计算公司员工信息的模块

WINCET
通过传入的月薪,计算日新,法定月工作日22.5天
通过传入的月薪,计算年薪
533.3333333333334
144000 
"""

模块的创建和测试代码

每个模块都有一个名称,通过特殊变量__name__可以获取模块的名称。在正常情况下,模块名字对应源文件名。 仅有一个例外,就是当一个模块被作为程序入口时(主程序、交互式提示符下),它的__name__的值为“__main__”。我们可以根据这个特点,将模块源代码文件中的测试代码进行独立的处理。例如:

import math
math.__name__  #输出'math'

【示例】__name__ == "__main__独立处理模块的测试代码

"""
用于计算公司员工信息的模块
"""
company = "WINCET"


def yesrsalary(monthsalary):
    """通过传入的月薪,计算年薪"""
    return monthsalary * 12


def daysalary(monthsalary):
    """通过传入的月薪,计算日新,法定月工作日22.5天"""
    return monthsalary / 22.5


if __name__ == "__main__":
    print(yesrsalary(12000))
    print((daysalary(12000)))


""" 
144000
533.3333333333334
"""

模块的导入

模块化设计的好处之一就是“代码复用性高”。写好的模块可以被反复调用,重复使用。模块的导入就是“在本模块中使用其他模块”。

import 语句导

mport 语句的基本语法格式如下:
import 模块名 #导入一个模块
import 模块 1,模块 2… #导入多个模块
import 模块名 as 模块别名 #导入模块并使用新名字

mport 加载的模块分为四个通用类别:

  1. 使用 python 编写的代码(.py 文件);
  2. 已被编译为共享库或 DLL 的 C 或 C++扩展;
  3. 包好一组模块的包
  4. 使用 C 编写并链接到 python 解释器的内置模块;

我们一般通过 import 语句实现模块的导入和使用,import 本质上是使用了内置函数
__import__()

当我们通过 import 导入一个模块时,python 解释器进行执行,最终会生成一个对象,这个对象就代表了被加载的模块

import math
print(id(math))
print(type(math))
print(math.pi) #通过 math.成员名来访问模块中的成
"""
31840800
<class 'module'>
"""

由上,我们可以看到 math 模块被加载后,实际会生成一个 module 类的对象,该对象被math 变量引用。我们可以通过 math 变量引用模块中所有的内容。

我们通过 import 导入多个模块,本质上也是生成多个 module 类的对象而已。

有时候,我们也需要给模块起个别名,本质上,这个别名仅仅是新创建一个变量引用加载的模块对象而已。

import math as m
#import math
#m = math
print(m.sqrt(4)) #开方运算

from…import 导入

Python 中可以使用 from…import 导入模块中的成员。基本语法格式如下:

from 模块名 import 成员 1,成员 2

如果希望导入一个模块中的所有成员,则可以采用如下方式:

from 模块名 import *

【注】尽量避免“from 模块名 import ”这种写法。 它表示导入模块中所有的不 是以下划线(_)开头的名字都导入到当前位置。 但你不知道你导入什么名字,很有可能 会覆盖掉你之前已经定义的名字.而且可读性极其的差。一般生产环境中尽量避免使用, 学习时没有关系。

【示例】使用 from…import 导入模块指定的成员

from math import pi,sin
print(sin(pi/2)) #输出 1.0

import 语句和 from...import 语句的区别

import 导入的是模块。

from...import 导入的是模块中的一个函数/一个类。

如果进行类比的话,import 导入的是“文件”,我们要使用该“文件”下的内容,必 须前加“文件名称”。from...import 导入的是文件下的“内容”,我们直接使用这 些“内容”即可,前面再也不需要加“文件名称”了

我们自定义一个模块 calculator.py:

"""一个实现四则运算的计算器""" 
def add(a,b):
	return a+b

def minus(a,b):
	return a-b

class MyNum():
	def print123(self):
		print(123)

我们在另一个模块 test.py 测试:

import calculator
a = calculator.add(30,40)
# add(100,200) #不加模块名无法识别
print(a)
from calculator import *
a = add(100,200) #无需模块名,可以直接引用里面的函数/类
print(a)
b = MyNum()
b.print123()

包 package 的使用

包(package)的概念和结构

当一个项目中有很多个模块时,需要再进行组织。我们将功能类似的模块放到一起, 形成了“包”。本质上,“包”就是一个必须有__init__.py 的文件夹。典型结构如下:

包下面可以包含“模块(module)”,也可以再包含“子包(subpackage)”。就像文件 夹下面可以有文件,也可以有子文件夹一样。

上图中,a 是上层的包,下面有一个子包:aa。可以看到每个包里面都有__init__.py 文件。

导入包操作和本质

上一节中的包结构,我们需要导入 module_AA.py。方式如下:

import a.aa.module_AA

在使用时,必须加完整名称来引用,比如:a.aa.module_AA.fun_AA()

from a.aa import module_AA

在使用时,直接可以使用模块名。 比如:module_AA.fun_AA()

from a.aa.module_AA import fun_AA # 直接导入函数

在使用时,直接可以使用函数名。 比如:fun_AA()

**【注】 **

  1. from package import item 这种语法中,item 可以是包、模块,也可以是函数、 类、变量。

  2. import item1.item2 这种语法中,item 必须是包或模块,不能是其他。

导入包的本质其实是“导入了包的__init__.py”文件。也就是说,”import pack1”意味 着执行了包 pack1 下面的__init__.py 文件。 这样,可以在__init__.py 中批量导入我们需要 的模块,而不再需要一个个导入。

__init__.py 的三个核心作用:

  1. 作为包的标识,不能删除。

  2. 用来实现模糊导入

  3. 导入包实质是执行__init__.py 文件,可以在__init__.py 文件中做这个包的初始化、以及 需要统一执行代码、批量导入。

【示例】测试包的__init__.py 文件本质用法

a 包下的__init__.py 文件内容:

import turtle
import math
print("导入 a 包")

b 包下的 module_B1.py 文件中导入 a 包,代码如下:

import a
print(a.math.pi)
"""
导入 a 包
3.141592653589793
"""

【注】如上测试我们可以看出 python 的设计者非常巧妙的通过__init__.py 文件将包转成了 模块的操作。因此,可以说“包的本质还是模块”。

用*导入包

import * 这样的语句理论上是希望文件系统找出包中所有的子模块,然后导入它们。 这可能会花长时间等。Python 解决方案是提供一个明确的包索引。

这个索引由 __init__.py 定义 __all__ 变量,该变量为一列表,如上例 a 包下的 __init__.py 中,可定义 __all__ = ["module_A","module_A2"] 这意味着, from sound.effects import * 会从对应的包中导入以上两个子模块;

__all__ = ["module_A", "module_B"]

包内引用

如果是子包内的引用,可以按相对位置引入子模块 以 aa 包下的 module_AA 中导入 a 包下内容为例:

from .. import module_A # ..表示上级目录 .表示同级目录
from . import module_A2 # .表示同级目录

sys.path 和模块搜索路径

当我们导入某个模块文件时, Python 解释器去哪里找这个文件呢?只有找到这个文 件才能读取、装载运行该模块文件。它一般按照如下路径寻找模块文件(按照顺序寻找,找 到即停不继续往下寻找):

  1. 置模块

  2. 当前目录

  3. 程序的主目录

  4. pythonpath 目录(如果已经设置了 pythonpath 环境变量)

  5. 标准链接库目录

  6. 第三方库目录(site-packages 目录)

  7. .pth 文件的内容(如果存在的话)

  8. sys.path.append()临时添加的目录

当任何一个 python 程序启动时,就将上面这些搜索路径(除内置模块以外的路径)进行收集, 放到 sys 模块的 path 属性中(sys.path)

使用 sys.path 查看和临时修改搜索路径

import sys
sys.path.append("d:/")
print(sys.path)

模块发布和安装

模块发布

当我们完成了某个模块开发后,可以将他对外发布,其他开发者也可以以“第三方扩展 库”的方式使用我们的模块。我们按照如下步骤即可实现模块的发布:

  1. 为模块文件创建如下结构的文件夹(一般,文件夹的名字和模块的名字一样):

  1. 在文件夹中创建一个名为『setup.py』的文件,内容如下:

    from distutils.core import setup
    
    setup(
        name="supermath",  # 对外模块名
        version="1.0",  # 版本号
        description="这是一个对发发布的测试模块,Test,Not use",  # 描述
        author="HoveyCHEN",  # 作者
        author_email="chenhao852@icloud.com",  # 作者邮箱
        py_modules=["supermath.demo1_add", "supermath.demo2_mul"]  # 要发布的模块
    )
    
  2. 建一个发布文件。通过终端,cd 到模块文件夹 c 下面,再键入命令:

    python setup.py sdist
    
  3. 执行结果

    # chenh @ HAO-PC-ROG in ~\OneDrive\Data Learn\Python 基础\课堂笔记\12\C [20:13:37]
    $ python setup.py sdist
    running sdist
    running egg_info
    creating supermath.egg-info
    writing supermath.egg-info\PKG-INFO
    writing dependency_links to supermath.egg-info\dependency_links.txt
    writing top-level names to supermath.egg-info\top_level.txt
    writing manifest file 'supermath.egg-info\SOURCES.txt'
    reading manifest file 'supermath.egg-info\SOURCES.txt'
    writing manifest file 'supermath.egg-info\SOURCES.txt'
    warning: sdist: standard file not found: should have one of README, README.rst, README.txt, README.md
    
    running check
    creating supermath-1.0
    creating supermath-1.0\supermath
    creating supermath-1.0\supermath.egg-info
    copying files to supermath-1.0...
    copying setup.py -> supermath-1.0
    copying supermath\__init__.py -> supermath-1.0\supermath
    copying supermath\demo1_add.py -> supermath-1.0\supermath
    copying supermath\demo2_mul.py -> supermath-1.0\supermath
    copying supermath.egg-info\PKG-INFO -> supermath-1.0\supermath.egg-info
    copying supermath.egg-info\SOURCES.txt -> supermath-1.0\supermath.egg-info
    copying supermath.egg-info\dependency_links.txt -> supermath-1.0\supermath.egg-info
    copying supermath.egg-info\top_level.txt -> supermath-1.0\supermath.egg-info
    Writing supermath-1.0\setup.cfg
    creating dist
    Creating tar archive
    removing 'supermath-1.0' (and everything under it)
    
  4. 执行完毕后,目录结构变为:(supermath-1.0.tar.gz 就是我们的包)

demo1_add.py

def add(a, b):
    """这是一个加法方法"""
    return a + b

demo2_mul.py

def mul(a, b):
    """这是一个乘法模块"""
    return a * b

模块安装

将发布安装到你的本地计算机上。仍在 cmd 命令行模式下操作,进 setup.py 所在目 录,键入命令:

python setup.py install

执行结果:

# chenh @ HAO-PC-ROG in ~\OneDrive\Data Learn\Python 基础\课堂笔记\12\C [20:19:29]
$ python setup.py install
running install
C:\Users\chenh\AppData\Local\Programs\Python\Python311\Lib\site-packages\setuptools\command\install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
  warnings.warn(
C:\Users\chenh\AppData\Local\Programs\Python\Python311\Lib\site-packages\setuptools\command\easy_install.py:144: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.
  warnings.warn(
running bdist_egg
running egg_info
writing supermath.egg-info\PKG-INFO
writing dependency_links to supermath.egg-info\dependency_links.txt
writing top-level names to supermath.egg-info\top_level.txt
reading manifest file 'supermath.egg-info\SOURCES.txt'
writing manifest file 'supermath.egg-info\SOURCES.txt'
installing library code to build\bdist.win-amd64\egg
running install_lib
running build_py
creating build
creating build\lib
creating build\lib\supermath
copying supermath\__init__.py -> build\lib\supermath
copying supermath\demo1_add.py -> build\lib\supermath
copying supermath\demo2_mul.py -> build\lib\supermath
creating build\bdist.win-amd64
creating build\bdist.win-amd64\egg
creating build\bdist.win-amd64\egg\supermath
copying build\lib\supermath\demo1_add.py -> build\bdist.win-amd64\egg\supermath
copying build\lib\supermath\demo2_mul.py -> build\bdist.win-amd64\egg\supermath
copying build\lib\supermath\__init__.py -> build\bdist.win-amd64\egg\supermath
byte-compiling build\bdist.win-amd64\egg\supermath\demo1_add.py to demo1_add.cpython-311.pyc
byte-compiling build\bdist.win-amd64\egg\supermath\demo2_mul.py to demo2_mul.cpython-311.pyc
byte-compiling build\bdist.win-amd64\egg\supermath\__init__.py to __init__.cpython-311.pyc
creating build\bdist.win-amd64\egg\EGG-INFO
copying supermath.egg-info\PKG-INFO -> build\bdist.win-amd64\egg\EGG-INFO
copying supermath.egg-info\SOURCES.txt -> build\bdist.win-amd64\egg\EGG-INFO
copying supermath.egg-info\dependency_links.txt -> build\bdist.win-amd64\egg\EGG-INFO
copying supermath.egg-info\top_level.txt -> build\bdist.win-amd64\egg\EGG-INFO
zip_safe flag not set; analyzing archive contents...
creating 'dist\supermath-1.0-py3.11.egg' and adding 'build\bdist.win-amd64\egg' to it
removing 'build\bdist.win-amd64\egg' (and everything under it)
Processing supermath-1.0-py3.11.egg
Copying supermath-1.0-py3.11.egg to c:\users\chenh\appdata\local\programs\python\python311\lib\site-packages
Adding supermath 1.0 to easy-install.pth file

Installed c:\users\chenh\appdata\local\programs\python\python311\lib\site-packages\supermath-1.0-py3.11.egg
Processing dependencies for supermath==1.0
Finished processing dependencies for supermath==1.0

【测试】测试我们安装的模块

from supermath.demo1_add import add
from supermath.demo2_mul import mul

print(add(10, 2))   # 12
print(mul(10, 2))   # 20
print(add.__doc__)  # 这是一个加法方法
print(mul.__doc__)  # 这是一个乘法模块