全网最适合入门的面向对象编程教程:46 Python函数方法与接口-函数与事件驱动框架

全网最适合入门的面向对象编程教程:46 Python 函数方法与接口-函数与事件驱动框架

摘要:

函数是 Python 中的一等公民,是一种可重用的代码块,用于封装特定的逻辑;事件驱动框架是一种编程模式,它将程序的控制流转移给外部事件,如用户输入、系统消息等,它事件驱动框架可以使用函数作为事件的处理逻辑。

原文链接:

FreakStudio的博客

往期推荐:

学嵌入式的你,还不会面向对象??!

全网最适合入门的面向对象编程教程:00 面向对象设计方法导论

全网最适合入门的面向对象编程教程:01 面向对象编程的基本概念

全网最适合入门的面向对象编程教程:02 类和对象的 Python 实现-使用 Python 创建类

全网最适合入门的面向对象编程教程:03 类和对象的 Python 实现-为自定义类添加属性

全网最适合入门的面向对象编程教程:04 类和对象的Python实现-为自定义类添加方法

全网最适合入门的面向对象编程教程:05 类和对象的Python实现-PyCharm代码标签

全网最适合入门的面向对象编程教程:06 类和对象的Python实现-自定义类的数据封装

全网最适合入门的面向对象编程教程:07 类和对象的Python实现-类型注解

全网最适合入门的面向对象编程教程:08 类和对象的Python实现-@property装饰器

全网最适合入门的面向对象编程教程:09 类和对象的Python实现-类之间的关系

全网最适合入门的面向对象编程教程:10 类和对象的Python实现-类的继承和里氏替换原则

全网最适合入门的面向对象编程教程:11 类和对象的Python实现-子类调用父类方法

全网最适合入门的面向对象编程教程:12 类和对象的Python实现-Python使用logging模块输出程序运行日志

全网最适合入门的面向对象编程教程:13 类和对象的Python实现-可视化阅读代码神器Sourcetrail的安装使用

全网最适合入门的面向对象编程教程:全网最适合入门的面向对象编程教程:14 类和对象的Python实现-类的静态方法和类方法

全网最适合入门的面向对象编程教程:15 类和对象的 Python 实现-__slots__魔法方法

全网最适合入门的面向对象编程教程:16 类和对象的Python实现-多态、方法重写与开闭原则

全网最适合入门的面向对象编程教程:17 类和对象的Python实现-鸭子类型与“file-like object“

全网最适合入门的面向对象编程教程:18 类和对象的Python实现-多重继承与PyQtGraph串口数据绘制曲线图

全网最适合入门的面向对象编程教程:19 类和对象的 Python 实现-使用 PyCharm 自动生成文件注释和函数注释

全网最适合入门的面向对象编程教程:20 类和对象的Python实现-组合关系的实现与CSV文件保存

全网最适合入门的面向对象编程教程:21 类和对象的Python实现-多文件的组织:模块module和包package

全网最适合入门的面向对象编程教程:22 类和对象的Python实现-异常和语法错误

全网最适合入门的面向对象编程教程:23 类和对象的Python实现-抛出异常

全网最适合入门的面向对象编程教程:24 类和对象的Python实现-异常的捕获与处理

全网最适合入门的面向对象编程教程:25 类和对象的Python实现-Python判断输入数据类型

全网最适合入门的面向对象编程教程:26 类和对象的Python实现-上下文管理器和with语句

全网最适合入门的面向对象编程教程:27 类和对象的Python实现-Python中异常层级与自定义异常类的实现

全网最适合入门的面向对象编程教程:28 类和对象的Python实现-Python编程原则、哲学和规范大汇总

全网最适合入门的面向对象编程教程:29 类和对象的Python实现-断言与防御性编程和help函数的使用

全网最适合入门的面向对象编程教程:30 Python的内置数据类型-object根类

全网最适合入门的面向对象编程教程:31 Python的内置数据类型-对象Object和类型Type

全网最适合入门的面向对象编程教程:32 Python的内置数据类型-类Class和实例Instance

全网最适合入门的面向对象编程教程:33 Python的内置数据类型-对象Object和类型Type的关系

全网最适合入门的面向对象编程教程:34 Python的内置数据类型-Python常用复合数据类型:元组和命名元组

全网最适合入门的面向对象编程教程:35 Python的内置数据类型-文档字符串和__doc__属性

全网最适合入门的面向对象编程教程:36 Python的内置数据类型-字典

全网最适合入门的面向对象编程教程:37 Python常用复合数据类型-列表和列表推导式

全网最适合入门的面向对象编程教程:38 Python常用复合数据类型-使用列表实现堆栈、队列和双端队列

全网最适合入门的面向对象编程教程:39 Python常用复合数据类型-集合

全网最适合入门的面向对象编程教程:40 Python常用复合数据类型-枚举和enum模块的使用

全网最适合入门的面向对象编程教程:41 Python常用复合数据类型-队列(FIFO、LIFO、优先级队列、双端队列和环形队列)

