How can I make a deepcopy of a function in Python?

Question:

I would like to make a deepcopy of a function in Python. The copy module is not helpful, according to the documentation, which says:

This module does not copy types like module, method, stack trace, stack frame, file,
socket, window, array, or any similar types. It does “copy” functions and classes (shallow
and deeply), by returning the original object unchanged; this is compatible with the way
these are treated by the pickle module.

My goal is to have two functions with the same implementation but with different docstrings.

def A():
    """A"""
    pass

B = make_a_deepcopy_of(A)
B.__doc__ = """B"""

So how can this be done?

Asked By: Tom

||

Answers:

def A():
    """A"""
    pass

def B():
    """B"""
    return A()
Answered By: Pushpak Dagade

put it in a function:

def makefunc( docstring ):
    def f():
        pass
    f.__doc__ = docstring
    return f

f = makefunc('I am f')
g = makefunc("I am f too")
Answered By: Jochen Ritzel
from functools import partial

def a():
    """Returns 1"""
    return 1

b = partial(a)
b.__doc__ = """Returns 1, OR DOES IT!"""

print help(a)
print help(b)

Wrap it as a partial?

Answered By: Jakob Bowyer

The FunctionType constructor is used to make a deep copy of a function.

import types
def copy_func(f, name=None):
    return types.FunctionType(f.func_code, f.func_globals, name or f.func_name,
        f.func_defaults, f.func_closure)

def A():
    """A"""
    pass
B = copy_func(A, "B")
B.__doc__ = """B"""
Answered By: Glenn Maynard

My goal is to have two functions with the same implementation but with different docstrings.

Most users will do this, say the original function is in old_module.py:

def implementation(arg1, arg2): 
    """this is a killer function"""

and in new_module.py

from old_module import implementation as _implementation

def implementation(arg1, arg2):
    """a different docstring"""
    return _implementation(arg1, arg2)

This is the most straightforward way to reuse functionality. It is easy to read and understand the intent.

Nevertheless, perhaps you have a good reason for your main question:

How can I make a deepcopy of a function in Python?

To keep this compatible with Python 2 and 3, I recommend using the function’s special __dunder__ attributes. For example:

import types

def copy_func(f, name=None):
    '''
    return a function with same code, globals, defaults, closure, and 
    name (or provide a new name)
    '''
    fn = types.FunctionType(f.__code__, f.__globals__, name or f.__name__,
        f.__defaults__, f.__closure__)
    # in case f was given attrs (note this dict is a shallow copy):
    fn.__dict__.update(f.__dict__) 
    return fn

And here’s an example usage:

def main():
    from logging import getLogger as _getLogger # pyflakes:ignore, must copy
    getLogger = copy_func(_getLogger)
    getLogger.__doc__ += 'n    This function is from the Std Lib logging module.n    '
    assert getLogger.__doc__ is not _getLogger.__doc__
    assert getLogger.__doc__ != _getLogger.__doc__

A commenter says:

This can’t work for built‑in functions

Well I wouldn’t do this for a built-in function. I have very little reason to do this for functions written in pure Python, and my suspicion is that if you are doing this, you’re probably doing something very wrong (though I could be wrong here).

If you want a function that does what a builtin function does, and reuses the implementation, like a copy would, then you should wrap the function with another function, e.g.:

_sum = sum
def sum(iterable, start=0):
    """sum function that works like the regular sum function, but noisy"""
    print('calling the sum function')
    return _sum(iterable, start)
    

The others answers do not allow for serialization with pickle. Here a code that I am using to clone a function and allow for serialization for python3:

import pickle
import dill
import types

def foo():
    print ('a')


oldCode=foo.__code__

name='IAmFooCopied'

newCode= types.CodeType(
        oldCode.co_argcount,             #   integer
        oldCode.co_kwonlyargcount,       #   integer
        oldCode.co_nlocals,              #   integer
        oldCode.co_stacksize,            #   integer
        oldCode.co_flags,                #   integer
        oldCode.co_code,                 #   bytes
        oldCode.co_consts,               #   tuple
        oldCode.co_names,                #   tuple
        oldCode.co_varnames,             #   tuple
        oldCode.co_filename,             #   string
        name,                  #   string
        oldCode.co_firstlineno,          #   integer
        oldCode.co_lnotab,               #   bytes
        oldCode.co_freevars,             #   tuple
        oldCode.co_cellvars              #   tuple
        )

IAmFooCopied=types.FunctionType(newCode, foo.__globals__, name,foo.__defaults__ , foo.__closure__)
IAmFooCopied.__qualname__= name
print ( 'printing foo and the copy', IAmFooCopied, foo )
print ( 'dill output: ', dill.dumps(IAmFooCopied ))
print ( 'pickle Output: ', pickle.dumps (IAmFooCopied) )

Output:

printing foo and the copy <function IAmFooCopied at 0x7f8a6a8159d8> <function foo at 0x7f8a6b5f5268>
dill output:  b'x80x03cdill._dilln_create_functionnqx00(cdill._dilln_load_typenqx01Xx08x00x00x00CodeTypeqx02x85qx03Rqx04(Kx00Kx00Kx00Kx02KCCx0ctx00dx01x83x01x01x00dx00Sx00qx05NXx01x00x00x00aqx06x86qx07Xx05x00x00x00printqx08x85qt)Xx10x00x00x00testCloneFunc.pyqnXx0cx00x00x00IAmFooCopiedqx0bKx05Cx02x00x01qx0c))tqrRqx0ec__builtin__n__main__nhx0bNN}qx0ftqx10Rqx11.'
pickle Output:  b'x80x03c__main__nIAmFooCopiednqx00.'

You may encounter problem with the qualname attribute if you try this snippet with class methods (I think pickle should fail to find your function). I never tried it, however it should be easily fixable. Just check the doc about qualname

Answered By: ninjaconcombre

It’s quite easy to do using lambda and rest parameters:

def my_copy(f): 
        # Create a lambda that mimics f
        g = lambda *args: f(*args)
        # Add any properties of f
        t = list(filter(lambda prop: not ("__" in prop),dir(f)))
        i = 0
        while i < len(t):
            setattr(g,t[i],getattr(f,t[i]))
            i += 1
        return g
        
# Test
def sqr(x): return x*x
sqr.foo = 500

sqr_copy = my_copy(sqr)
print(sqr_copy(5)) # -> 25
print(sqr_copy(6)) # -> 36
print(sqr_copy.foo) # -> 500
print(sqr_copy == sqr) # -> False

Try it online!

Answered By: Nirvana

Adjusted for python3

import types
def copy_func(f, name=None):
    return types.FunctionType(f.__code__, f.__globals__, name or f.__name__,
        f.__defaults__, f.__closure__)
def func1(x):
  return 2*x
func2=copy_func(func1)
print(func2(7))
Answered By: hmghaly

I’ve implemented a general-purpose function copy in haggis, a library which I wrote and maintain (available with pip but probably not conda). haggis.objects.copy_func makes a copy on which you can not only reassign the __doc__ attribute, but also modify the module and __globals__ attributes effectively.

from haggis.objects import copy_func

def a(*args, **kwargs):
    """A docstring"""

a2 = copy_func(a)
a2.__doc__ = """Another docstring"""
>>> a == a2
False
>>> a.__code__ == a2.__code__
True
>>> a.__doc__
'A docstring'
>>> a2.__doc__
'Another docstring'
Answered By: Mad Physicist
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.