我在haskell中搜索了一种“fmap”,但是在python3中
首先,让我们讨论一下 Haskell 的 fmap,以了解它为什么会这样,尽管我假设您在考虑这个问题时对 Haskell 相当熟悉。 fmap是Functor type-class中定义的泛型方法:
class Functor f where
fmap :: (a -> b) -> f a -> f b
...
Functor 遵循几个重要的数学定律,并且有几个从 fmap 派生的方法,尽管后者对于最小完整的 functor 实例来说已经足够了。换句话说,在属于Functor 类型类的Haskell 类型中实现了它们自己的fmap 函数(此外,Haskell 类型可以通过newtype 定义有多个Functor 实现)。在 Python 中,我们没有类型类,尽管我们确实有类,虽然在这种情况下不太方便,但允许我们模拟这种行为。不幸的是,对于类,我们不能在没有子类的情况下向已经定义的类添加功能,这限制了我们为所有内置类型实现通用 fmap 的能力,尽管我们可以通过在 @987654332 中显式检查可接受的可迭代类型来克服它@ 执行。使用 Python 的类型系统也无法表达更高种类的类型,但我离题了。
总而言之,我们有几种选择:
- 支持所有
Iterable 类型(@jpp 的解决方案)。它依靠构造函数将 Python 的 map 返回的迭代器转换回原始类型。那就是对容器内的值应用函数的职责被从容器中移除。这种方法与仿函数接口有很大不同:仿函数应该自己处理映射并处理对重构容器至关重要的其他元数据。
- 支持易于映射的内置可迭代类型的子集(即不携带任何重要元数据的内置)。此解决方案由 @Alfe 实现,虽然不太通用,但更安全。
- 采用解决方案 #2 并添加对适当的用户定义函子的支持。
这是我对第三种解决方案的看法
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')
这是一个演示
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
此解决方案允许我们通过子类化为同一类型定义多个仿函数:
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}