`map`-like函数保留序列类型

时间:2013-04-30 20:46:03

标签: python list python-2.7 generator

我想实现一个类似map的函数,它保留输入序列的类型。 map不保留它:

map(str, (8, 9))  # input is a tuple
=> ['8', '9']     # output is a list

我提出的一个方法是:

def map2(f, seq):
   return type(seq)( f(x) for x in seq )

map2(str, (1,2))
=> ('1', '2')
map2(str, [3,4])
=> ['3', '4']
map2(str, deque([5,6]))
=> deque(['5', '6'])

但是,如果seq是迭代器/生成器,则不起作用。 imap适用于这种情况。

所以我的问题是:

  1. 有没有更好的方法来实现map2,它支持list,tuple和其他许多?
  2. 是否有一种优雅的方式可以将map2扩展为支持生成器(如imap那样)?显然,我想避免:try: return map2(...) except TypeError: return imap(...)
  3. 我正在寻找类似的东西的原因是我正在编写一个函数装饰器,它将返回值从X类转换为Y.如果原始函数返回一个序列(让我们假设一个序列只能是列表,元组或生成器),我假设它是一个X的序列,我想将它转换为相应的Y序列(同时保留序列的类型)。

    您可能已经意识到,我正在使用python 2.7,但python 3也很有用。

3 个答案:

答案 0 :(得分:7)

您的形式主义也不适用于map(str,'12')

最终,你不知道迭代器的类型在构造函数/初始化器中实际会采用什么参数,所以通常无法做到这一点。另请注意,imap不会为您提供与生成器相同的类型:

>>> type(x for x in range(10))
<type 'generator'>
>>> type(imap(str,range(10)))
<type 'itertools.imap'>
>>> isinstance((x for x in range(10)),type(imap(str,range(10))))
False

你可能会想到自己“肯定用python的内省,我可以检查初始化器的参数” - 而你是对的!但是,即使您知道有多少参数转到初始化程序,以及它们的名称是什么,您仍然无法获得有关您实际应该传递给它们的内容的任何信息。我想你可以编写某种机器学习算法来从文档字符串中找出它...但我认为这远远超出了这个问题的范围(并且它假设作者表现得很好并且开始创建好的文档字符串)。

答案 1 :(得分:1)

首先,type(seq)( f(x) for x in seq )实际上只是type(seq)(imap(f, seq))。为什么不用它?

其次,你想要做的事情一般没有意义。 map需要iterable,而不只是sequence。区别在于,序列具有len并且可以随机访问。

没有规则可以通过调用type(X)(y_iter)从类型为Y的值构造X类型的迭代。事实上,虽然对于序列来说通常都是正确的,但是 的其他例子很少。

如果你想要的是特别处理一些特殊类型,你可以这样做:

def map2(f, seq):
    it = imap(f, seq)
    if isinstance(seq, (tuple, list)):
        return type(seq)(it)
    else:
        return it

或者,如果你想假设所有序列都可以这种方式构建(对于大多数内置序列都是如此,但考虑一下,例如xrange - 它不是设计为序列但是确实符合协议 - 当然除了内置之外没有任何保证:

def map2(f, seq):
    it = imap(f, seq)
    try:
        len(seq)
    except:
        return it
    else:
        return type(seq)(it)

可以假设任何可以从迭代中构造的可迭代类型都是一个序列(正如你在问题中所建议的那样)......但是这可能会导致更多误报而不是利益,所以我不会。同样,请记住len是序列定义的一部分,而“从迭代器构造”不是,并且在给定迭代器时,有完全合理的可迭代类型将完全不同。

无论你做什么都将成为一个黑客,因为它的目的是破解,并违背了Python开发人员明确的设计愿望。迭代器/可迭代协议的重点在于您应该尽可能少地关注迭代的类型。这就是为什么Python 3.x已经走得更远,用基于迭代器的函数替换基于列表的函数,如mapfilter


那么,我们如何将其中一个转换变成装饰器呢?

好吧,首先,让我们跳过装饰器位,然后编写一个高阶函数,它接受一个类似imap的函数,并返回一个等效函数,并应用这个转换:

def sequify(func):
    def wrapped(f, seq):
        it = func(f, seq)
        try:
            len(seq)
        except:
            return it
        else:
            return type(seq)(it)
    return wrapped

所以:

>>> seqmap = sequify(itertools.imap)
>>> seqmap(int, (1.2, 2.3))
(1, 2)
>>> sequify(itertools.ifilter)(lambda x: x>0, (-2, -1, 0, 1, 2))
(1, 2)

现在,我们如何将其变成装饰者?好吧,一个返回已经的函数的函数是一个装饰器。您可能希望添加functools.wraps(尽管可能想要即使在非装饰器情况下也是如此),但这是唯一的变化。例如,我可以编写一个像imap一样的生成器,或者一个返回迭代器的函数,并自动转换为类似seqmap的函数:

@sequify
def map_and_discard_none(func, it):
    for elem in imap(func, it):
        if elem is not None:
            yield elem

现在:

>>> map_and_discard_none(lambda x: x*2 if x else x, (1, 2, None))
(2, 4)

当然,这仅适用于具有map的函数 - 就像语法一样 - 即它们采用函数和迭代。 (好吧,它会偶然地用于处理各种错误类型的函数 - 例如,你可以调用sequify(itertools.count(10, 5))并且它会成功检测到5不是序列,因此只需将迭代器传回未触动过。)为了使它更通用,你可以做类似的事情:

def sequify(func, type_arg=1):
    def wrapped(*args, **kwargs):
        it = func(f, seq)
        try:
            len(args[type_arg])
        except:
            return it
        else:
            return type(seq)(it)
    return wrapped

现在,你可以对sequify(itertools.combinations, 0)或任何你喜欢的事情发疯。在这种情况下,为了使它成为一个有用的装饰者,你可能想要更进一步:

def sequify(type_arg=1):
    def wrapper(func):
        def wrapped(*args, **kwargs):
            it = func(f, seq)
            try:
                len(args[type_arg])
            except:
                return it
            else:
                return type(seq)(it)
        return wrapped
    return wrapper

所以你可以这样做:

@sequify(3)
def my_silly_function(pred, defval, extrastuff, main_iterable, other_iterable):

答案 2 :(得分:1)

您的问题归结为: 给定一个序列(你似乎意味着任何支持迭代的python对象,而不是相同的序列python docs奠定)和转换,是否有一种通用的方法将转换应用于每个元素并创建一个新的精确序列相同的类型?

答案是否定的。无法保证可迭代类型将支持从iterable创建新实例。一些对象在其构造函数中固有地支持它;有些人没有。可迭代类型不保证支持相反的操作。您将需要特殊情况,您知道的所有类型都不能用简单的iterable作为初始化案例的参数。