36

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!?

11
  • 1
    Use copy.deepcopy if you really must. But I think there's a deeper design flaw somewhere. Why do you want to copy functions in the first place? Commented Nov 21, 2012 at 22:27
  • 2
    Can you help us understand a bit about what you want to do -- your question is a little strange/vague. In the thousands of lines of python I've written, I've never copied a function. Commented Nov 21, 2012 at 22:28
  • 1
    If you need to maintain state, why not use classes and instances? Why functions that need to be copied somehow? Commented Nov 21, 2012 at 22:29
  • 3
    If you're changing attributes on a function you're probably better off wrapping the function in an object then changing attributes on the object. It will make your code cleaner. If you add a __call__ method to your object it can be called just like a function. Commented Nov 21, 2012 at 22:30
  • 5
    I don't see why treating function like any other object should be a problem, in python. I do have the same need: decorated functions are passed to internal and external code which expect a function object. Copying a function in order to change its decoration is thus useful and working around with objects that look-a-like function but are not is just overly complicated. Commented Aug 7, 2013 at 11:32

1 Answer 1

54

In Python3:

import types
import functools

def copy_func(f):
    """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)"""
    g = types.FunctionType(f.__code__, f.__globals__, name=f.__name__,
                           argdefs=f.__defaults__,
                           closure=f.__closure__)
    g = functools.update_wrapper(g, f)
    g.__kwdefaults__ = f.__kwdefaults__
    return g

def f(arg1, arg2, arg3, kwarg1="FOO", *args, kwarg2="BAR", kwarg3="BAZ"):
    return (arg1, arg2, arg3, args, kwarg1, kwarg2, kwarg3)
f.cache = [1,2,3]
g = copy_func(f)

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

yields

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

In Python2:

import types
import functools
def copy_func(f):
    """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)"""
    g = types.FunctionType(f.func_code, f.func_globals, name=f.func_name,
                           argdefs=f.func_defaults,
                           closure=f.func_closure)
    g = functools.update_wrapper(g, f)
    return g

def f(x, y=2):
    return x,y
f.cache = [1,2,3]
g = copy_func(f)

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

yields

(1, 2)
(1, 2)
[1, 2, 3]
Sign up to request clarification or add additional context in comments.

14 Comments

The function dictionary should also be copied: g.__dict__.update(f.__dict__)
functools.update_wrapper would update all the metadata other then __kwdefaults__ like __doc__ and __module__
Currently, this answer is based on Glenn Maynard's answer to a similar question. Under that same question, there is an updated answer by Aaron Hall which is very similar to this answer. Aaron updates the new dict based on the old, while this answer uses update_wrapper to update the attributes individually. I am wondering (in the case of Python 3) whether or not the outcome is always the same.
@BenMares: With g = functools.update_wrapper(g, f), you get g.__wrapped__ which equals f. If you use fn.__dict__.update(f.__dict__) you do not get the __wrapped__ attribute. I'm not aware of any other difference due to update_wrapper.
@BenMares: A more dramatic difference is made by the inclusion of g.__kwdefaults__ = f.__kwdefaults__. With this, g(1,2,3) returns (1, 2, 3, (), 'FOO', 'BAR', 'BAZ'). Without it, g(1,2,3) raises TypeError: f() missing 3 required positional arguments: 'arg1', 'arg2', and 'arg3'.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.