【Python零基础入门笔记 | 11】函数、类、模块和包如何构建四级模块化体系

这是机器未来的第19篇文章

写在前面:

  • 博客简介:专注AIoT领域,追逐未来时代的脉搏,记录路途中的技术成长!
  • 专栏简介:本专栏的核心就是:快!快!快!2周快速拿下Python,具备项目开发能力,为机器学习和深度学习做准备。
  • 面向人群:零基础编程爱好者
  • 专栏计划:接下来会逐步发布跨入人工智能的系列博文,敬请期待
    • Python零基础快速入门系列
    • 快速入门Python数据科学系列
    • 人工智能开发环境搭建系列
    • 机器学习系列
    • 物体检测快速入门系列
    • 自动驾驶模拟器AirSim入门系列
    • 自动驾驶物体检测系列
  • 原文首发地址:https://blog.csdn.net/RobotFutures/article/details/125353783

【Python零基础入门笔记 | 11】函数、类、模块和包如何构建四级模块化体系_第1张图片

1. 概述

前面已经学习过函数和类了,今天我们继续学习模块化的另外2种封装方式:模块和包。函数、类、模块和包构成了模块化四级封装体系。

2. 什么是模块?什么是包?

模块其实就是python源代码文件,以.py后缀结尾,而包就是文件夹,其内包含.py源代码和__init__.py文件。他们的层级结构如下:

├─package
│  ├─module1.py
│  │  ├─class1
│  │  │  ├─function1
│  │  │  └─function2
│  │  ├─class2
│  │  │  ├─function11
│  │  │  └─function12
│  ├─module2.py
│  └─__init__.py
└─package2
    ├─module21.py
    │  └─class21
    │      └─function21
    └─__init__.py
  • 功能高度相关或类似的函数封装在一个类中;
  • 一个或多个功能高度相关或类似的类存放到同一个源代码文件中
  • 一个或多个相关的源代码存放在包(文件夹)中

以上就构成了模块化的四级封装体系。

以python三剑客matplotlib绘图库为例来看看是不是这样在多层封装体系,我们先来写一段代码:

# 引入pylot模块
from matplotlib import pyplot as plt

# 引入numpy
import numpy as np


X = np.linspace(start=-5, stop=5, num=50)
y = X**2 + 6

plt.plot(X, y)
plt.show()

【Python零基础入门笔记 | 11】函数、类、模块和包如何构建四级模块化体系_第2张图片

要实现绘制曲线的功能,需要使用到matplotlib包pyplot模块下的plot和show函数,我们先来看一下他在文件中的组织形式:

  • matplotlib是一个文件夹,是python中的一个包,它应该包含一个__init__.py文件,如图所示:
    【Python零基础入门笔记 | 11】函数、类、模块和包如何构建四级模块化体系_第3张图片

  • pyplot是一个模块,在文件系统中在体现是一个文件,我们找到它
    【Python零基础入门笔记 | 11】函数、类、模块和包如何构建四级模块化体系_第4张图片

  • plt.plot(X, y)中的plot函数是pyplot模块中在一个函数,看它在不在?

小技巧:在vscode环境下,将鼠标放在函数名上,按住CTRL键可以直接跳转到函数定义在位置。

跳转后,发现了plot函数在身影,的确在pyplot模块文件中。
【Python零基础入门笔记 | 11】函数、类、模块和包如何构建四级模块化体系_第5张图片

3. 实现一个自定义功能模块库

我们实现一个功能的模块化封装:

  • 定义功能模块库
    • 首先创建一个包
    • 在包里创建一个__init__.py文件,并按照规则填充它
    • 创建一个模块.py文件
    • 在模块文件中创建类class
    • 在类中实现方法
  • 调用功能模块库
    • 引用相关的模块或模块中的类、函数、变量
    • 调用

以上篇文章中在吃饭例子为例
【Python零基础入门笔记 | 10】类的设计哲学:自然法则的具现, 将它划分如下:

Project 
|-- restaurant_industry   # 餐馆
    |-- __init__.py     # 包初始化文件,更改后缀
    |-- restaurant.py
    |-- customer.py     # 食客
|-- main.py # 主程序

3.1 定义功能模块

创建文件夹restaurant_industry和__init__.py, 那么__init__.py到底要写什么,以及有什么用呢?

功能:

_init_.py用来标识当前文件夹为Python包(Python3.2以后版本无需__init__.py也可以)

用法:

  • _init_.py可以为空,仅告诉解释器当前文件夹为Python包即可

  • 做一些预加载工作

    执行import package时,package文件夹下的__init__.py会自动执行,基于加载包时自动加载特性,init.py还可以用来做一些预加载工作,例如模块的导入等

3.1.1 用法一:标识包

下面举例进行说明,创建如下的文件结构,内容及代码如下:

