How can I write a function fmap that returns the same type of iterable that was inputted?
如何编写具有以下属性的函数"fmap":
1 2 3 4 5 6 | >>> 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} |
(我在haskell中搜索一种"fmap",在python3中搜索)。
我有一个非常难看的解决方案,但肯定有一个更像Python和普通的解决方案?:
1 2 3 4 | def fmap(f, container): t = container.__class__.__name__ g = map(f, container) return eval(f"{t}(g)") |
号
直接实例化,而不是通过
1 2 3 | def mymap(f, contener): t = contener.__class__ return t(map(f, contener)) |
这就消除了对
1 2 3 | def mymap(f, contener): t = type(contener) return t(map(f, contener)) |
号
如本文和文档中所述:
The return value is a type object and generally the same object as returned by
object.__class__ .
号
对于新样式的类,"一般相同"应被视为"等效"。
测试iterable您可以通过多种方式检查/测试iterable。要么用
1 2 3 4 5 6 | def mymap(f, contener): try: mapper = map(f, contener) except TypeError: return 'Input object is not iterable' return type(contener)(mapper) |
或使用
1 2 3 4 5 6 | from collections import Iterable def mymap(f, contener): if isinstance(contener, Iterable): return type(contener)(map(f, contener)) return 'Input object is not iterable' |
。
这是因为通常用作容器的内置类(如
在任何情况下,使用输入类型作为转换器都不一定有效。
因此,更干净、更健壮的版本应该是这样一个版本,它明确地期望它可以处理各种可能的输入,并且在所有其他情况下都会引发错误:
1 2 3 4 5 6 7 8 9 10 11 12 | 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)) |
您可以在"原因"的
1 2 | else: return (fun(x) for x in iterable) |
。
但这将返回一个iterable,用于
请注意,我故意不使用
有人可能会说,任何属于
1 2 3 4 5 6 7 8 9 10 | 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)) |
。
I search a kind of"fmap" in haskell, but in python3
号
首先,让我们讨论一下哈斯克尔的
1 2 3 | class Functor f where fmap :: (a -> b) -> f a -> f b ... |
函子服从几个重要的数学规律,并且有从
总之,我们有几个选择:
这是我对第三个解决方案的看法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import abc from typing import Generic, TypeVar, Callable, Union, \ Dict, List, Tuple, Set, Text A = TypeVar('A') B = TypeVar('B') class Functor(Generic[A], metaclass=abc.ABCMeta): @abc.abstractmethod def fmap(self, f: Callable[[A], B]) -> 'Functor[B]': raise NotImplemented FMappable = Union[Functor, List, Tuple, Set, Dict, Text] def fmap(f: Callable[[A], B], fmappable: FMappable) -> FMappable: if isinstance(fmappable, Functor): return fmappable.fmap(f) if isinstance(fmappable, (List, Tuple, Set, Text)): return type(fmappable)(map(f, fmappable)) if isinstance(fmappable, Dict): return type(fmappable)( (key, f(value)) for key, value in fmappable.items() ) raise TypeError('argument fmappable is not an instance of FMappable') |
号
这是一个演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | In [20]: import pandas as pd In [21]: class FSeries(pd.Series, Functor): ...: ...: def fmap(self, f): ...: return self.apply(f).astype(self.dtype) ...: In [22]: fmap(lambda x: x * 2, [1, 2, 3]) Out[22]: [2, 4, 6] In [23]: fmap(lambda x: x * 2, {'one': 1, 'two': 2, 'three': 3}) Out[23]: {'one': 2, 'two': 4, 'three': 6} In [24]: fmap(lambda x: x * 2, FSeries([1, 2, 3], index=['one', 'two', 'three'])) Out[24]: one 2 two 4 three 6 dtype: int64 In [25]: fmap(lambda x: x * 2, pd.Series([1, 2, 3], index=['one', 'two', 'three'])) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-27-1c4524f8e4b1> in <module> ----> 1 fmap(lambda x: x * 2, pd.Series([1, 2, 3], index=['one', 'two', 'three'])) <ipython-input-7-53b2d5fda1bf> in fmap(f, fmappable) 34 if isinstance(fmappable, Functor): 35 return fmappable.fmap(f) ---> 36 raise TypeError('argument fmappable is not an instance of FMappable') 37 38 TypeError: argument fmappable is not an instance of FMappable |
此解决方案允许我们通过子类化为同一类型定义多个函数:
1 2 3 4 5 6 7 8 9 | In [26]: class FDict(dict, Functor): ...: ...: def fmap(self, f): ...: return {f(key): value for key, value in self.items()} ...: ...: In [27]: fmap(lambda x: x * 2, FDict({'one': 1, 'two': 2, 'three': 3})) Out[27]: {'oneone': 1, 'twotwo': 2, 'threethree': 3} |
。