1

我想实现一个类似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,它支持列表、元组和许多其他方法?
  2. 有没有一种优雅的方式来扩展map2以支持生成器(就像imap那样)?显然,我想避免:try: return map2(...) except TypeError: return imap(...)

我正在寻找类似的东西的原因是我正在编写一个函数装饰器,它将返回值从 X 类型转换为 Y。如果原始函数返回一个序列(假设一个序列只能是一个列表,元组或生成器),我假设它是 X 的序列,我想将其转换为 Y 的相应序列(同时保留序列的类型)。

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

4

3 回答 3

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 的自省,我可以检查初始化程序的参数”——你是对的!但是,即使您知道有多少参数传递给初始化程序,以及它们的名称是什么,您仍然无法获得有关您实际上应该传递给它们的任何信息。我想你可以编写某种机器学习算法来从文档字符串中找出答案......但我认为这远远超出了这个问题的范围(并且它假设作者表现良好并从一开始就创建了好的文档字符串)。

于 2013-04-30T20:50:26.317 回答
1

首先,type(seq)( f(x) for x in seq )真的只是type(seq)(imap(f, seq))。为什么不直接使用它?

其次,您尝试做的事情通常没有意义。map接受任何可迭代的,而不仅仅是一个序列。基本上,不同之处在于,序列具有 alen并且是随机可访问的。

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

如果您想要专门处理一些特殊类型,您可以这样做:

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是序列定义的一部分,而“可从迭代器构造”不是,并且存在完全合理的可迭代类型,当给定迭代器时它们会做一些完全不同的事情。

无论你做什么都将是 hack,因为其意图就是 hack,并且违背了 Python 开发人员明确的设计意愿。迭代器/可迭代协议的全部意义在于您应该尽可能少地关心可迭代对象的类型。这就是为什么 Python 3.x 更进一步并用基于迭代器的函数代替了基于列表的函数,例如map和。filter


那么,我们如何将这些转换之一变成装饰器呢?

好吧,首先,让我们跳过装饰器位,只编写一个高阶函数,它接受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):
于 2013-04-30T21:11:27.003 回答
1

您的问题归结为:给定一个序列(您似乎指的是任何支持迭代的python对象,而不是python文档放置的相同序列)和一个转换,是否有一种通用方法可以将转换应用于每个元素并创建完全相同类型的新序列?

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

于 2013-04-30T21:52:33.517 回答