Python 如何在整个iterable中应用函数?

Python 如何在整个iterable中应用函数?,python,data-structures,iterator,Python,Data Structures,Iterator,我希望函数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
的工作方式是:

>>> 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
时,我问了以下问题

每个具有
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。?为什么“附加”仅用于列表而“添加”用于集合?为什么没有像
\uuuu iter\uuuuuuu
\uuuuu next\uuuuuuuuu
这样的通用接口


我试过了

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,但是

  • 很难看
  • 如果明天我得到两个不同的人写的树d.s.怎么办
映射(
dict
-类似的东西)与其他集合有根本不同,因此您无法完全避免使用特殊的大小写,但至少可以限制所需的特殊代码量。虽然没有常见的“将项添加到集合”API,但大多数集合都接受构造函数中的iterable值

使用基于构造函数的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
是基于惰性生成器的函数;它们仅在最后构造返回类型时才生成值)。它假设所有非
映射
类型都可以从一个值的iterable中构造,而所有
映射
类型都可以从一个对的iterable中构造;对于通用内置集合(
tuple
list
set
frozenset
dict
),情况就是这样。不太通用的容器不一定能工作(
字节
取决于
func
的结果,
str
如果没有使用
''的特殊大小写就不能工作。join

也就是说,你试图做的是不必要的。您的
f
函数正在严重复制
map
。由于永远不可能使用完美的类型复制来编写像您这样的函数,因此最好按照
map
所做的那样,将结果留给调用者以适当的数据结构重新打包(例如,对于
dict
s,它们可以显式地传递期望键/值
元组的映射函数,并传递
.items()
,然后根据结果构造
dict
;实际上,它们可能需要
dict
理解)

毕竟,您所要求的永远不会适用于每种输入类型,这仅仅是因为构造函数并不总是遵循相同的规则。例如,此代码不适用于
集合。defaultdict
,因为它将
default\u factory
作为第一个参数,第二个参数是iterable初始值设定项(违反了我们对第一个参数是iterable初始值设定项的期望)。试图处理这意味着更多的特殊情况,而您只是让它越来越难确定
f
实际上做了什么。调用方显式执行:
{myfunc(k):v for k,v in mydict.items()}
而不是
f(myfunc,mydict)
,或者
[myfunc(x)在mylist中表示x]
/
列表(map(myfunc,mylist))
而不是
f(myfunc,mylist)

我认为这可能会为您提供保持代码清晰所需的功能

from functools import singledispatch


@singledispatch
def f(i_ds: list, func):
    holder = list()
    for val in i_ds:
        new_val = func(val)
        holder.append(new_val)
    return holder


@f.register(set)
def _(i_ds: set, func):
    holder = set()
    for val in i_ds:
        new_val = func(val)
        holder.add(new_val)
    return holder


@f.register(dict)
def _(i_ds: dict, func):
    holder = dict()
    for k, v in i_ds.items():
        new_key = func(k)
        holder[new_key] = v
    return holder


print(f([65, 97], chr))
print(f({65, 97}, chr))
print(f({65: 'upper_a', 97: 'lower_a'}, chr))
singledispatch仅适用于第一个参数,但您可以使用multipledispatch第三方库()


当新的数据结构类型出现时,您需要为它注册新的函数。

到目前为止您尝试了什么?@PaulRooney:我想他们的意思是,这意味着无条件地以
列表
结束,而不是以任何输入结束。他们可能不理解
列表
列表(映射(…)的一部分
可以更改为任何其他兼容的构造函数,该构造函数接受一个iterable的输入值来初始化集合。@PaulRooney我的意思是,即使输入dict、set等,最终输出也是list。这是一个聪明的主意;我从来没有真正费心使用该装饰器,但这是一个很好的用例。它实际上只是对每个数据进行特殊的装箱pe(在内部,它在一个
dict
中查找支持的类型并调用与之相关的函数),但至少每个特殊情况的代码并没有混合到一个巨大的丑陋函数中。哇,太棒了,这个想法对你有何影响?@ShadowRanger,我真的很喜欢你的答案;)谢谢。我原以为
map
的某个变体可以就地执行功能-在穿越d.s.时进行修改,但我得到了your@vvsLaxman:如果您的
func
lambda ch:chr(ord(ch)+1)
,并且您将
{a':1,'b':2}的
dict
传递给它,则肯定无法进行就地修改
,如果它先处理
'a'
,您最终会替换
'b'
(在任何情况下,在迭代过程中修改
dict
键集都是非法的,即使没有重叠)。同样的问题也适用于
set
。只有
list
可以做一些稍微合理的事情(使用
enumerate
并在每个索引运行时赋值,或者更简单地说,
mylist[:]=map(func,mylist)
)。