我是 python 编程新手,我想知道如何增强内置函数的功能(Monkeypatch)
例如
我知道sum()内置函数只允许在数字项目上
>>> sum([4,5,6,7]) #22
我想让 sum 函数允许将项目列表作为字符串,如下所示
例如
>>> sum(['s','t','a','c','k']) # 'stack'
提前致谢
我是 python 编程新手,我想知道如何增强内置函数的功能(Monkeypatch)
例如
我知道sum()内置函数只允许在数字项目上
>>> sum([4,5,6,7]) #22
我想让 sum 函数允许将项目列表作为字符串,如下所示
例如
>>> sum(['s','t','a','c','k']) # 'stack'
提前致谢
你不能像你对一个类、对象、模块等那样真正地“monkeypatch”一个函数。
那些其他的东西最终都归结为属性的集合,因此用不同的属性替换一个属性,或者添加一个新属性,既简单又有用。另一方面,函数基本上是原子的东西。*
当然,您可以通过替换sum
函数来对内置模块进行猴子补丁。但我认为这不是你要问的。(如果你是,请参见下文。)
无论如何,你不能 patch sum
,但你可以编写一个新函数,如果你愿意的话,可以使用相同的名称,(可能在原始函数周围加上一个包装器——你会注意到,这正是装饰器所做的)。
但是真的没有办法用来sum(['s','t','a','c','k'])
做你想做的事,因为sum
默认情况下从 0 开始并添加东西。而且您不能将字符串添加到 0.**
当然,您始终可以传递显式start
而不是使用默认值,但您必须更改调用代码以发送适当的start
. 在某些情况下(例如,您要发送文字列表显示),这很明显;在其他情况下(例如,在通用函数中)它可能不是。这在这里仍然行不通,因为sum(['s','t','a','c','k'], '')
只会引发 a TypeError
(尝试并阅读错误以了解原因),但在其他情况下它会起作用。
但是没有办法避免必须知道一个适当的起始值sum
,因为这就是sum
工作原理。
如果您考虑一下,sum
在概念上等同于:
def sum(iterable, start=0):
reduce(operator.add, iterable, start)
这里唯一真正的问题是start
,对吧?reduce
允许您省略起始值,它将从可迭代的第一个值开始:
>>> 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
在整数上会比原来的慢得多,并且它会引发 a而不是在空序列上TypeError
返回,依此类推。0
如果你真的想对内置函数进行猴子补丁(而不是仅仅定义一个具有新名称的新函数,或者在你的模块中定义一个具有相同名称的新函数来隐藏内置函数globals()
),这里有一个适用于 Python 3.1+ 的示例,只要您的模块使用普通的全局字典(除非您在嵌入式解释器或exec
调用或类似程序中运行,否则它们将是):
import builtins
builtins.sum = _new_sum
换句话说,与猴子修补任何其他模块相同。
在 2.x 中,该模块被称为__builtin__
. 通过全局变量访问它的规则在 2.3 左右发生了变化,在 3.0 中又发生了变化。详情见builtins
/ __builtin__
。
* 当然这并不完全正确。函数在其代码对象之上具有名称、闭包单元列表、文档字符串等。甚至代码对象也是一系列字节码,你可以在上面使用bytecodehacks
或硬编码hackery。除了它sum
实际上是一个内置函数,而不是一个函数,所以它甚至没有可以从 Python 访问的代码......无论如何,对于大多数用途来说,它已经足够接近,可以说函数是原子的东西。
** 当然,您可以将字符串转换为某个知道如何将自身添加到整数的子类(通过忽略它们),但实际上,您不想这样做。
不完全是猴子修补,只是重新定义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'
做你想做的事,你应该使用str.join
:
"".join(['s','t','a','c','k'])
在 Python 中,猴子补丁是可能的,但不赞成,尤其是对于像这样的琐碎事情。它会让你的代码更难阅读,因为标准库函数会做意想不到的事情。
但是,如果你真的想要,你可以重新定义函数。Python 不会阻止你:
def sum(l):
return "".join(l)
Python 会让你对现有模块做任何你想做的事情:
import sys
sys.stdout = open("somefile", "w")
但同样,你不应该。
sum
已经适用于任何定义__add__
函数的东西。第二个参数是起点,默认为 0,但您可以将其替换为“无”版本的求和。例如,将一个列表列表相加,从一个空列表开始:
sum([[1, 2, 3], [4, 5, 6]], [])
返回:
[1, 2, 3, 4, 5, 6]
所以通常,这实际上会起作用:
sum(['s','t','a','c','k'], '')
但这会引发一个异常,它专门告诉您使用join
字符串。可能是因为它的性能更好。
请求宽恕比许可更容易:
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 在一个不受欢迎的答案中解释的那样。