Project 
|-- restaurant_industry   # 餐馆
    |-- __init__.py     # 包初始化文件,更改后缀
    |-- restaurant.py
    |-- customer.py     # 食客
|--package2
    |--__init__.py
|-- main.py # 主程序
# restaurant_industry/__init__.py
print("restaurant_industry __init__.py load!")
# package2/__init__.py
print("package2 __init__.py load!")
# main.py

import restaurant_industry
import package2

# 打印文件中加载了哪些内容,dir()函数输入为空时,表明是当前文件
print(dir())

restaurant_industry __init__.py load!
package2 __init__.py load!
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'package2', 'restaurant_industry']

可以看到在dir()的输出中restaurant_industry和package2这两个文件夹已经被识别为包了,可以通过import引入,在引用时就自动执行了包下的_init_.py文件。

Python3.2以后版本无需_init_.py也可以识别为包,测试一下:将_init_.py后缀修改为.pyi,再次执行

Project 
|-- restaurant_industry   # 餐馆
    |-- __init__.pyi     # 包初始化文件,更改后缀
    |-- restaurant.py
    |-- customer.py     # 食客
|--package2
    |--__init__.pyi
|-- main.py # 主程序
# main.py

import restaurant_industry
import package2

# 打印文件中加载了哪些内容,dir()函数输入为空时,表明是当前文件
print(dir())
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'package2', 'restaurant_industry']

发现没有报错,可以执行,dir()的输出中同样包含了package2和restaurant_industry,但是不再打印_init_.py中的打印信息了。说明Python3.2以后版本无需_init_.py也可以识别为包了。

3.1.2 用法二:预加载相关的模块

在_init_.py中可以使用一个特殊变量__all__来配合from module import *预加载模糊引入的模块。

# restaurant_industry/__init__.py

#模糊引入时,指定加载的模块,如果不指定则不加载任何模块或模块中的内容,测试Python版本3.7.0
__all__ = ['restaurant']

print("restaurant_industry __init__.py load!")
# main.py
from restaurant_industry import *
import package2

print(dir())

运行python main.py的输出结果为

restaurant_industry __init__.py load!
package2 __init__.py load!
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'package2', 'restaurant']  

可以看到restaurant模块被引用成功了,customer未引用,在代码中可以直接调用restaurant模块中的函数、类、变量了,而不需要import restaurant。

注意:__all__变量配合from module import *代码使用时才生效

3.2 调用功能模块库

引用模块有以下几种形式

3.2.1 引用模块

# 方式一:直接引用模块名
import module
# 方式二:通过包名.模块名引用
import package.module
# 方式三:通过from [package] import [module]引用,等价方式二
from package import module

不可以引用包,会提示模块无相关属性,类似这样

AttributeError: module 'restaurant_industry' has no attribute 'Restaurant'

3.2.2 引用模块中的变量、类或函数

语法:

from [module] import [class1, class2, func1, varable1,...]

详解:

  • module可以直接是模块名,也可以是package.module,支持多级关系
  • class1, class2, func1, varable1,…, 为模块内的内容(类、函数、变量),可以为一项,也可以为多项
  • from后面只可以接模块,不可以接单独的包,必须定位到模块

3.2.3 实例演示

现在,根据上一篇博文中的吃饭例子,我们将相关代码分别封装到customer和restaurant模块中,代码如下:

# 文件位置:Project\restaurant_industry\customer.py
class Customer:
    """客人
    """
    def __init__(self, order_id):
        self.order_id = order_id
        self.amount = 0

    def order(self, restaurant, dish_id):
        restaurant.order(self.order_id, dish_id)

# 用于演示函数引用
def func1():
    print("客人对餐品很满意,五星点赞!")
    
# 用于演示变量引用
customer_list = ['c', 'u', 's', 't', 'o', 'm', 'e', 'r']
# 文件位置:Project\restaurant_industry\restaurant.py
class Dish:
    """
    菜品
    """
    def __init__(self, id, name, price):
        self.id = id
        self.name = name
        self.price = price

class Restaurant:
    """
    菜单
    """
    def __init__(self):
        self.menu = []
        self.ordered_menu = {}
    
    def add(self, id, name, price):
        """添加新菜品
        """
        self.menu.append(Dish(id, name, price))

    def display_menu(self):
        """展示菜谱
        """
        for item in self.menu:
            print(f"{item.id}\t{item.name}\t\t{item.price}")

    def order(self, order_id, dish_id):
        # ord = {item for item in self.ordered_menu.keys if item == order_id}
        ord = self.ordered_menu.get(order_id, [])
        if ord:     #  不为空,说明订单已经产生
            ord.append(dish_id)
        else:       # 为空,说明是新订单
            self.ordered_menu[order_id] = list([dish_id])
 
    def check(self, order_id):
        amount = 0
        # self.ordered_menu[order_id]直接获得客户的已选菜单,然后用列表推导式获得结算价格
        checklist = [dish.price for dish in self.menu if dish.id in self.ordered_menu[order_id]]
        for x in checklist:
            amount += x      

        return amount 
