如何在python中增强内置函数的功能?

时间:2013-06-13 22:42:26

标签: python python-2.7 python-3.x

我是python编程的新手, 我想知道如何增强内置函数的功能(Monkeypatch

例如

我知道sum()内置函数只允许使用数字项

>>> sum([4,5,6,7]) #22

我想将sum函数设为允许项目列表为字符串,如下所示

例如

>>> sum(['s','t','a','c','k']) # 'stack'

提前致谢

5 个答案:

答案 0 :(得分:12)

你不能像对象,对象,模块等那样“monkeypatch”一个函数。

其他所有东西最终都归结为属性集合,因此用不同的属性替换一个属性或添加新属性既简单又有用。另一方面,函数基本上是原子的东西。*

当然,您可以通过替换sum函数来monkeypatch builtins模块。但我不认为这就是你所要求的。 (如果你是,见下文。)

无论如何,你不能修补sum,但你可以编写一个新的函数,如果你想要的话,可以使用相同的名称,(可能包含原始函数的包装器 - 你会注意到,究竟装饰者做了什么。)


但实际上没有办法使用sum(['s','t','a','c','k'])来做你想做的事情,因为默认情况下sum从0开始并添加内容。并且您不能将字符串添加到0。**

当然,您始终可以传递明确的start而不是默认值,但您必须更改调用代码才能发送相应的start。在某些情况下(例如,您发送文字列表显示的地方),这很明显;在其他情况下(例如,在通用功能中)它可能不是。这仍然不适用于此,因为sum(['s','t','a','c','k'], '')只会引发TypeError(尝试并阅读错误以了解原因),但在其他情况下也可以。

但是没有办法避免必须使用sum知道合适的起始值,因为这就是sum的工作方式。

如果您考虑一下,sum在概念上等同于:

def sum(iterable, start=0):
    reduce(operator.add, iterable, start)

这里唯一真正的问题是start,对吧? reduce允许您保留起始值,它将以iterable中的第一个值开头:

>>> reduce(operator.add, ['s', 't', 'a', 'c', 'k'])
'stack'

这是sum无法做到的事情。但是,如果您真的想要,可以重新定义sum,以便可以

>>> def sum(iterable):
...     return reduce(operator.add, iterable)

......或:

>>> sentinel = object()
>>> def sum(iterable, start=sentinel):
...     if start is sentinel:
...         return reduce(operator.add, iterable)
...     else:
...         return reduce(operator.add, iterable, start)

但请注意,这个sum在整数上要比原始值慢得多,它会在空序列上引发TypeError而不是返回0,依此类推。


如果你确实想要monkeypatch内置函数(而不是仅仅使用新名称定义一个新函数,或者在模块的globals()中使用同名的新函数来隐藏内置函数),这里是一个适用于Python 3.1+的示例,只要您的模块使用普通的全局字典(除非您在嵌入式解释器或exec调用或类似操作中运行,否则它们将一直存在):

import builtins
builtins.sum = _new_sum

换句话说,就像monkeypatching任何其他模块一样。

在2.x中,模块称为__builtin__。关于它如何通过全局变量访问的规则在2.3左右变化,在3.0中再变化。有关详细信息,请参阅builtins / __builtin__


*当然不是相当是真的。函数在其代码对象之上具有名称,闭包单元列表,文档字符串等。甚至代码对象也是一系列字节码,您可以使用bytecodehacks或硬编码的hackery。除了sum实际上是一个内置函数,而不是一个函数,因此它甚至没有可以从Python访问的代码......无论如何,它足够接近大多数目的来说函数是原子的东西。

**当然,你可以将字符串转换为知道如何将自身添加到整数的子类(通过忽略它们),但实际上,你不想这样做。

答案 1 :(得分:5)

不是完全修补猴子,只是重新定义sum以使其适用于字符串。

>>> import __builtin__
def sum(seq, start = 0):
    if all(isinstance(x,str) for x in seq):
        return "".join(seq)
    else:
        return __builtin__.sum(seq, start)
...     
>>> sum([4,5,6,7])
22
>>> sum(['s','t','a','c','k'])
'stack'

答案 2 :(得分:3)

要做你想做的事,你应该使用str.join

"".join(['s','t','a','c','k'])

猴子修补是可能的,但在Python中不赞成,特别是对于像这样的琐碎事情。它会让你的代码更难阅读,因为标准的库函数会做出意想不到的事情。

但是,如果你真的想,你可以重新定义这个功能。 Python不会阻止你:

def sum(l):
    return "".join(l)

Python将允许您对现有模块执行任何操作:

import sys
sys.stdout = open("somefile", "w")

但是,你不应该。

答案 3 :(得分:3)

sum已经适用于定义__add__函数的任何内容。第二个参数是起始点,默认为0,但您可以将其替换为任何正在求和的“无”版本。例如,从空列表开始添加列表列表:

sum([[1, 2, 3], [4, 5, 6]], [])

返回:

[1, 2, 3, 4, 5, 6]

通常,这实际上会起作用:

sum(['s','t','a','c','k'], '')

但是这引发了一个异常,它特别告诉你对字符串使用join。可能是因为它表现得更好。

答案 4 :(得分:1)

比请求更容易请求宽恕:

import __builtin__
def sum(seq, start = 0):
    try:
        return "".join(seq)
    except TypeError:
        return __builtin__.sum(seq, start)
...     
>>> sum([4,5,6,7])
22
>>> sum(['s','t','a','c','k'])
'stack'

请原谅我,如果这看起来像我刚刚复制了别人的大部分回答。 :)

但严重的是,你应该使用''.join()而不是@nmclean在一个不太理解的答案中解释过。