Reputation: 2222
How do i override a Class' method, based on this situation? However, i cannot edit solid.py and run_me.py. While mainting the decoration and able to call the original.
# - solid.py - (no control)
import http
class Solid(object):
_cp_path = '/pos'
@http.jsonrequest
def break_apart(self):
return "to pieces!"
;
# - me.py -
import solid
def break_apart(self):
return "to sand! and " + super(solid.Solid, self).break_apart()
solid.Solid.break_apart = break_apart
;
# - run_me.py - (no control)
import me # yes, me first
import solid
pebble = solid.Solid()
pebble.break_apart() # "to sand! and to pieces!"
EDIT
Thank you for the assistance, sorry for being incomplete. I forgot to add that solid.py has decorators. Doing the monkeypatch
works like a charm, however:
i can't super call the original
AttributeError: 'super' object has no attribute 'break_apart'
Upvotes: 1
Views: 2584
Reputation: 332
Standing on the shoulder of giants here at stackoverflow, this works for me. I know it's a bit complex but I haven't stumbled on something simpler yet. The code works on Python3.
import functools
def log( fn ):
@functools.wraps( fn )
def wrapper( *args, **kwargs ):
print( "log", fn.__name__, args[ 1: ] )
return fn( *args, **kwargs )
return wrapper
def prepare_class( clazz ):
@classmethod
def on_class_patcher( cls, func_name, context ):
def patch_by_name( new_func) :
original_func = getattr( cls, func_name )
def patched_func( self, *args, **kwargs ):
return new_func( self, original_func, context, *args, **kwargs )
setattr( cls, func_name, patched_func )
return patch_by_name
setattr( clazz, "patch", on_class_patcher )
# --- Use like this ---
class Demo:
@log
def m1( self, x, y ):
print( "Demo.m1" )
print( x, y )
def m2( self ):
print( "Demo.m2" )
class Foo:
def m3( self ):
print( "Foo.m3" )
prepare_class( Demo )
foo = Foo()
@Demo.patch( "m1", context = { "foo": foo } )
def patched_m1( self, original_func, context, *args, **kwargs ):
print( "Demo.m1_patched" )
self.m2()
context[ "foo" ].m3()
x = args[ 0 ] * 2
y = args[ 1 ] * 2
return original_func( self, x, y )
demo = Demo()
demo.m1( 1, 2 )
Result:
Demo.m1_patched
Demo.m2
Foo.m3
log m1 (2, 4)
Demo.m1
2 4
Upvotes: 0
Reputation: 104712
I think there are two different approaches you can take to override a method in a class you can't edit the source code to.
The first is to create a subclass that overrides the method. This is pretty straightforward, but it only effects objects that you create yourself using the constructor of the child class, rather than the original class:
me.py:
import http
import solid
class MySolid(solid.Solid):
@http.jsonjequest
def break_apart(self):
return "to sand! and " + super(solid.Solid, self).break_apart()
I don't actually know what the http.jsonrequest
decorator does, so you may need to change the call to the original value to work with it, if it modifies the call signature in some way.
The other approach is to monkey-patch the existing class. This means that you replace the method implementation in the class with your own alternative version. This is useful if the instances of the original class are being created by other code that you also don't control. Note that if you still need access to the original method implementation, you'll need to save a reference to it yourself (super
doesn't handle this situation).
me.py:
import http
import solid
_orig_break_apart = solid.Solid.break_apart
@http.jsonrequest
def break_apart(self):
return "to sand! and " + _orig_break_apart(self)
solid.Solid.break_apart = break_apart
Here to, you may need to change how you call the original method if the decorator changed the signature.
It's possible that the decorated version of the method differs greatly from the undecorated version (e.g. it radically changes the call signature or return value). If that's the case, it may not be easy to "reverse" this process to get to the original method implementation so that you can call it from within your new version. In that case, you'll probably need to copy the code from the existing implementation into your overriding version (using either of the override methods above). In the example code, you'd simply return "to sand! and to pieces!"
without trying to call the original method. In real code, this would probably be more complicated, but the same idea applies.
Upvotes: 0
Reputation: 171
Since Python's functions are first-class, make your me.py this:
import solid
# replacement method
def break_apart(self):
return "to sand!"
# override Solid.break_apart with the new method
solid.Solid.break_apart = break_apart
Also, since you called pebble.break_apart(), this implies that solid.py should be:
class Solid(object):
def break_apart(self):
return "to pieces!"
Notice the addition of the self parameter to break_apart. This is implied when you call pebble.break_apart() (i.e. self = pebble instance)
Upvotes: 1
Reputation: 8722
First of all, what kind of class method do you actually have?
It looks like a staticmethod
but you're not using the staticmethod
decorator.
Instance methods look like:
class C:
def instance_method(self, *args, **kwargs):
return self.foo
Class methods look like:
@classmethod
def class_method(cls, *args, **kwargs):
return cls.foo
Static methods look like:
@staticmethod
def static_method(*args, **kwargs):
return foo
You can't use inheritance to change this code if you don't have the ability to change run_me.py
.
Instead, you can simply monkeypatch
the Solid
class with a compatible implementation of break_apart
like this:
import solid
def break_apart(self):
return "whatever you want"
solid.Solid.break_apart = break_apart
Of course, since your code is imported first you could actually just replace the entire class.
So just define Concrete
as you are already doing and then monkey patch the whole class inside the solid
module:
import solid
class Concrete:
...
solid.Solid = Concrete
Upvotes: 1