全网最适合入门的面向对象编程教程:42 Python常用复合数据类型-collections容器数据类型

全网最适合入门的面向对象编程教程:43 Python常用复合数据类型-扩展内置数据类型

全网最适合入门的面向对象编程教程:44 Python内置函数与魔法方法-重写内置类型的魔法方法

全网最适合入门的面向对象编程教程:45 Python实现常见数据结构-链表、树、哈希表、图和堆

更多精彩内容可看:

给你的 Python 加加速:一文速通 Python 并行计算

一文搞懂 CM3 单片机调试原理

肝了半个月,嵌入式技术栈大汇总出炉

电子计算机类比赛的“武林秘籍”

一个MicroPython的开源项目集锦:awesome-micropython,包含各个方面的Micropython工具库

Avnet ZUBoard 1CG开发板—深度学习新选择

SenseCraft 部署模型到Grove Vision AI V2图像处理模块

文档和代码获取:

可访问如下链接进行对文档下载:

https://github.com/leezisheng/Doc

本文档主要介绍如何使用 Python 进行面向对象编程,需要读者对 Python 语法和单片机开发具有基本了解。相比其他讲解 Python 面向对象编程的博客或书籍而言,本文档更加详细、侧重于嵌入式上位机应用,以上位机和下位机的常见串口数据收发、数据处理、动态图绘制等为应用实例,同时使用 Sourcetrail 代码软件对代码进行可视化阅读便于读者理解。

相关示例代码获取链接如下:https://github.com/leezisheng/Python-OOP-Demo

正文

函数

函数是一个命名的代码块,需要 0 个或多个输入参数,运行以后会返回输出值。在 Python 中,函数不仅是对象,也是第一类对象(First-Class Object),这是 Python 函数的一大特性。函数作为对象可以赋值给一个变量、可以作为元素添加到集合对象中、可作为参数值传递给其它函数,还可以当做函数的返回值,这些特性就是第一类对象所特有的。

简单来说,就是在 Python 中可以把函数像普通变量一样任意地使用,。包括赋值,以及作为其它函数的参数和返回值。这样就能很容易地写出高阶函数与闭包的代码。而在其他应用面向对象设计的语言中(如 C++)实现类似操作,需要借助函数指针进行实现。

在许多情况下,我们需要将函数作为参数传递到另一个函数中。最典型的例子就是事件驱动编程模式,所谓事件驱动编程模式不同于原先依次执行每个函数的编程模式,其基本的处理思路是预先设计一个事件循环所形成的程序,这个事件循环程序不断地检查目前要处理的信息,根据要处理的信息执行一个触发函数进行必要的处理。其中这个外部信息可能来自一个目录夹中的文件,可能来自键盘或鼠标的动作,或者是一个时间事件。

从事件角度说,事件驱动程序的基本结构是由一个事件收集器、一个事件发送器和一个事件处理器组成。事件收集器专门负责收集所有事件,包括来自用户的(如鼠标、键盘事件等)、来自硬件的(如时钟事件等)和来自软件的(如操作系统、应用程序本身等)。事件发送器负责将收集器收集到的事件分发到目标对象中。事件处理器做具体的事件响应工作,它往往要到实现阶段才完全确定。在事件处理器中一般都需要提前传入一个注册好的回调函数,回调函数被注册之后,程序将继续执行后面的语句,而回调函数则会在对应事件产生后被异步调用。

函数身为一个对象,拥有对象模型的三个通用属性:id、类型、和值。在下面的代码中,我们创建了一个平均值滤波函数,实现三个周期的传感器采样值计算平均值,在代码中,DataList 为全局变量,它用来记录 FileterLength 个周期的采样值,AverageFilter(value)为滤波函数,它的输入为新采集到的数据,函数中,首先将 DataList[]中的数据进行移位,并将新采集到的数据保存到 DataList[]最后的元素中,同时计算 DataList[]中 10 个数据的和,最后返回 FileterLength 个数据和的平均值。示例代码如下:

_# 全局变量,记录三个周期的采样值_
FileterLength   = 3
DataList        = [0] * FileterLength
def AverageFilter(value):
    '''
    平均值滤波函数,实现三个周期的传感器采样值计算平均值
    :param value: 当前采样值
    :return: 滤波后的传感器数值
    '''
    global DataList
    _# 临时变量,存储列表中数据之和_
    sum = 0
    for i in range(FileterLength-1):
        _# 实现列表的移位操作_
        DataList[i] = DataList[i+1]
        _# 实现列表求和_
        sum += DataList[i]
    DataList[FileterLength-1] = value
    sum += DataList[FileterLength-1]
    average = sum / len(DataList)
    return average

我们首先访问一下这个函数的基本属性:

print(id(AverageFilter))
print(type(AverageFilter))
print(AverageFilter)

运行结果如下:

我们可以给函数添加自定义属性并且访问自定义属性:

