When Monkey Patching an Instance Method, Can You Call the Overridden Method from the New Implementation

When monkey patching an instance method, can you call the overridden method from the new implementation?

EDIT: It has been 9 years since I originally wrote this answer, and it deserves some cosmetic surgery to keep it current.

You can see the last version before the edit here.


You can’t call the overwritten method by name or keyword. That’s one of the many reasons why monkey patching should be avoided and inheritance be preferred instead, since obviously you can call the overridden method.

Avoiding Monkey Patching

Inheritance

So, if at all possible, you should prefer something like this:

class Foo
def bar
'Hello'
end
end

class ExtendedFoo < Foo
def bar
super + ' World'
end
end

ExtendedFoo.new.bar # => 'Hello World'

This works, if you control creation of the Foo objects. Just change every place which creates a Foo to instead create an ExtendedFoo. This works even better if you use the Dependency Injection Design Pattern, the Factory Method Design Pattern, the Abstract Factory Design Pattern or something along those lines, because in that case, there is only place you need to change.

Delegation

If you do not control creation of the Foo objects, for example because they are created by a framework that is outside of your control (like ruby-on-rails for example), then you could use the Wrapper Design Pattern:

require 'delegate'

class Foo
def bar
'Hello'
end
end

class WrappedFoo < DelegateClass(Foo)
def initialize(wrapped_foo)
super
end

def bar
super + ' World'
end
end

foo = Foo.new # this is not actually in your code, it comes from somewhere else

wrapped_foo = WrappedFoo.new(foo) # this is under your control

wrapped_foo.bar # => 'Hello World'

Basically, at the boundary of the system, where the Foo object comes into your code, you wrap it into another object, and then use that object instead of the original one everywhere else in your code.

This uses the Object#DelegateClass helper method from the delegate library in the stdlib.

“Clean” Monkey Patching

Module#prepend: Mixin Prepending

The two methods above require changing the system to avoid monkey patching. This section shows the preferred and least invasive method of monkey patching, should changing the system not be an option.

Module#prepend was added to support more or less exactly this use case. Module#prepend does the same thing as Module#include, except it mixes in the mixin directly below the class:

class Foo
def bar
'Hello'
end
end

module FooExtensions
def bar
super + ' World'
end
end

class Foo
prepend FooExtensions
end

Foo.new.bar # => 'Hello World'

Note: I also wrote a little bit about Module#prepend in this question: Ruby module prepend vs derivation

Mixin Inheritance (broken)

I have seen some people try (and ask about why it doesn’t work here on StackOverflow) something like this, i.e. includeing a mixin instead of prepending it:

class Foo
def bar
'Hello'
end
end

module FooExtensions
def bar
super + ' World'
end
end

class Foo
include FooExtensions
end

Unfortunately, that won’t work. It’s a good idea, because it uses inheritance, which means that you can use super. However, Module#include inserts the mixin above the class in the inheritance hierarchy, which means that FooExtensions#bar will never be called (and if it were called, the super would not actually refer to Foo#bar but rather to Object#bar which doesn’t exist), since Foo#bar will always be found first.

Method Wrapping

The big question is: how can we hold on to the bar method, without actually keeping around an actual method? The answer lies, as it does so often, in functional programming. We get a hold of the method as an actual object, and we use a closure (i.e. a block) to make sure that we and only we hold on to that object:

class Foo
def bar
'Hello'
end
end

class Foo
old_bar = instance_method(:bar)

define_method(:bar) do
old_bar.bind(self).() + ' World'
end
end

Foo.new.bar # => 'Hello World'

This is very clean: since old_bar is just a local variable, it will go out of scope at the end of the class body, and it is impossible to access it from anywhere, even using reflection! And since Module#define_method takes a block, and blocks close over their surrounding lexical environment (which is why we are using define_method instead of def here), it (and only it) will still have access to old_bar, even after it has gone out of scope.

Short explanation:

old_bar = instance_method(:bar)

Here we are wrapping the bar method into an UnboundMethod method object and assigning it to the local variable old_bar. This means, we now have a way to hold on to bar even after it has been overwritten.

old_bar.bind(self)

