关于 Python 生成器,请教各位大佬一个问题

写了两个生成器 agentman,agreement,生成器里面的元素是一个个的 python 字典,每个字典里面都有一个 key 相同,key 对应的 value 也相同,我的需求就是分别遍历两个生成器找出拥有相同 key-value 的字典,然后将这两个字典合并成一个字典。(有没有更好的实现方法,两个 for 循环看起来实在是太丑了)

for i in agreement:
    for j in agentman:
        if i['agent_name'] == j['agent_name']:
            print(dict(i, **j))

结果我发现,当 agreement 取了第一个值的时候,agentman 可以遍历一遍,但是,当 agreement 取第二个值一直到最后一个值的时候,无法进入 agentman 的循环了,j 一直等于 agentman 的最后一个值,感觉就像是 agentman 为空一样,最后的结果就是有且只有第一次的循环得到的一个合并之后的字典。

我本以为是因为生成器只能迭代一次。可当我又写了个小例子的时候,发现可以正常输出我想要的结果。

a = [{'a': 1, 'b': 2, 'c': 3}, {'a': 4, 'b': 5, 'c': 6}, {'a': 7, 'b': 8, 'c': 9}]
b = [{'a': 1, 'd': 14, 'e': 45}, {'a': 4, 'd': 24, 'e': 5}, {'a': 7, 'd': 34, 'e': 55}]

def ag(): for i in a: yield i

def bg(): for j in b: yield j

for i in ag(): for j in bg(): if i[‘a’] == j[‘a’]: print(dict(i, **j))

结果:

	{'a': 1, 'b': 2, 'c': 3, 'd': 14, 'e': 45}
	{'a': 4, 'b': 5, 'c': 6, 'd': 24, 'e': 5}
	{'a': 7, 'b': 8, 'c': 9, 'd': 34, 'e': 55}

所以我选择就不明白到底是什么问题??

我的描述可以看的明白吧?


关于 Python 生成器,请教各位大佬一个问题

20 回复

好像没法编辑代码的格式啊


生成器这玩意儿其实挺有意思的,它就是个懒加载的迭代器。简单说,你用 yield 而不是 return,函数就变成了生成器函数,每次调用 next() 或者迭代它的时候,它才执行到 yield 那儿,把值吐出来,然后暂停,等下次再被调用时接着从暂停的地方继续跑。

最直接的好处就是省内存。比如你要处理一个超大文件,用列表一次全读进来内存可能就炸了,但用生成器可以一行一行读,处理一行扔一行,内存占用就很小。

看个简单例子你就明白了:

def simple_generator(n):
    i = 0
    while i < n:
        yield i  # 每次执行到这里就返回 i,然后暂停
        i += 1

# 使用
gen = simple_generator(5)
for value in gen:
    print(value)
# 输出: 0, 1, 2, 3, 4

这里 simple_generator(5) 并不会立刻算出所有值,for 循环每次问它要下一个值,它才执行一次循环体,yield 一个数出来。你也可以用 next(gen) 手动获取。

另一个常见场景是处理无穷序列,比如生成所有偶数:

def even_numbers():
    num = 0
    while True:
        yield num
        num += 2

evens = even_numbers()
print(next(evens))  # 0
print(next(evens))  # 2
# 可以一直 next() 下去,但它不会预先生成无穷个数占内存

生成器表达式和列表推导式语法很像,但用的是圆括号,它也是惰性的:

# 列表推导式:立即生成所有结果,占用内存
squares_list = [x*x for x in range(1000000)]

# 生成器表达式:不立即计算,需要时再生成
squares_gen = (x*x for x in range(1000000))

总结一下,当你需要按需生成数据、处理大数据流或者实现某种协程-like 的控制流时,生成器就是你的好帮手。

你的 agentman 怕不是一个迭代器。迭代器遍历完了就消耗完了,不会自动重启的。

你下面的小例子,bg() 每次都产生了一个新的迭代器。所以可以重复遍历。

理解 生成器函数,生成器,迭代器,列表 的区别和关联

肯定是生成器的,以为我对调了 agentman 和 agreement 的循环顺序,结果还是一样的

楼上解释了部分问题。对于两个 for 太丑的问题:当你使用两个序列类型配对的时候,因为你只能进行顺序查找,所以复杂度会是 O(n2)。那么你可以把一个较大的对象换成字典来实现 O(n) 配对。字典可以 ( key,value ) 元组为键,元素为值。

