美文网首页
函数(4)-- 参数迭代

函数(4)-- 参数迭代

作者: 小懒额 | 来源:发表于2018-05-10 08:02 被阅读0次
第17条:在参数上面迭代时,要多加小心

如果函数接受的参数是个对象列表,那么很有可能在这个列表上面进行多次迭代。
比如在一个文件中获取数字,获取当前每个数字占所有数字总和百分比的结果时,可能会这样写:

def normalize(numbers):
    total = sum(numbers)
    result = []
    for num in numbers:
        percentage = 100 * num / total
        result.append(percentage)
    return result
# 这里使用生成器来实现文件的读取
def read_visits(data_path):
    with open(data_path) as f:
        for line in f:
            yield int(line)

然而在运行的时候,发现并没有输出预想的结果,而是输出了一个 None

it = read_visits('D:\\numbers.txt')
percentages = normalize(it)
print(percentages)
>>>
None

对于出乎意料的结果,原因就在于给 normalize 函数的参数是一个生成器,在进行 sum() 运算的时候,就已经对这个参数进行了一直完全的迭代(直到抛出 StopIteration 异常),当再去使用 for 语句来对其进行迭代的时候,由于该生成器已经通过 sum() 遍历到结尾了,就无法继续进行遍历。
这里还要注意的是, for 语句对 StopIteration 异常认为是合法的,因为在 for 语句中正常遍历到最后都会出现这个异常,所以 for 语句中也没有任何报错。
所以在把生成器作为函数的参数时,如果函数中存在对生成器进行多次迭代,就不能正常运行返回结果。

为了函数的正确运行,我们可以在函数中将迭代器复制一份,在函数中多次使用,

def normalize(numbers):
    number_list = list(numbers)
    total = sum(number_list)
    result = []
    for num in number_list:
        percentage = 100 * num / total
        result.append(percentage)
    return result

it = read_visits('D:\\numbers.txt')
percentages = normalize(it)
print(percentages)
>>>
[13.186813186813186, 31.16883116883117, 21.37862137862138, 34.26573426573427]

还有一种方法就是传入一个可以返回新的生成器的函数作为参数,保证函数中每次使用这个参数时都是一个新的生成器,就不会出现 StopIteration 异常。

def normalize_func(get_iter):
    total = sum(get_iter())
    result = []
    for num in get_iter():
        percentage = 100 * num / total
        result.append(percentage)
    return result

这里的 get_iter() 函数可以使用 lamdba 表达式来实现。

percentages = read_visits(lambda: read_visits(data_path))

这样把函数作为参数,确实使得结果正确,只是看起来 lambda 表达式略显生硬。还有一个更好的方法,就是新编一种实现迭代器协议的容器。
在执行 for x in foo 时, python 实际上会调用 iter(foo) 。内置的 iter 函数又会调用 foo.__iter__ 这个特殊方法。该方法必须返回迭代器对象,而迭代器本身,则实现了 __next__ 的方法。然后, for 循环会在迭代器上反复调用内置的 next 函数,直到 next 函数返回 StopIteration 为止。
按照这个思路,我们只要把自己类中的 __iter__ 方法实现为生成器就可以满足这个要求。

class ReadVisits(data_path):
    def __init__(self):
        self.data_path = data_path

    def __iter__(self):
        with open(self.data_path) as f:
            for line in f:
                yield int(line)

visit = ReadVisits(data_path)
percentages = normalize(visit)
print(percentages)
>>>
[13.186813186813186, 31.16883116883117, 21.37862137862138, 34.26573426573427]

这时再把前面的 normalize 函数改写一下,保证传入的参数不是生成器对象,

def normalize_defensive(numbers):
    if iter(numbers) is iter(numbers):
        raise TypeError('Must supply a container')
    total = sum(numbers())
    result = []
    for num in numbers():
        percentage = 100 * num / total
        result.append(percentage)
    return result

visit = ReadVisits(data_path)
percentages = normalize_defensive(visit)
print(percentages)
>>>
[13.186813186813186, 31.16883116883117, 21.37862137862138, 34.26573426573427]
it = read_visits(data_path)
percentages = normalize_defensive(it)
print(percentages)
>>>
TypeError: Must supply a container

相关文章

  • 函数(4)-- 参数迭代

    第17条:在参数上面迭代时,要多加小心 如果函数接受的参数是个对象列表,那么很有可能在这个列表上面进行多次迭代。比...

  • Python基础知识详解

    函数可变参数 函数关键字参数 命名关键字参数 递归函数 尾递归函数优化 切片 迭代 列表迭代:字典迭代: 在这里小...

  • 5、迭代器实现

    闭包实现迭代器 迭代函数实现迭代器 --1,调用迭代函数,(把状态变量和控制变量当做参数传递给迭代函数) 状态变...

  • 迭代器

    创建一个迭代器,接收任意多个函数参数 创建多个异步的函数,注入到迭代器中

  • 内置函数

    一:filter函数:过滤器 1-1:函数使用 参数1:过滤规则函数参数2:可迭代对象 二:map函数:将函数应用...

  • python重要概念记录

    数据类型文件操作爬虫 可变参数和关键词参数(*args **kw)迭代 迭代器 迭代对象列表生成式生成器递归函数...

  • python-函数的可变参数

    可变参数定义为 def 函数名(*可变参数名),可变参数类型为元组,注意在函数内不可修改。但可以通过for循环迭代...

  • python函数式编程(map reduce filter so

    map(function,Iterable)函数 map函数接收两个参数,第一个参数是函数,第二个是可迭代对象(列...

  • JS数组的几种迭代方法和归并方法

    一、数组的迭代方法, 每一个迭代方法的参数都是一个函数,该函数有三个参数:数组项的值,该项在数组中的位置,数组对象...

  • Python zip方法理解

    zip函数的原型为:zip([iterable, …]) 参数iterable为可迭代的对象,并且可以有多个参数。...

网友评论

      本文标题:函数(4)-- 参数迭代

      本文链接:https://www.haomeiwen.com/subject/gzxirftx.html