我如何编写具有以下属性的函数“ 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}
(我在haskell中搜索了一种“ fmap”,但在python3中搜索。)
我有一个非常丑陋的解决方案,但是肯定有更Pythonic和generic的解决方案吗? :
def fmap(f, container):
t = container.__class__.__name__
g = map(f, container)
return eval(f"{t}(g)")
答案 0 :(得分:5)
eval
__class__
也可以用于实例化新实例:
def mymap(f, contener):
t = contener.__class__
return t(map(f, contener))
这消除了对eval
的需求,该需求被认为是poor practice。根据@EliKorvigo的评论,您可能更喜欢内置type
而不是魔术方法:
def mymap(f, contener):
t = type(contener)
return t(map(f, contener))
返回值是类型对象,通常与
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)
from collections import Iterable
def mymap(f, contener):
if isinstance(contener, Iterable):
return type(contener)(map(f, contener))
return 'Input object is not iterable'
之所以特别有效,是因为内置类通常用作容器,例如list
,set
,tuple
,collections.deque
等,可用于通过惰性可迭代实例化实例。存在例外:例如,即使str(map(str.upper, 'hello'))
实例是可迭代的,str
也无法按预期工作。
答案 1 :(得分: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))
您可以在原因的else
子句中为所有其他可迭代值添加大小写:
else:
return (fun(x) for x in iterable)
但是那将会是。 G。返回可能不是您想要的set
子类的可迭代对象。
请注意,我故意({em> not )使用isinstance
,因为例如这将使列表来自list
的子类。我认为在这种情况下显然不需要这样做。
一个人可能会争辩说,list
(即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))
答案 2 :(得分:1)
我在haskell中搜索了一种“ fmap”,但是在python3中
首先,让我们讨论Haskell的fmap
,以了解其行为方式的原因,尽管我假设您对这个问题相当了解Haskell。 fmap
是Functor
type-class中定义的通用方法:
class Functor f where
fmap :: (a -> b) -> f a -> f b
...
Functor遵循几个重要的数学定律,并具有从fmap
派生的几种方法,尽管后者对于最小的完整函子实例是足够的。换句话说,在属于Functor
类型类的Haskell类型中实现其自己的fmap
函数(此外,Haskell类型可以通过Functor
定义具有多个newtype
实现)。在Python中,我们没有类型类,尽管我们有一些类,尽管在这种情况下不太方便,但允许我们模拟这种行为。不幸的是,对于类,我们不能在没有子类的情况下向已定义的类添加功能,这限制了我们为所有内置类型实现通用fmap
的能力,尽管我们可以通过在我们的显式检查可接受的可迭代类型中克服它。 fmap
实现。使用Python的类型系统来表达更高种类的类型实际上也是不可能的,但是我离题了。
总结一下,我们有几种选择:
Iterable
类型(@jpp的解决方案)。它依靠构造函数将Python的map
返回的迭代器转换回原始类型。那就是对容器内部的值应用函数的职责被从容器中拿走了。这种方法与函子界面有很大的不同:函子应该自己处理映射并处理对重建容器至关重要的其他元数据。这是我对第三种解决方案的看法
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}