所以你能不能把整个代码发上来,包括怎么产生这两个变量的。。。

迭代器每一次执行__next()__方法后会记录当前变量值,所以一次循环结束会指向最后一个元素

可以在一次循环结束调用生成器的 send 函数,手动指向第一个元素

j = bg()


id(bg())
Out[125]: 139947918612704


id(bg())
Out[126]: 139947918289296


id(j)
Out[127]: 139947918612176


id(j)
Out[128]: 139947918612176


python<br>class AgentmanReader(ReadExcel):<br> def __init__(self, path):<br> super(AgentmanReader, self).__init__(path)<br> assert self.ncols == 27, 'columns must be 27'<br><br> def parse_data(self):<br> for i in range(1, self.nrows):<br> agentman_dict = dict(<br> # 公司名称(代理人名称)*<br> agent_name=self.sheet.cell_value(i, 0),<br> # 归属机构代码<br> org_code=str(self.sheet.cell_value(i, 1)),<br> # 地址*<br> address=self.sheet.cell_value(i, 2),<br> # 邮编*<br> postcode=self.sheet.cell_value(i, 3),<br> # 业务渠道*<br> trade_channel=str(TRADE_CHANNEL[self.sheet.cell_value(i, 4)]),<br> # 代理人类型*<br> agentman_type=str(AGENT_TYPE[self.sheet.cell_value(i, 5)]),<br> # 许可证号*<br> license_num=self.sheet.cell_value(i, 6),<br> # 组织机构代码*<br> social_code=self.sheet.cell_value(i, 7),<br> # 负责人*<br> principal=self.sheet.cell_value(i, 8),<br> # 电话*<br> phone=self.sheet.cell_value(i, 9),<br> # 手机<br> mobile=self.sheet.cell_value(i, 10),<br> # MAC 地址<br> mac_addr=self.sheet.cell_value(i, 11),<br> # 资格证有效期<br> Validity=self.sheet.cell_value(i, 12),<br> # 数字证书编码<br> digital_code=self.sheet.cell_value(i, 13),<br> # 开户银行<br> opening_bank=self.sheet.cell_value(i, 14),<br> # 户名<br> account_name=self.sheet.cell_value(i, 15),<br> # 银行类别<br> bank_type=BANK_TYPE.get(self.sheet.cell_value(i, 16), '0'),<br> # 省份<br> province=self.sheet.cell_value(i, 17),<br> # 城市<br> city=self.sheet.cell_value(i, 18),<br> # 银行帐号<br> bank_account=self.sheet.cell_value(i, 19),<br> # 是否发送短信息<br> is_send=self.sheet.cell_value(i, 20),<br> # 纳税人身份<br> taxpayer=self.sheet.cell_value(i, 21),<br> # 纳税人识别号<br> taxpayer_num=self.sheet.cell_value(i, 22),<br> # 纳税人地址<br> taxpayer_addr=self.sheet.cell_value(i, 23),<br> # 纳税人电话<br> taxpayer_ph=self.sheet.cell_value(i, 24),<br> # 纳税人开户行名称<br> taxpayer_bank=self.sheet.cell_value(i, 25),<br> # 纳税人银行账号<br> taxpayer_account=self.sheet.cell_value(i, 26)<br> )<br> yield agentman_dict<br><br>agentman_data = AgentmanReader(AGENTMAN_PATH).parse_data()<br>
excel 里面有几千条数据,读出来转换成一个生成器。

具体怎么做,能不能举个列子

一个生成器只能遍历一次。

读 excel 为什么不用 pandas

agentman_data = AgentmanReader(AGENTMAN_PATH).parse_data()

这一条就是根本原因。agentman_data 现在只是一个迭代器,只能用一次。

那应该怎么改呢?

没用过 pandas,有空学习下

居然打不开

别存它,直接 for 函数调用



mans_agreements = {} # 假设 agreement 比较大
for a in agreement:
mans_agreements.setdefault(a[‘agent_name’], [])
mans_agreements[a[‘agent_name’]].append(a)

for man in agentman:
his_agreements = mans_agreements.get(man[‘agent_name’], [])
for a in his_agreements:
print(dict(man, **a))

回到顶部