Can you monkey patch methods on core types in Python?
Solution 1
What exactly do you mean by Monkey Patch here? There are several slightly different definitions.
If you mean, "can you change a class's methods at runtime?", then the answer is emphatically yes:
class Foo:
pass # dummy class
Foo.bar = lambda self: 42
x = Foo()
print x.bar()
If you mean, "can you change a class's methods at runtime and make all of the instances of that class change after-the-fact?" then the answer is yes as well. Just change the order slightly:
class Foo:
pass # dummy class
x = Foo()
Foo.bar = lambda self: 42
print x.bar()
But you can't do this for certain built-in classes, like int
or float
. These classes' methods are implemented in C and there are certain abstractions sacrificed in order to make the implementation easier and more efficient.
I'm not really clear on why you would want to alter the behavior of the built-in numeric classes anyway. If you need to alter their behavior, subclass them!!
Solution 2
No, you cannot. In Python, all data (classes, methods, functions, etc) defined in C extension modules (including builtins) are immutable. This is because C modules are shared between multiple interpreters in the same process, so monkeypatching them would also affect unrelated interpreters in the same process. (Multiple interpreters in the same process are possible through the C API, and there has been some effort towards making them usable at Python level.)
However, classes defined in Python code may be monkeypatched because they are local to that interpreter.
Solution 3
def should_equal_def(self, value):
if self != value:
raise ValueError, "%r should equal %r" % (self, value)
class MyPatchedInt(int):
should_equal=should_equal_def
class MyPatchedStr(str):
should_equal=should_equal_def
import __builtin__
__builtin__.str = MyPatchedStr
__builtin__.int = MyPatchedInt
int(1).should_equal(1)
str("44").should_equal("44")
Have fun ;)
Solution 4
You can do this, but it takes a little bit of hacking. Fortunately, there's a module now called "Forbidden Fruit" that gives you the power to patch methods of built-in types very simply. You can find it at
http://clarete.github.io/forbiddenfruit/?goback=.gde_50788_member_228887816
or
https://pypi.python.org/pypi/forbiddenfruit/0.1.0
With the original question example, after you write the "should_equal" function, you'd just do
from forbiddenfruit import curse
curse(int, "should_equal", should_equal)
and you're good to go! There's also a "reverse" function to remove a patched method.
Solution 5
Python's core types are immutable by design, as other users have pointed out:
>>> int.frobnicate = lambda self: whatever()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'int'
You certainly could achieve the effect you describe by making a subclass, since user-defined types in Python are mutable by default.
>>> class MyInt(int):
... def frobnicate(self):
... print 'frobnicating %r' % self
...
>>> five = MyInt(5)
>>> five.frobnicate()
frobnicating 5
>>> five + 8
13
There's no need to make the MyInt
subclass public, either; one could just as well define it inline directly in the function or method that constructs the instance.
There are certainly a few situations where Python programmers who are fluent in the idiom consider this sort of subclassing the right thing to do. For instance, os.stat()
returns a tuple
subclass that adds named members, precisely in order to address the sort of readability concern you refer to in your example.
>>> import os
>>> st = os.stat('.')
>>> st
(16877, 34996226, 65024L, 69, 1000, 1000, 4096, 1223697425, 1223699268, 1223699268)
>>> st[6]
4096
>>> st.st_size
4096
That said, in the specific example you give, I don't believe that subclassing float
in item.price
(or elsewhere) would be very likely to be considered the Pythonic thing to do. I can easily imagine somebody deciding to add a price_should_equal()
method to item
if that were the primary use case; if one were looking for something more general, perhaps it might make more sense to use named arguments to make the intended meaning clearer, as in
should_equal(observed=item.price, expected=19.99)
or something along those lines. It's a bit verbose, but no doubt it could be improved upon. A possible advantage to such an approach over Ruby-style monkey-patching is that should_equal()
could easily perform its comparison on any type, not just int
or float
. But perhaps I'm getting too caught up in the details of the particular example that you happened to provide.
airportyh
Some favorite languages: Javascript, Python, Ruby, Haskell.
Updated on July 09, 2022Comments
-
airportyh almost 2 years
Ruby can add methods to the Number class and other core types to get effects like this:
1.should_equal(1)
But it seems like Python cannot do this. Is this true? And if so, why? Does it have something to do with the fact that type can't be modified?
Update: Rather than talking about different definitions of monkey patching, I would like to just focus on the example above. I have already concluded that it cannot be done as a few of you have answered. But I would like a more detailed explanation of why it cannot be done, and maybe what feature, if available in Python, would allow this.
To answer some of you: The reason I might want to do this is simply aesthetics/readability.
item.price.should_equal(19.99)
This reads more like English and clearly indicates which is the tested value and which is the expected value, as supposed to:
should_equal(item.price, 19.99)
This concept is what Rspec and some other Ruby frameworks are based on.
-
Jakob Bowyer over 12 yearsIts very wrong because it makes programmers on other projects want you DEAD. Imagine I import your big ass library and you monkeypatch int. Now what happens if int doesn't behave like I expect it to?
-
michel-slm almost 12 yearsNice, thanks! Here's the Pipe project page on PyPI: pypi.python.org/pypi/pipe
-
Hanfei Sun about 11 years@JakobBowyer I think it's very wrong only when you use
from HisPatch import *
-
dreftymac over 10 yearsNice addition, although this is not within core python, so that has drawbacks for people who are not authorized to import custom libraries
-
Tobias Kienzler over 9 years@Firegun The problem is, any import from
HisPatch
would cause that code to be executed and globally modify the builtin classes. Not that I didn't consider using something like this... -
ArtOfWarfare almost 9 yearsSeems weird. Some stuff I try works just fine. Other stuff I try seems to just get ignored. Can't find any pattern, rhyme, or reason for it.
-
holbech over 8 yearsThis doesn't appear to work for all cases. Try replacing builtin.list, for instance, and then look at the object you get from range(4). It is of the same old built in Pythin list type.
-
Boris Verkhovskiy about 5 years@AlexanderLjungberg the place to suggest changes to Python is bugs.python.org (or better yet, working code submitted to their git repository), not stackoverflow.
-
kmader almost 5 yearsvery cool! it even works with list (which many solutions do not) letting you chain operations like javascript/scala in Python
-
Martin Thoma almost 4 yearsI would expect he means pytest monkeypatch
-
Dan Lenski almost 4 yearsIt certainly does not mean that, @MartinThoma. Did you read the question, and also note that it was asked in 2008?
-
mtraceur almost 3 yearsThis answer is incomplete nowadays without mentioning
forbiddenfruit
. -
mtraceur almost 3 yearsThis answer is incomplete nowadays without mentioning
forbiddenfruit
. -
astralwolf over 2 yearsYou say that
builtins
are immutable, but ins'tlist
a mutable type and a builtin? -
Dan Lenski over 2 yearsMy thoughts regarding
forbiddenfruit
… (1) wow, that's clever, and (2) yikes 😳🙈. -
Grant Gryczan almost 2 years@astralwolf
list
is immutable.list
s are mutable. There is a distinction between the class and its instances.