This is a bit tricky. Basically, in Ruby (and in pretty much all single-dispatch based OO languages), a method is bound to a specific receiver object, called self in Ruby. In other words: a method always knows what object it was called on, it knows what its self is. But, we grabbed the method directly from a class, how does it know what its self is?

Well, it doesn’t, which is why we need to bind our UnboundMethod to an object first, which will return a Method object that we can then call. (UnboundMethods cannot be called, because they don’t know what to do without knowing their self.)

And what do we bind it to? We simply bind it to ourselves, that way it will behave exactly like the original bar would have!

Lastly, we need to call the Method that is returned from bind. In Ruby 1.9, there is some nifty new syntax for that (.()), but if you are on 1.8, you can simply use the call method; that’s what .() gets translated to anyway.

Here are a couple of other questions, where some of those concepts are explained:

  • How do I reference a function in Ruby?
  • Is Ruby’s code block same as C♯’s lambda expression?

“Dirty” Monkey Patching

alias_method chain

The problem we are having with our monkey patching is that when we overwrite the method, the method is gone, so we cannot call it anymore. So, let’s just make a backup copy!

class Foo
def bar
'Hello'
end
end

class Foo
alias_method :old_bar, :bar

def bar
old_bar + ' World'
end
end

Foo.new.bar # => 'Hello World'
Foo.new.old_bar # => 'Hello'

The problem with this is that we have now polluted the namespace with a superfluous old_bar method. This method will show up in our documentation, it will show up in code completion in our IDEs, it will show up during reflection. Also, it still can be called, but presumably we monkey patched it, because we didn’t like its behavior in the first place, so we might not want other people to call it.

Despite the fact that this has some undesirable properties, it has unfortunately become popularized through AciveSupport’s Module#alias_method_chain.

An aside: Refinements

In case you only need the different behavior in a few specific places and not throughout the whole system, you can use Refinements to restrict the monkey patch to a specific scope. I am going to demonstrate it here using the Module#prepend example from above:

class Foo
def bar
'Hello'
end
end

module ExtendedFoo
module FooExtensions
def bar
super + ' World'
end
end

refine Foo do
prepend FooExtensions
end
end

Foo.new.bar # => 'Hello'
# We haven’t activated our Refinement yet!

using ExtendedFoo
# Activate our Refinement

Foo.new.bar # => 'Hello World'
# There it is!

You can see a more sophisticated example of using Refinements in this question: How to enable monkey patch for specific method?


Abandoned ideas

Before the Ruby community settled on Module#prepend, there were multiple different ideas floating around that you may occasionally see referenced in older discussions. All of these are subsumed by Module#prepend.

Method Combinators

One idea was the idea of method combinators from CLOS. This is basically a very lightweight version of a subset of Aspect-Oriented Programming.

Using syntax like

class Foo
def bar:before
# will always run before bar, when bar is called
end

def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end

you would be able to “hook into” the execution of the bar method.

It is however not quite clear if and how you get access to bar’s return value within bar:after. Maybe we could (ab)use the super keyword?

class Foo
def bar
'Hello'
end
end

class Foo
def bar:after
super + ' World'
end
end

Replacement

The before combinator is equivalent to prepending a mixin with an overriding method that calls super at the very end of the method. Likewise, the after combinator is equivalent to prepending a mixin with an overriding method that calls super at the very beginning of the method.

You can also do stuff before and after calling super, you can call super multiple times, and both retrieve and manipulate super’s return value, making prepend more powerful than method combinators.

class Foo
def bar:before
# will always run before bar, when bar is called
end
end

# is the same as

module BarBefore
def bar
# will always run before bar, when bar is called
super
end
end

class Foo
prepend BarBefore
end

and

class Foo
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end

# is the same as

class BarAfter
def bar
original_return_value = super
# will always run after bar, when bar is called
# has access to and can change bar’s return value
end
end

class Foo
prepend BarAfter
end

old keyword

This idea adds a new keyword similar to super, which allows you to call the overwritten method the same way super lets you call the overridden method:

class Foo
def bar
'Hello'
end
end

class Foo
def bar
old + ' World'
end
end

Foo.new.bar # => 'Hello World'

The main problem with this is that it is backwards incompatible: if you have method called old, you will no longer be able to call it!

Replacement

super in an overriding method in a prepended mixin is essentially the same as old in this proposal.

