22

how do i add code to an existing function, either before or after?

for example, i have a class:

 class A(object):
     def test(self):
         print "here"

how do i edit the class wit metaprogramming so that i do this

 class A(object):
     def test(self):
         print "here"

         print "and here"

maybe some way of appending another function to test?

add another function such as

 def test2(self):
      print "and here"

and change the original to

 class A(object):
     def test(self):
         print "here"
         self.test2()

is there a way to do this?

Timmy
  • 12,468
  • 20
  • 77
  • 107

7 Answers7

27

You can use a decorator to modify the function if you want. However, since it's not a decorator applied at the time of the initial definition of the function, you won't be able to use the @ syntactic sugar to apply it.

>>> class A(object):
...     def test(self):
...         print "orig"
...
>>> first_a = A()
>>> first_a.test()
orig
>>> def decorated_test(fn):
...     def new_test(*args, **kwargs):
...         fn(*args, **kwargs)
...         print "new"
...     return new_test
...
>>> A.test = decorated_test(A.test)
>>> new_a = A()
>>> new_a.test()
orig
new
>>> first_a.test()
orig
new

Do note that it will modify the method for existing instances as well.

EDIT: modified the args list for the decorator to the better version using args and kwargs

Daniel DiPaolo
  • 55,313
  • 14
  • 116
  • 115
  • What about packages from third party, like this [one](https://stackoverflow.com/questions/55721581/python-monkey-patch-fails-why)? – Tengerye Apr 18 '19 at 09:38
  • Technically, this does not modify the function, but replaces it with a new one which internally calls the original one. – HelloGoodbye Jul 19 '22 at 13:17
10

The typical way to add functionality to a function is to use a decorator (using the wraps function):

from functools import wraps

def add_message(func):
    @wraps
    def with_additional_message(*args, **kwargs)
        try:
            return func(*args, **kwargs)
        finally:
            print "and here"
    return with_additional_message

class A:
    @add_message
    def test(self):
        print "here"

Of course, it really depends on what you're trying to accomplish. I use decorators a lot, but if all I wanted to do was to print extra messages, I'd probably do something like

class A:
    def __init__(self):
        self.messages = ["here"]

    def test(self):
        for message in self.messages:
            print message

a = A()
a.test()    # prints "here"

a.messages.append("and here")
a.test()    # prints "here" then "and here"

This requires no meta-programming, but then again your example was probably greatly simplified from what you actually need to do. Perhaps if you post more detail about your specific needs, we can better advise what the Pythonic approach would be.

EDIT: Since it seems like you want to call functions, you can have a list of functions instead of a list of messages. For example:

class A:
    def __init__(self):
        self.funcs = []

    def test(self):
        print "here"
        for func in self.funcs:
            func()

def test2():
    print "and here"

a = A()
a.funcs.append(test2)
a.test()    # prints "here" then "and here"

Note that if you want to add functions that will be called by all instances of A, then you should make funcs a class field rather than an instance field, e.g.

class A:
    funcs = []
    def test(self):
        print "here"
        for func in self.funcs:
            func()

def test2():
    print "and here"

A.funcs.append(test2)

a = A()
a.test()    # print "here" then "and here"
Community
  • 1
  • 1
Eli Courtwright
  • 186,300
  • 67
  • 213
  • 256
6

This answer allows you to modify the original function without creating a wrapper. I have found the following two native python types, which are useful to answer your question:

types.FunctionType

And

types.CodeType

This Code seems to do the job:

import inspect
import copy
import types
import dill
import dill.source


#Define a function we want to modify:
def test():
    print "Here"

#Run the function to check output
print '\n\nRunning Function...'
test()
#>>> Here

#Get the source code for the test function:
testSource = dill.source.getsource(test)
print '\n\ntestSource:'
print testSource


#Take the inner part of the source code and modify it how we want:
newtestinnersource = ''
testSourceLines = testSource.split('\n')
linenumber = 0 
for line in testSourceLines:
    if (linenumber > 0):
        if (len(line[4:]) > 0):
            newtestinnersource += line[4:] + '\n'
    linenumber += 1
newtestinnersource += 'print "Here2"'
print '\n\nnewtestinnersource'
print newtestinnersource


#Re-assign the function's code to be a compiled version of the `innersource`
code_obj = compile(newtestinnersource, '<string>', 'exec')
test.__code__ = copy.deepcopy(code_obj)
print '\n\nRunning Modified Function...'
test() #<- NOW HAS MODIFIED SOURCE CODE, AND PERFORMS NEW TASK
#>>>Here
#>>>Here2

TODO: change this answer such that dill.source.getsource obtains thew correct new source code.

D A
  • 3,130
  • 4
  • 25
  • 41
  • I do however find myself aggrivated that my solution does not work for more than one iteration of a function modification loop. dill.source.getsource only works on the original version of the function, and after modifying the function, it cannot be used again to get the source code. IT would appear that python only keeps track of the one-time originally compiled source code, and does NOT keep track of source code changes to a function. In principle, you could take the compiled version of the function and then reconstruct python code... but that seems silly. – D A Sep 23 '15 at 22:26
  • 1
    This is exactly what I was looking for! Take a function, convert it to source, modify the source and recompile it! Very useful for metaprogramming inside of triple nested loops in large scale simulation. You can beforehand create complex functions depending on conditions that you don't have to check inside the loops anymore :) (dynamically modifying any kind of cost function or the likes) – Robin De Schepper Oct 15 '20 at 08:58
  • I think it is great that someone found this useful finally. Now we should modify my code snip to change the "source" such that dill.source.getsource properly obtains the modified source code. – D A Dec 22 '20 at 17:27
3

There are a lot of really good suggestions above, but one I didn't see was passing in a function with the call. Might look something like this:

class A(object):
    def test(self, deep=lambda self: self):
        print "here"
        deep(self)
def test2(self):
    print "and here"

Using this:

>>> a = A()
>>> a.test()
here
>>> a.test(test2)
here
and here
Jack M.
  • 30,350
  • 7
  • 55
  • 67
2

Why not use inheritance?

class B(A):
    def test(self):
        super(B, self).test()
        print "and here"
Samir Talwar
  • 14,220
  • 3
  • 41
  • 65
  • the short is i had to dynamically add parents to a class, so i cannot call the super easily, without doing what the question is asking in the first place – Timmy May 07 '10 at 14:57
  • Fair enough. Decorators definitely seem like the way to go then. – Samir Talwar May 07 '10 at 15:39
1

Copy and paste, enjoy!!!!!

#!/usr/bin/env python 

def say(host, msg): 
   print '%s says %s' % (host.name, msg) 

def funcToMethod(func, clas, method_name=None): 
   setattr(clas, method_name or func.__name__, func) 

class transplant: 
   def __init__(self, method, host, method_name=None): 
      self.host = host 
      self.method = method 
      setattr(host, method_name or method.__name__, self) 

   def __call__(self, *args, **kwargs): 
      nargs = [self.host] 
      nargs.extend(args) 
      return apply(self.method, nargs, kwargs) 

class Patient: 
   def __init__(self, name): 
      self.name = name 

if __name__ == '__main__': 
   jimmy = Patient('Jimmy') 
   transplant(say, jimmy, 'say1') 
   funcToMethod(say, jimmy, 'say2') 

   jimmy.say1('Hello') 
   jimmy.say2(jimmy, 'Good Bye!') 
Delta
  • 2,004
  • 3
  • 25
  • 33
0

If your class A inherits from object, you could do something like:

def test2():
    print "test"

class A(object):
    def test(self):
        setattr(self, "test2", test2)
        print self.test2
        self.test2()

def main():
    a = A()
    a.test()

if __name__ == '__main__':
    main()

This code has no interest and you can't use self in your new method you added. I just found this code fun, but I will never use this. I don't like to dynamicaly change the object itself.

It's the fastest way, and more understandable.

dzen
  • 6,923
  • 5
  • 28
  • 31