关于复制SimPy仿真环境的生成器的讨论

文章目录


参考文章:

  1. SimPy Discrete event simulation for Python
  2. python离散事件仿真库SimPy官方教程
  3. 离散事件仿真原理DES

基础内容

生成器介绍

离散事件仿真库Simpy的执行效率之所以很高,关键在于生成器的使用,在Python中通过yield来暂时停止进程,再次调用时才从中断的位置开始。这会有什么特点呢?就是程序并不需要在一开始完全执行,而是随着一点点推进(调用)的时候,才执行相应的操作,因此它不像常规的序列(如列表)一样在内存中保存所有的元素值,这种惰性计算地特性显著地减少了内存使用,但也使得生成器对象的状态不容易序列化和反序列化。

如下是一个简单的例子:

python 复制代码
def generator(n):
    while True:
        for j in range(n):
            yield j+100

先创建一个生成器,并进行调用,得到的返回值如下:

python 复制代码
a = generator(3)
print(next(a))
print(next(a))
print(next(a))

注意:yield 中断的是具体生成器的进程,因此创建多个不同生成器(一个对象也可以有多个生成器),可以实现多事件线的启停,这种特性非常适合用来进行离散仿真。

对于同一个生成器,调用3次,返回的结果为:

text 复制代码
100
101
102

那继续调用呢?调用6次后的返回结果如下:

text 复制代码
100
101
102
100
101
102

由于 while True 的存在,且暂无终止条件,只要一直调用,生成器就会一直抛出计算结果。

保存和恢复生成器的状态

现在,我想实现这么个功能,就是生成器在调用到一半的时候,对他进行复制,并希望复制的新的生成器能从源生成器的中断位置继续执行,如下代码,在第一次调用 a 生成器时,返回 100,复制为 b 生成器,希望 b 的下次调用抛出 101,那么,执行后会出现什么情况:

python 复制代码
def generator(n):
    while True:
        for j in range(n):
            yield j+100

a = generator(3)
print(next(a))

b = deepcopy(a)
print(next(b))

结果会返回类型错误。

text 复制代码
TypeError: cannot pickle 'generator' object

前面提到,生成器对象的状态不容易序列化和反序列,计算结果并不存储在内存中,这就使得我们如果想要保存生成器的状态,并进行复制,就只能考虑将这些生成器的值保存到可序列化的数据结构当中,比如列表或元组,通过这些可序列化的数据结构,来保存生成器的状态。那如何基于这些保存的可序列化的数据进行恢复呢?我们知道,生成器暂停进程,以及抛出一次次的计算结果,是因为函数 yield,因此恢复生成器状态只需要模拟生成器照常记录计算结果,在计算结果到达记录状态之前,跳过yield

为了恢复数据,需要记录生成器的当前状态,以及需要恢复的状态,这里我们建立一个 event 类,初始化的成员属性如下:

python 复制代码
class event:
    def __init__(self) -> None:
        self.record = []		# 当前状态
        self.from_record = []	# 待恢复状态

记录当前状态需要在每次运行到 yield 之前进行记录,这里记录的内容不一定是要yield语句的执行内容,可以只是一些标记的关键数据。

python 复制代码
class event:
    def __init__(self) -> None:
        self.record = []
        self.from_record = []

    def generator(self, n):
        while True:
            for j in range(n):
                self.record.append(j+100)
                yield j+100

记录完当前状态后,将当前状态与待恢复状态进行比较,如果不同,则跳过 yield,若相同,则释放到 yield 并完成状态的恢复。并建立一个成员方法 set_init_record 用来定义生成器的起始状态。

这里记录的状态可以是任何数据结构,当前状态和待恢复状态之间的比较也可以是任何逻辑运算,但要注意不论是记录状态还是对比恢复状态的操作,都是放在 yield 语句之前,不改变外层的循环。

python 复制代码
class event:
    def __init__(self) -> None:
        self.record = []
        self.from_record = []

    def generator(self, n):
        while True:
            for j in range(n):
                self.record.append(j+100)

                if len(self.record) <= len(self.from_record):
                    continue
                else:
                    yield j+100

    def set_init_record(self, init_record):
        self.from_record = init_record

此时,通过复制一个 event 对象的当前记录信息,将记录信息作为起始状态传入另一个 event 对象,即实现了生成器的状态复制,进行如下实验:

python 复制代码
a = event()
a_g = a.generator(3)
print(f"\na的抛出: ", next(a_g))

b = event()
b.set_init_record(deepcopy(a.record))
b_g = b.generator(3)
print(f"\nb的抛出: ", next(b_g))
print(f"b的抛出: ", next(b_g))
print(f"b的抛出: ", next(b_g))
print(f"b的抛出: ", next(b_g))
print(f"b的抛出: ", next(b_g))

输出结果为:

text 复制代码
a的抛出:  100

b的抛出:  101
b的抛出:  102
b的抛出:  100
b的抛出:  101
b的抛出:  102

显然的,这种方式相当于做了个标记,跳过了事件抛出的过程,但是所有的计算步骤都会重新执行一遍,但它是完完整整地恢复了整个生成器。因此换个思路,有没有必要完整地恢复整个生成器的抛出序列,还是记录待恢复状态,然后基于这个状态出发进行仿真。

相关推荐
ZH15455891314 分钟前
Flutter for OpenHarmony Python学习助手实战:面向对象编程实战的实现
python·学习·flutter
玄同7654 分钟前
SQLite + LLM:大模型应用落地的轻量级数据存储方案
jvm·数据库·人工智能·python·语言模型·sqlite·知识图谱
User_芊芊君子10 分钟前
CANN010:PyASC Python编程接口—简化AI算子开发的Python框架
开发语言·人工智能·python
白日做梦Q20 分钟前
Anchor-free检测器全解析:CenterNet vs FCOS
python·深度学习·神经网络·目标检测·机器学习
喵手34 分钟前
Python爬虫实战:公共自行车站点智能采集系统 - 从零构建生产级爬虫的完整实战(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·采集公共自行车站点·公共自行车站点智能采集系统·采集公共自行车站点导出csv
喵手42 分钟前
Python爬虫实战:地图 POI + 行政区反查实战 - 商圈热力数据准备完整方案(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·地区poi·行政区反查·商圈热力数据采集
熊猫_豆豆1 小时前
YOLOP车道检测
人工智能·python·算法
nimadan121 小时前
**热门短剧小说扫榜工具2025推荐,精准捕捉爆款趋势与流量
人工智能·python
默默前行的虫虫1 小时前
MQTT.fx实际操作
python
YMWM_1 小时前
python3继承使用
开发语言·python