AverageFilter.description = ("The average value filter function realizes the calculation of the average value "
                             "of the sensor sample value for three periods")
print(AverageFilter.description)

运行结果如下:

事件驱动框架计时器

接下来我们用一个事件驱动框架计时器的示例更好的说明函数作为参数可以传递到另一个函数中的相关应用。示例代码如下:

import datetime
import time

_# 定义定时器事件类_
class TimedEvent:

    def __init__(self, endtime, callback):
        '''
        初始化方法,存储endtime和callback
        :param endtime:  callback执行前需要等待的时间
        :param callback: 回调函数,即到达执行时间后调用的函数
        '''
        self.endtime = endtime
        self.callback = callback

    def ready(self):
        '''
        判断是否事件已经到达了该执行的适合
        :return:
        '''
        return self.endtime <= datetime.datetime.now()

_# 定义定时器类,轮询检测实现任务调度_
class Timer:
    def __init__(self):
        '''
        初始化方法,定义一个events列表存储事件
        '''
        self.events = []

    def call_after(self, delay, callback):
        '''
        添加新的事件
        :param delay: 执行回调方法之前要等待的秒数
        :param callback: 回调方法
                         callback函数应该接收一个参数:执行调用的计时器
        :return:
        '''
        end_time = (datetime.datetime.now() +
                    datetime.timedelta(seconds=delay))
        self.events.append(TimedEvent(end_time, callback))

    def run(self):
        '''
        轮询检测,执行到达执行时间的回调函数
        :return:
        '''
        _# 轮询检测,执行到达执行时间的回调函数_
        while True:
            _# 使用一个生成器表达式,将将时间已到的事件过滤出来_
            ready_events = (e for e in self.events if e.ready())
            _# 按照顺序执行_
            for event in ready_events:
                event.callback(self)
                _# 执行完成后,移除已执行完毕的任务_
                self.events.remove(event)
            _# 在每次迭代过程中休眠 0.5 秒以防止系统死机_
            time.sleep(0.5)

下面,我们编写几个回调函数测试一下:

def format_time(message, *args):
    '''
    用字符串的 format 方法将当前时间添加到信息中,并说明变量参数
    :param message: 接收任意数量的定位参数
    :param args:    用于在函数中处理传递的位置参数序列
    :return: None
    '''
    _# 当前的时间,格式为:时-分-秒_
    now = datetime.datetime.now().strftime("%I:%M:%S")
    _# 格式化打印参数_
    print(message.format(*args, now=now))

_# 回调函数:任务一_
def Task_One(timer):
    format_time("{now}: Called Task One")

_# 回调函数:任务二_
def Task_Two(timer):
    format_time("{now}: Called Task Two")

_# 回调函数:任务三_
def Task_Three(timer):
    format_time("{now}: Called Task Three")

_# 创建定时器对象_
timer = Timer()
_# 添加回调函数_
timer.call_after(1, Task_One)
timer.call_after(2, Task_One)
timer.call_after(2, Task_Two)
timer.call_after(4, Task_Two)
timer.call_after(3, Task_Three)
timer.call_after(6, Task_Three)
_# 开始运行定时器_
format_time("{now}: Starting")
timer.run()

运行结果如下,可以看到到达各自的执行时间后,每个事件都简单地输出当前时间和一段简短的消息,告诉我们调用的是哪个回调方法。

实际上类的方法也可以用作回调函数,示例代码如下

_# 定义类_
class Repeater:
    def __init__(self):
        self.count = 0
    def repeater(self, timer):
        '''
        在函数中在创建一个定时任务
        :param timer: 定时器
        :return: None
        '''
        format_time("{now}: repeat {0}", self.count)
        self.count += 1
        _# 类的方法也可以用作回调函数_
        timer.call_after(5, self.repeater)

_# 创建定时器对象_
timer = Timer()
repeater = Repeater()
_# 添加回调函数,类的方法_
timer.call_after(5, repeater.repeater)
_# 开始运行定时器_
format_time("{now}: Starting")
timer.run()

运行结果如下:

从运行结果中我们不能看出,我们可以从当前正在运行的回调函数中给计时器添加新的事件。然后创建一个计时器并添加几个事件,在不同时间之后调用。

Format 函数

在 Python 中,format()是一个内置函数,用于格式化字符串。format()函数提供了灵活的方式来将变量插入到字符串中,并控制它们的显示格式。

它的语法如下:

format(value, format_spec)

其中,参数说明如下:

  • value: 要格式化的值;format_spec: 格式化规范,用于指定值的显示方式。
  • format()函数返回一个格式化后的字符串。

它是通过 {} 和 : 来代替以前的 % 。format 函数可以接受不限个参数,位置可以不按顺序。示例代码如下:

_# 不设置指定位置,按默认顺序_
print("{} {}".format("hello", "world"))
_# 设置指定位置_
print("{0} {1}".format("hello", "world"))
_# 设置指定位置_
print("{1} {0} {1}".format("hello", "world"))

输出如下: