首页 > 解决方案 > 如何在整个迭代中应用函数?

问题描述

我想要f的工作方式是:

>>> f(chr, [65, 97])
['A', 'a']
>>> f(chr, {65, 97})
{'A', 'a'}
>>> f(chr, {65: 'upper_a', 97: 'lower_a'})
{'A': 'upper_a', 'a': 'lower_a'}

map很懒,所以我不得不这样做list(map(function, iterable_ds)),但是这样破坏了原始数据结构。

你怎么能做到这一点?


有一天尝试写作时f,我得到了这个问题?

每个数据结构类__iter__都是可迭代的,但就像__next__()在任何可迭代中给出下一个元素一样,那么什么特殊函数会给 upower 添加任何 xyz 到可迭代 ds?为什么 append 仅用于列表而 add 用于设置?为什么没有像__iter__and这样的通用接口__next__


我试过了

def f(func, i_ds):
    ctor = type(i_ds)
    holder = list()
    for _ in i_ds:
        new_val = func(_)
        # now what add? append? update? xyz? I hope ctor can consume list
        holder.append(new_val)
    return ctor(holder) # I know I know it fail for dict type easily

我可以写 f 来处理 dict 但是

标签: pythondata-structuresiterator

解决方案


映射(dict类似的东西)与其他集合根本不同,因此您将无法完全避免对它们进行特殊封装,但您至少可以限制所需的专门代码的数量。虽然您是对的,没有通用的“将项目添加到集合”API,但大多数集合都接受构造函数的可迭代值。

使用基于构造函数的ducktyping,对你所拥有的一个小的改进是:

import collections.abc

def f(func, it):
    res = map(func, it)
    if isinstance(it, collections.abc.Mapping):
        # Pair up mapped keys with original values
        res = zip(res, it.values())
    return type(it)(res)

这不需要任何中间临时数据结构(map并且zip是基于惰性生成器的函数;它们仅在最后构造返回类型时才产生值)。它假设所有非Mapping类型都可以从值的可迭代构造,而所有Mapping类型都可以从对的可迭代构造;对于通用内置集合(tuple, list, set, frozenset, dict),就是这种情况。不太通用的容器不一定能工作(bytes取决于结果funcstr如果没有特殊的外壳就不能工作''.join)。

也就是说,您尝试做的事情是不必要的。您的f功能正在复制map,非常糟糕。由于永远不可能编写像您这样具有完美类型复制的函数,因此最好照此执行,map并将其留给调用者以适当的数据结构重新打包结果(例如,对于dicts,它们可以显式传递映射函数期望一个键/值tuple和 pass .items(),然后dict从结果中构造;真的,他们可能想要一个dict理解)。

毕竟,您所要求的永远不会适用于每种输入类型,仅仅是因为构造函数并不总是遵循相同的规则。例如,此代码将无法使用,collections.defaultdict因为它将default_factory作为第一个参数,然后是一个可迭代的初始化程序(违反了我们对第一个参数是可迭代初始化程序的期望)。试图处理这意味着更多的特殊情况,而你只是越来越难以确定f实际做了什么。调用者真的很难明确地做:{myfunc(k): v for k, v in mydict.items()}而不是f(myfunc, mydict),或[myfunc(x) for x in mylist]/list(map(myfunc, mylist))结束f(myfunc, mylist)吗?


推荐阅读