在iter中更改函数的参数(function,sentinel)

时间:2018-10-16 12:04:24

标签: python python-3.x

观看pycon的Raymond Hettinger演讲后,他演示了“循环执行”的更好方法

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

有办法使它工作吗?

2 个答案:

答案 0 :(得分:1)

您遗漏了一个重要点:iter()带有一个 static 可调用对象,该对象的参数不能更改,但是重复调用f.read()会返回不同的值。具有两个参数的iter()函数将重复调用partial(f.read, 32)(因此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表达式不是'live';结果仅计算一次,并以partial()的形式传递给52;在调用i对象之前将81设置为partial()都没关系。

您可以使用一个可调用对象,该对象在每次调用时都会重新计算get_json_from()的参数;一个lambda表达式可以做到这一点(将urli作为父级作用域的闭包):

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

每次调用url + str(i)对象时,都会计算lambda。我假设当网址不存在时get_json_from()返回None,而不是False

但是,在您的情况下,您可以通过使用 generator函数将更改为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(....)将为您提供a循环中的每个while True:。该函数将状态与局部变量保持在中间,因此i会保留(以及url),以在下次未暂停代码时使用。

然后您可以使用:

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

不需要iter(),并且上面的内容比iter(lambda: ..., None)的定义要干净得多。

答案 1 :(得分:1)

您可以使用lambda而不是partial来允许每次调用时重新评估内部变量:

for piece in iter(lambda: get_json_from(url+str(i)), False):