Can you monkey patch methods on core types in Python?
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!!
Can you monkey-patch built in classes and if not, how do I overload an operator to define addition for two different classes?
As for your first question: it's essentially impossible to alter built-in classes. Although you can mess with other classes that is generally a terrible idea. Instead, you can make a subclass that has the property that you want.
For example:
class mystr(str):
def __add__(self, other):
return mystr(str(self) + str(other))
This code inherits all properties from the str
class, except for the one we want to change, namely its addition behavior. By casting self
to str
we still delegate to str
's addition, but we also cast the other argument to str
to get the behavior you described.
Finally, we cast back to mystr
so that we don't end up with a str
.
Now we can do the following:
>>> some_string = mystr("abc")
>>> some_string + 4
"abc4"
How to monkey patch an instance method of an external library that uses other instance methods internally?
You need to import your class and just replace the method with a modified method.
In python you can replace a method dynamically as you wish.
Example:
from x.y.z import Client
def mynewconnect(self):
# your code here
self.url = "..."
pass
Client.connect = mynewconnect
How to monkeypatch dunder methods to existing instances?
You can try monkeypatching the dunder by changing the __class__
property of the instance. As explained in by docs section Special method lookup:
For custom classes, implicit invocations of special methods are only
guaranteed to work correctly if defined on an object’s type, not in
the object’s instance dictionary.
def patch_call(instance, func, memo={}):
if type(instance) not in memo:
class _(type(instance)):
def __lt__(self, *arg, **kwargs):
return func(self, *arg, **kwargs)
memo[type(instance)] = _
instance.__class__ = memo[type(instance)]
patch_call(m, myLT)
patch_call(n, myLT)
n < m
# True
Modified from reference.
Thanks to @juanpa.arrivilaga for recommending the classes be cached to improve performance.
What is monkey patching?
No, it's not like any of those things. It's simply the dynamic replacement of attributes at runtime.
For instance, consider a class that has a method get_data
. This method does an external lookup (on a database or web API, for example), and various other methods in the class call it. However, in a unit test, you don't want to depend on the external data source - so you dynamically replace the get_data
method with a stub that returns some fixed data.
Because Python classes are mutable, and methods are just attributes of the class, you can do this as much as you like - and, in fact, you can even replace classes and functions in a module in exactly the same way.
But, as a commenter pointed out, use caution when monkeypatching:
If anything else besides your test logic calls
get_data
as well, it will also call your monkey-patched replacement rather than the original -- which can be good or bad. Just beware.If some variable or attribute exists that also points to the
get_data
function by the time you replace it, this alias will not change its meaning and will continue to point to the originalget_data
. (Why? Python just rebinds the nameget_data
in your class to some other function object; other name bindings are not impacted at all.)
Monkey patching a class in another module in Python
The following should work:
import thirdpartymodule_a
import thirdpartymodule_b
def new_init(self):
self.a = 43
thirdpartymodule_a.SomeClass.__init__ = new_init
thirdpartymodule_b.dosomething()
If you want the new init to call the old init replace the new_init()
definition with the following:
old_init = thirdpartymodule_a.SomeClass.__init__
def new_init(self, *k, **kw):
old_init(self, *k, **kw)
self.a = 43
To monkey-patch or not to?
Don't!
Especially with free software, you have all the possibilities out there to get your changes into the main distribution. But if you have a weakly documented hack in your local copy you'll never be able to ship the product and upgrading to the next version of curses (security updates anyone) will be very high cost.
See this answer for a glimpse into what is possible on foreign code bases. The linked screencast is really worth a watch. Suddenly a dirty hack turns into a valuable contribution.
If you really cannot get the patch upstream for whatever reason, at least create a local (git) repo to track upstream and have your changes in a separate branch.
Recently I've come across a point where I have to accept monkey-patching as last resort: Puppet is a "run-everywhere" piece of ruby code. Since the agent has to run on - potentially certified - systems, it cannot require a specific ruby version. Some of those have bugs that can be worked around by monkey-patching select methods in the runtime. These patches are version-specific, contained, and the target is frozen. I see no other alternative there.
Monkey Patching in Python using super.__init__
You have to monkey patch the original Message object that pyrogram uses.
from pyrogram.types import Message
@property
def test(self):
return "test"
Message.test = test
If you really wanna update the Message class when changing the subclass (NOT RECOMMENDED!) you can do this:
from pyrogram.types import Message
Message.__init_subclass__ = classmethod(lambda sub: [setattr(Message, k, v) for (k,v) in sub.__dict__.items() if k[:2] != '__'])
class MyMessage(Message):
def __init__(self, **kwargs):
super().__init__(**kwargs)
@property
def test(self):
return "test"
Related Topics
How to Test That a Python Function Throws an Exception
What Are the 'Levels', 'Keys', and Names Arguments for in Pandas' Concat Function
Pylint "Unresolved Import" Error in Visual Studio Code
Check If Something Is (Not) in a List in Python
Saving and Loading Multiple Objects in Pickle File
Retrieving Parameters from a Url
Getting Syntaxerror for Print with Keyword Argument End=' '
Pandas Long to Wide Reshape, by Two Variables
How to Find Median and Quantiles Using Spark
Secondary Axis with Twinx(): How to Add to Legend
Getting Distance Between Two Points Based on Latitude/Longitude
Sqlite Parameter Substitution Problem
Temporarily Redirect Stdout/Stderr
How to Check for Valid Email Address
Set Colorbar Range in Matplotlib