# main.py

# 从模块customer.py中引用Customer类
from restaurant_industry.customer import Customer
# 直接引用restaurant,并使用as将restaurant_industry.restaurant定义别名为restaurant,避免每次调用时都要加上restaurant_industry.restaurant.前缀,用as定义别名后,可以直接使用rest.前缀即可。
import restaurant_industry.restaurant as rest

# 模块customer.py中引用func1函数,customer_list变量
from restaurant_industry.customer import func1, customer_list

print(dir())

# 因为仅引用了restaurant模块,因此需要使用【模块.类】的访问方式
restaurant = rest.Restaurant()

# 添加新菜品
restaurant.add(1, "青椒肉丝", 22)
restaurant.add(2, "皮蛋豆腐", 16)
restaurant.add(3, "新疆大盘鸡", 89)
restaurant.add(4, "虎皮青椒", 22)

# 展示菜单
restaurant.display_menu()

# 客人c1点餐
# 因为已经从模块customer.py中引用了Customer类,因此无需使用【模块.类】的访问方式
c1 = Customer(order_id = 1)
c1.order(restaurant=restaurant, dish_id = 1)     # c1点了青椒肉丝
c1.order(restaurant=restaurant, dish_id = 2)     # c1点了皮蛋豆腐

# 客人c2点餐
c2 = Customer(order_id = 2)
c2.order(restaurant=restaurant, dish_id = 2)     # c2点了皮蛋豆腐
c2.order(restaurant=restaurant, dish_id = 3)     # c2点了新疆大盘鸡

# 客人c1结账
c1.amount = restaurant.check(c1.order_id)
# 客人c2结账
c2.amount = restaurant.check(c2.order_id)

print(f"客人c1消费了{c1.amount}元")
print(f"客人c2消费了{c2.amount}元")

# 因为已经从模块customer.py中引用了func1函数,因此无需使用【模块.函数】的访问方式
func1()
restaurant_industry __init__.py load!
# dir()的输出
['Customer', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'customer_list', 'func1', 'rest']
1       青椒肉丝                22
2       皮蛋豆腐                16
3       新疆大盘鸡              89
4       虎皮青椒                22
客人c1消费了38元
客人c2消费了105元
客人对餐品很满意,五星点赞!

从代码可以了解到以下知识点:

  • 从代码dir()的输出中可以看到,customer_list, func1, rest,这三项已经被引用到文件中来了,rest是restaurant_industry.restaurant的别名,可以看到都引用成功了。
  • 如果已经将类引用到文件中,那么在文件中可以直接使用类名,而无需使用模块前缀了。
  • 如果没有将模块中的类、变量、函数引用到文件中,那么需要添加模块前缀才能使用,就像restaurant = rest.Restaurant()
  • 如果已经将模块中的类、变量、函数引用到文件中,因此使用时无需使用【模块.类】的访问方式,就像c1 = Customer(order_id = 1)、func1()等

4. 总结

到这里,自定义一个功能模块库的例子就讲解完毕了,通过定义包、模块、类、方法四级体系,搭建了完整的模块化封装体系。模块化也讲了好几期了,今天为模块化画上了一个圆满的句号。模块化的优势就不多说了,使用过程中自然就有体会了。

《Python零基础快速入门系列》快速导航:

  • 【Python零基础入门笔记 | 01】 人工智能序章:开发环境搭建Anaconda+VsCode+JupyterNotebook(零基础启动)
  • 【Python零基础入门笔记 | 02】一文快速掌握Python基础语法
  • 【Python零基础入门笔记 | 03】AI数据容器底层核心之Python列表
  • 【Python零基础入门笔记 | 04】为什么内存中最多只有一个“Love“?一文读懂Python内存存储机制
  • 【Python零基础入门笔记 | 05】Python只读数据容器:列表List的兄弟,元组tuple
  • 【Python零基础入门笔记 | 06】字符串、列表、元组原来是一伙的?快看序列Sequence
  • 【Python零基础入门笔记 | 07】成双成对之Python数据容器字典
  • 【Python零基础入门笔记 | 08】无序、不重复、元素只读,Python数据容器之集合
  • 【Python零基础入门笔记 | 09】高级程序员绝世心法——模块化之函数封装
  • 【Python零基础入门笔记 | 10】类的设计哲学:自然法则的具现

【Python零基础入门笔记 | 11】函数、类、模块和包如何构建四级模块化体系_第6张图片

你可能感兴趣的:(Python零基础快速入门系列,python,人工智能,开发语言,Python零基础,Python笔记)