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)
)。