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

本文介绍了Python模块和包的基础概念,包括模块定义、包的结构以及如何创建、导入和使用模块。通过实例展示了如何实现功能模块化,如顾客与餐厅类的封装,以及如何通过`from`语句灵活引用模块内容。

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

在这里插入图片描述

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

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()

png

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

  • matplotlib是一个文件夹,是python中的一个包,它应该包含一个__init__.py文件,如图所示:

  • pyplot是一个模块,在文件系统中在体现是一个文件,我们找到它

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

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

跳转后,发现了plot函数在身影,的确在pyplot模块文件中。

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零基础快速入门系列》快速导航:

混合动力汽车(HEV)模型的Simscape模型(Matlab代码、Simulink仿真实现)内容概要:本文档介绍了一个混合动力汽车(HEV)的Simscape模型,该模型通过Matlab代码Simulink仿真工具实现,旨在对混合动力汽车的动力系统进行建模与仿真分析。模型涵盖了发动机、电机、电池、传动系统等关键部件,能够模拟车辆在不同工况下的能量流动与控制策略,适用于动力系统设计、能耗优化及控制算法验证等研究方向。文档还提及该资源属于一个涵盖多个科研领域的MATLAB仿真资源包,涉及电力系统、机器学习、路径规划、信号处理等多个技术方向,配套提供网盘下载链接,便于用户获取完整资源。; 适合人群:具备Matlab/Simulink使用基础的高校研究生、科研人员及从事新能源汽车系统仿真的工程技术人员。; 使用场景及目标:①开展混合动力汽车能量管理策略的研究与仿真验证;②学习基于Simscape的物理系统建模方法;③作为教学案例用于车辆工程或自动化相关课程的实践环节;④与其他优化算法(如智能优化、强化学习)结合,实现控制策略的优化设计。; 阅读建议:建议使用者先熟悉Matlab/Simulink及Simscape基础操作,结合文档中的模型结构逐步理解各模块功能,可在此基础上修改参数或替换控制算法以满足具体研究需求,同时推荐访问提供的网盘链接获取完整代码与示例文件以便深入学习与调试。
评论 18
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

机器未来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值