我想将函数f
应用于集合xs
,但保留其类型。如果我使用map
,我会得到一个“map object”:
def apply1(xs, f):
return map(f, xs)
如果我知道xs
类似于list
或tuple
,我可以强制它具有相同的类型:
def apply2(xs, f):
return type(xs)(map(f, xs))
但是,对于namedtuple
(我目前正在使用它)来说,这种情况很快就会崩溃——因为据我所知namedtuple
需要用解包语法或调用它的_make
函数来构造。而且,namedtuple
是const,所以我不能遍历所有条目,而只是更改它们。你知道吗
使用dict
会产生更多的问题。你知道吗
有没有一种通用的方法来表达这样一个apply
函数,它适用于所有可iterable的东西?你知道吗
我有预感你是哈斯克尔人,对吗?(我猜是因为您使用
f
和xs
作为变量名。)在Haskell中,您的问题的答案是“是的,它被称为fmap
,但它只适用于定义了Functor实例的类型。”另一方面,Python没有“Functor”的一般概念,所以严格地说,答案是否定的。要得到这样的东西,就必须依赖Python提供的其他抽象。你知道吗
ABCs救援
一种相当普遍的方法是使用abstract base classes。它们提供了一种结构化的方法来指定和检查特定的接口。Functor typeclass的Pythonic版本是一个抽象基类,它定义了一个特殊的
fmap
方法,允许单个类指定如何映射它们。但是没有这样的东西存在。(不过,我认为这将是对Python的一个非常酷的补充!)你知道吗现在,您可以定义自己的抽象基类,这样就可以创建一个需要
fmap
接口的函子ABC,但是您仍然需要编写自己的list
、dict
等的所有函数化子类,所以这并不是很理想。你知道吗更好的方法是使用现有接口拼凑出一个似乎合理的通用映射定义。您必须非常仔细地考虑需要组合现有接口的哪些方面。仅仅检查一个类型是否定义了
__iter__
是不够的,因为正如您已经看到的,类型的迭代定义不一定转化为构造定义。例如,对字典进行迭代只会得到关键字,但要以这种精确的方式映射字典,则需要对项进行迭代。你知道吗具体例子
这里是一个抽象基类方法,它包括
namedtuple
的特殊情况和三个抽象基类Sequence
、Mapping
和Set
。对于以预期方式定义上述任何接口的任何类型,它都将按预期的方式运行。然后返回到iterables的一般行为。在后一种情况下,输出的类型与输入的类型不同,但至少可以工作。你知道吗我将其定义为ABC,因为这样可以创建从它继承的新类。但您也可以在任何类的现有实例上调用它,它的行为将与预期的一样。您也可以将上面的
map
方法用作独立函数。你知道吗定义ABC最酷的一点是,您可以将它用作“mix-in”。下面是一个
MappablePoint
派生自Point
名称为duple的MappablePoint
:您还可以根据Azat Ibrakov's answer,使用
functools.singledispatch
装饰器稍微修改这种方法。(这对我来说是个新鲜事,他应该得到这部分答案的全部信任,但为了完整起见,我想我还是把它写下来吧。)这看起来像下面这样。注意,我们仍然必须使用特殊情况
namedtuple
,因为它们破坏了元组构造函数接口。以前我并不介意,但现在我觉得这是一个非常恼人的设计缺陷。另外,我设置了一些东西,以便最终的fmap
函数使用预期的参数顺序。(我想用mmap
而不是fmap
,因为“Mappable”比“Functor”IMO更像python,但是mmap
已经是一个内置库了!该死的。)一些测试:
关于中断接口的最后说明
这两种方法都不能保证这将适用于所有被认为是
Sequence
的事物,以此类推,因为ABC机制不检查函数签名。这不仅是构造函数的问题,也是所有其他方法的问题。如果没有类型注释,这是不可避免的。你知道吗但实际上,这可能并不重要。如果您发现自己使用的工具以奇怪的方式打破了接口约定,请考虑唱一首不同的歌。(实际上,我想说的是,
namedtuple
也是如此,因为我非常喜欢它们!)这就是许多Python设计决策背后的consenting adults哲学,在过去的几十年中,它一直运行得很好。你知道吗看起来是^{} decorator 的完美任务:
之后
apply
函数可以简单地使用如下虽然我不知道
dict
对象需要什么样的行为,但是这种方法的伟大之处在于它很容易扩展。你知道吗相关问题 更多 >
编程相关推荐