如何编写一个函数fmap,它返回输入的相同类型的iterable?

HNo*_*oob 5 python functional-programming python-3.x

如何使用以下属性编写函数"fmap":

>>> l = [1, 2]; fmap(lambda x: 2*x, l)
[2, 4]
>>> l = (1, 2); fmap(lambda x: 2*x, l)
(2, 4)
>>> l = {1, 2}; fmap(lambda x: 2*x, l)
{2, 4}
Run Code Online (Sandbox Code Playgroud)

(我在haskell中搜索了一种"fmap",但是在python3中).

我有一个非常难看的解决方案,但肯定有更多pythonic和泛型的解决方案?:

def fmap(f, container):
    t = container.__class__.__name__
    g = map(f, container)
    return eval(f"{t}(g)")
Run Code Online (Sandbox Code Playgroud)

jpp*_*jpp 5

直接实例化而不是通过实例 eval

__class__ 也可用于实例化新实例:

def mymap(f, contener):
    t = contener.__class__
    return t(map(f, contener))
Run Code Online (Sandbox Code Playgroud)

这样就不再需要eval使用它,这被认为是不好的做法.根据@ EliKorvigo的评论,您可能更喜欢内置于type魔术方法:

def mymap(f, contener):
    t = type(contener)
    return t(map(f, contener))
Run Code Online (Sandbox Code Playgroud)

正如这里文档中所解释的那样:

返回值是一个类型对象,通常与返回的对象相同object.__class__.

在新式课程中,"通常相同"应视为"等同".

测试可迭代的

您可以通过几种方式检查/测试迭代.使用try/ except捕获TypeError:

def mymap(f, contener):
    try:
        mapper = map(f, contener)
    except TypeError:
        return 'Input object is not iterable'
    return type(contener)(mapper)
Run Code Online (Sandbox Code Playgroud)

或使用collections.Iterable:

from collections import Iterable

def mymap(f, contener):
    if isinstance(contener, Iterable):
        return type(contener)(map(f, contener))
    return 'Input object is not iterable'
Run Code Online (Sandbox Code Playgroud)

因为这特别适用内置类常用的容器,如list,set,tuple,collections.deque,等,可用于通过一个懒惰的迭代实例的实例.存在异常:例如,str(map(str.upper, 'hello'))即使str实例是可迭代的,也不会像您预期的那样工作.

  • 我会说,调用`type(container)(map(...))`比访问magic属性更清晰. (2认同)

Alf*_*lfe 3

使用输入类型作为转换器并不一定适用于所有场合。 map只是使用其输入的“可迭代性”来产生输出。在 Python3 中,这就是为什么map返回一个生成器而不是一个列表(这更合适)。

因此,一个更干净、更健壮的版本将明确期望它可以处理各种可能的输入,并在所有其他情况下引发错误:

def class_retaining_map(fun, iterable):
  if type(iterable) is list:  # not using isinstance(), see below for reasoning
    return [ fun(x) for x in iterable ]
  elif type(iterable) is set:
    return { fun(x) for x in iterable }
  elif type(iterable) is dict:
    return { k: fun(v) for k, v in iterable.items() }
  # ^^^ use .iteritems() in python2!
  # and depending on your usecase this might be more fitting:
  # return { fun(k): v for k, v in iterable.items() }
  else:
    raise TypeError("type %r not supported" % type(iterable))
Run Code Online (Sandbox Code Playgroud)

else您可以在Cause 子句中为所有其他可迭代值添加一个案例:

  else:
    return (fun(x) for x in iterable)
Run Code Online (Sandbox Code Playgroud)

但这会返回一个可迭代的子类,set而该子类可能不是您想要的。

请注意,我故意使用isinstance,因为这会从 的子类中生成一个列表list,例如。我认为在这种情况下这是明确不想要的。

有人可能会争辩说,任何 a list(即 a 的子类list)都需要遵守一个构造函数,该构造函数为元素的迭代返回此类型的事物。同样,对于set,的子类dict(必须适用于对的迭代)等。然后代码可能如下所示:

def class_retaining_map(fun, iterable):
  if isinstance(iterable, (list, set)):
    return type(iterable)(fun(x) for x in iterable)
  elif isinstance(iterable, dict):
    return type(iterable)((k, fun(v)) for k, v in iterable.items())
  # ^^^ use .iteritems() in python2!
  # and depending on your usecase this might be more fitting:
  # return type(iterable)((fun(k), v) for k, v in iterable.items())
  else:
    raise TypeError("type %r not supported" % type(iterable))
Run Code Online (Sandbox Code Playgroud)