首页 > 解决方案 > 在 iter(function, sentinel) 中更改函数的参数

问题描述

在观看了 pycon 的 Raymond Hettinger 讲座后,他展示了“do while loop”的更好方法

blocks = []
while True:
    block = f.read(32)
    if block == '':
        break
    blocks.append(block)

等于:

blocks = []
for block in iter(partial(f.read, 32), ''):
    blocks.append(block)

代码中有相同的结构。但是,如果需要更改 iter 内部函数的参数,它就不能“正确”工作。

def get_data_from_user(user, type, token):
    data = []
    url = f'https://api.github.com/users/{user}/{type}?access_token={token}&page='
    i = 1
    while True:
        a = get_json_from(url + str(i))
        if not a:
            break
        data.extend(a)
        i += 1
    return data

i = 1
data = []
for piece in iter(partial(get_json_from, url+str(i)), False):
    data.append(piece)
    i += 1

有没有办法让它工作?

标签: pythonpython-3.x

解决方案


您遗漏了一个重点:iter()采用静态可调用对象,其中参数不能更改,但重复调用以f.read()返回不同的值。iter()具有两个参数的函数将重复调用partial(f.read, 32)(so f.read(32)),直到返回值与标记值匹配,这使得在循环中从文件中读取有效。

您的get_json_from()功能不会这样做。使用相同的参数重复调用get_json_from()不会改变返回值,因为get_json_from()没有任何状态可以依赖。

您的参数不是动态的,url + str(i)作为参数传入也不会i从循环中获取,因为partial()只记录一次值:

>>> from functools import partial
>>> i = 42
>>> p = partial(str, i + 10)
>>> p.args
(52,)
>>> i = 81
>>> p.args
(52,)
>>> p()
'52'

i + 10表达不是“活的” ;结果计算一次并传递给partial()as 52;在调用对象之前i设置为没有关系。81partial()

get_json_from()您可以使用可调用对象,该对象在每次调用时重新计算参数;一个 lambda 表达式会做到这一点(从父作用域获取url和作为闭包):i

for part in iter(lambda: get_json_from(url + str(i)), None):
    # ...

这会url + str(i)在每次lambda调用对象时进行计算。我假设在 url 不存在时get_json_from()返回,而不是.NoneFalse

i但是,在您的情况下,您可以通过使用生成器函数将“状态”(如更改值)绑定到可迭代对象,使代码更清晰:

def gen_data_from_user(user, type, token):
    url = f'https://api.github.com/users/{user}/{type}?access_token={token}&page='
    i = 1
    while True:
        a = get_json_from(url + str(i))
        if not a:
            break
        yield a
        i += 1

在生成器函数中,代码会暂停,直到您开始迭代调用函数返回的对象。迭代时,代码会一直运行,直到yield遇到下一个表达式,此时函数代码会再次停止,并为您提供表达式的值。

所以在上面,循环gen_data_from_user(....)会给你每个循环awhile True:该函数将状态保持在局部变量之间,因此在下次取消暂停代码时使用i(以及)。url

然后你可以使用:

for piece in gen_data_from_user(...):
    # ...

不需要iter(),上面的内容比iter(lambda: ..., None)定义要清晰得多。


推荐阅读