I am new to python programming, i like to know how can i enhance the features of builtin functions( Monkeypatch )
for example
i know sum() built in function allowed only on number items
>>> sum([4,5,6,7]) #22
i would like to make sum function to be allow list of items as strings as shown below
for example
>>> sum(['s','t','a','c','k']) # 'stack'
Thanks in advance
You can't really "monkeypatch" a function the way you can a class, object, module, etc.
Those other things all ultimately come down to a collection of attributes, so replacing one attribute with a different one, or adding a new one, is both easy and useful. Functions, on the other hand, are basically atomic things.*
You can, of course, monkeypatch the builtins module by replacing the sum
function. But I don't think that's what you were asking. (If you were, see below.)
Anyway, you can't patch sum
, but you can write a new function, with the same name if you want, (possibly with a wrapper around the original function—which, you'll notice, is exactly what decorators do).
But there is really no way to use sum(['s','t','a','c','k'])
to do what you want, because sum
by default starts off with 0 and adds things to it. And you can't add a string to 0.**
Of course you can always pass an explicit start
instead of using the default, but you'd have to change your calling code to send the appropriate start
. In some cases (eg, where you're sending a literal list display) it's pretty obvious; in other cases (eg, in a generic function) it may not be. That still won't work here, because sum(['s','t','a','c','k'], '')
will just raise a TypeError
(try it and read the error to see why), but it will work in other cases.
But there is no way to avoid having to know an appropriate starting value with sum
, because that's how sum
works.
If you think about it, sum
is conceptually equivalent to:
def sum(iterable, start=0):
reduce(operator.add, iterable, start)
The only real problem here is that start
, right? reduce
allows you to leave off the start value, and it will start with the first value in the iterable:
>>> reduce(operator.add, ['s', 't', 'a', 'c', 'k'])
'stack'
That's something sum
can't do. But, if you really want to, you can redefine sum
so it can :
>>> def sum(iterable):
... return reduce(operator.add, iterable)
… or:
>>> sentinel = object()
>>> def sum(iterable, start=sentinel):
... if start is sentinel:
... return reduce(operator.add, iterable)
... else:
... return reduce(operator.add, iterable, start)
But note that this sum
will be much slower on integers than the original one, and it will raise a TypeError
instead of returning 0
on an empty sequence, and so on.
If you really do want to monkeypatch the builtins (as opposed to just defining a new function with a new name, or a new function with the same name in your module's globals()
that shadows the builtin), here's an example that works for Python 3.1+, as long as your modules are using normal globals dictionaries (which they will be unless you're running in an embedded interpreter or an exec
call or similar):
import builtins
builtins.sum = _new_sum
In other words, same as monkeypatching any other module.
In 2.x, the module is called __builtin__
. And the rules for how it's accessed through globals changed somewhere around 2.3 and again in 3.0. See builtins
/ __builtin__
for details.
* Of course that isn't quite true. A function has a name, a list of closure cells, a doc string, etc. on top of its code object. And even the code object is a sequence of bytecodes, and you can use bytecodehacks
or hard-coded hackery on that. Except that sum
is actually a builtin-function, not a function, so it doesn't even have code accessible from Python… Anyway, it's close enough for most purposes to say that functions are atomic things.
** Sure, you could convert the string to some subclass that knows how to add itself to integers (by ignoring them), but really, you don't want to.
Not monkey patching exactly, just re-defined sum
to make it work for strings as well.
>>> 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'
To do what you want, you should use str.join
:
"".join(['s','t','a','c','k'])
Monkey patching is possible, but frowned upon, in Python, especially for trivial things like this. It will make you code harder to read, because standard library functions will do unexpected things.
But, if you really want to, you can just redefine the function. Python won't stop you:
def sum(l):
return "".join(l)
Python will let you do whatever you want to existing modules:
import sys
sys.stdout = open("somefile", "w")
But again, you shouldn't.
sum
already works with anything that defines an __add__
function. The second parameter is the starting point, which defaults to 0, but you can replace it with the "nothing" version of whatever you're summing. For example, adding together a list of lists, starting with an empty list:
sum([[1, 2, 3], [4, 5, 6]], [])
returns:
[1, 2, 3, 4, 5, 6]
So normally, this would actually work:
sum(['s','t','a','c','k'], '')
but this raises an exception which specifically tells you to use join
for strings. Probably because it performs better.
Easier to ask forgiveness than permission:
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'
Please forgive me if this looks like I just copied most of someone else's answer. :)
But seriously, you should just use ''.join()
instead as @nmclean has explained in an under appreciated answer.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.