Achim Achim - 5 months ago 14
Python Question

How to create a copy of a python function

Is there a possibility to create real copies of python functions? The most obvious choice was http://docs.python.org/2/library/copy.html but there I read:


It does “copy” functions and classes (shallow and deeply), by
returning the original object unchanged;


I need a real copy, because I might change some attributes of the function.

Update:

I'm aware of all the possibilities which are mentioned in the comments. My use case is based on meta programming where I construct classes out of some declarative specifications. Complete details would be too long for SO, but basically I have a function like

def do_something_usefull(self,arg):
self.do_work()


I will add this method to various classes. Thoses classes can be completly unrelated. Using mixin classes is not an option: I will have many such functions and would end up adding a base class for each function. My current "workaround" would be to wrap this function in a "factory" like this:

def create_do_something():
def do_something_usefull(self,arg):
self.do_work()


That way I always get a new do_something_useful function, but I have to wrap all my functions like this.

You can trust me, that I'm aware, that this is no "normal" OO programming. I know how to solve something like that "normally". But this is a dynamic code generator and I would like to keep everything as lightweight and simple as possible. And as python functions are quite normal objects, I don't think it's too strange to ask how to copy them!?

Answer

In Python2:

import types

def copy_func(f, name=None):
    g = types.FunctionType(f.func_code, f.func_globals, name=f.func_name,
                           argdefs=f.func_defaults,
                           closure=f.func_closure)

    g.__dict__.update(f.__dict__)        
    return g

def f(x, y=2):
    return x,y
g = copy_func(f)

print(f(1))
print(g(1))
assert f is not g

In Python3:

import types

def copy_func(f, name=None):
    g = types.FunctionType(f.__code__, f.__globals__, name=f.__name__,
                           argdefs=f.__defaults__,
                           closure=f.__closure__,)
    g.__kwdefaults__ = f.__kwdefaults__
    g.__dict__.update(f.__dict__)        
    return g

def f(arg1, arg2, arg3, kwarg1="FOO", *args, kwarg2="BAR", kwarg3="BAZ"):
    return (arg1, arg2, arg3, args, kwarg1, kwarg2, kwarg3)
g = copy_func(f)

print(f(1,2,3,4,5))
print(g(1,2,3,4,5))
assert f is not g

yields

(1, 2, 3, (5,), 4, 'BAR', 'BAZ')
(1, 2, 3, (5,), 4, 'BAR', 'BAZ')