redef keyword

Similar to above, but instead of adding a new keyword for calling the overwritten method and leaving def alone, we add a new keyword for redefining methods. This is backwards compatible, since the syntax currently is illegal anyway:

class Foo
def bar
'Hello'
end
end

class Foo
redef bar
old + ' World'
end
end

Foo.new.bar # => 'Hello World'

Instead of adding two new keywords, we could also redefine the meaning of super inside redef:

class Foo
def bar
'Hello'
end
end

class Foo
redef bar
super + ' World'
end
end

Foo.new.bar # => 'Hello World'

Replacement

redefining a method is equivalent to overriding the method in a prepended mixin. super in the overriding method behaves like super or old in this proposal.

python monkey patch a new class and import it

This is because of the import system.

Reading through the doc, you can find this paragraph:

[...] the statement from spam.ham import eggs, sausage as saus results in

_temp = __import__('spam.ham', globals(), locals(), ['eggs', 'sausage'], 0)
eggs = _temp.eggs
saus = _temp.sausage

The problem is: what does __import__() does?

The import statement combines two operations; it searches for the named module, then it binds the results of that search to a name in the local scope. [...]

A direct call to __import__() performs only the module search and, if found, the module creation operation.

So, when you re-import the module, your customization will be lost.

To ensure it stays on, you can import numpy as np and then, when using np - after you assing this new class - you can always access wrong_add.

>>> import numpy as np
>>> np.random.wrong_functions = wrong_functions
>>> np.random.wrong_function.wrong_add(1, 1)
3

EDIT: If you need/want to just call wrong_add instead of full package path to function, you can always assign it to a variable.

>>> wrong_add = np.random.wrong_function.wrong_add
>>> wrong_add(2, 2)
5

Override all instance methods of a class using define_method in Ruby

If you want to call super you should use Module#prepend:

ims = instance_methods(false)
prepend(Module.new do
ims.each do |method_name|
define_method(method_name) do
ErrorHandler.handle_error { super() }
end
end
end)

Monkey patch a python instance method using the original method from the class

Use partialmethod:

In [32]: from functools import partialmethod

In [33]: A.meth = partialmethod(A.meth, foo=2)

In [34]: a = A()

In [35]: a.meth()
2

Monkey-patching a method of an instance with hierarchical inheritance

super() is retrieving the method from the parent class object, of which there is only one. You can patch the method on that class, but the change will be felt by all instances of the parent class and its descendants. You can't patch on an individual instance.

Add functionality to a method by overriding, but still call original method

alias :old_fake_method :fake_method
def fake_method(cmd)
if (cmd) == "bla"
puts "caught cmd"
else
old_fake_method(cmd)
end
end

Calling original new() method from within overridden new()?

Yes, super without parameters will call the parent method with the same parameters passed to the new method.

Or, you can cherry-pick parameters by adding them to super(p1, p2, ...).

Regarding what you want to do by remembering previous invocations, that's called "memoizing" and there is at least one memoize gem for it, or, you can write your own, depending on your needs.

It's pretty easy to do using a hash. Use the parameters used when invoking the new method as the key, and the value is the instance you want to return. Without examples of your code it's hard to come up with an example that's custom-fit, but this is a simple, untested, version:

def initialize(*args)
@memoizer ||= {}
return @memoizer[args] if @memoizer[args]
# do what you will with the args in this initializer,
# then create a new instance for the future.
@memoizer[args] = super(args)
end

The idea is that @memoizer remembers the "arity" of the call and automatically returns the result of similar calls. If that set of parameters haven't been seen before it'll compute and create the new instance and then return it.

This breaks down when the result could change with the same set of input parameters. You don't want to memoize database calls or anything using random or a date/time value, or that returns something outside your control. Trying to use it in those cases will return stale or wrong values unless you design in a method to sweep through and revalidate the @memoizer values periodically.

Also, there is no flushing mechanism, so @memoizer will only grow in size, and could possibly consume all available space given enough different input values. To deal with that you could also have a timestamp for when the value was added to @memoizer, and periodically purge entries that exceed a given lifetime. Only "live" values would remain in the hash then.

Useful information is at: "Memoize Techniques in Ruby and Rails".



Related Topics



Leave a reply



Submit