Can You Monkey Patch Methods on Core Types in Python

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:

  1. 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.

  2. 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 original get_data. (Why? Python just rebinds the name get_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



Leave a reply



Submit