如何在Python中创建函数的深层副本?

时间:2011-06-29 21:44:21

标签: python function copy deep-copy

我想在Python中对函数进行深度复制。根据{{​​3}},副本模块没有帮助,它说:

  

此模块不复制模块,方法,堆栈跟踪,堆栈帧,文件等类型,   套接字,窗口,数组或任何类似的类型。它确实“复制”了函数和类(浅层)   通过返回原始对象不变;这与方式兼容   这些都是由泡菜模块处理的。

我的目标是让两个函数具有相同的实现,但具有不同的文档字符串。

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

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

那怎么办呢?

7 个答案:

答案 0 :(得分:22)

FunctionType构造函数用于制作函数的深层副本。

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"""

答案 1 :(得分:12)

  

我的目标是让两个函数具有相同的实现,但具有不同的文档字符串。

大多数用户会这样做,比如原始功能在old_module.py

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

并在new_module.py

from old_module import implementation as _implementation

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

这是重用功能最直接的方法。它易于阅读和理解意图。

然而,也许你有充分的理由提出你的主要问题:

  

如何在Python中对函数进行深层复制?

为了使其与Python 2 3保持兼容,我建议使用该功能的特殊__dunder__属性。例如:

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

以下是一个示例用法:

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__
评论者说:

  

这不适用于内置函数

对于内置函数,我不会 这个。我没有理由为使用纯Python编写的函数执行此操作,我怀疑如果你这样做,你可能会做一些非常错误的事情(尽管我在这里可能是错的)。

如果你想要一个执行内置函数功能的函数,并重用实现,就像副本一样,那么你应该用另一个函数包装函数,例如:

_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)

答案 2 :(得分:7)

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)

将其包裹为部分?

答案 3 :(得分:3)

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

def B():
    """B"""
    return A()

答案 4 :(得分:1)

把它放在一个函数中:

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

f = makefunc('I am f')
g = makefunc("I am f too")

答案 5 :(得分:0)

其他答案不允许使用pickle进行序列化。这是我用来克隆函数并允许对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) )

输出:

printing foo and the copy <function IAmFooCopied at 0x7f8a6a8159d8> <function foo at 0x7f8a6b5f5268>
dill output:  b'\x80\x03cdill._dill\n_create_function\nq\x00(cdill._dill\n_load_type\nq\x01X\x08\x00\x00\x00CodeTypeq\x02\x85q\x03Rq\x04(K\x00K\x00K\x00K\x02KCC\x0ct\x00d\x01\x83\x01\x01\x00d\x00S\x00q\x05NX\x01\x00\x00\x00aq\x06\x86q\x07X\x05\x00\x00\x00printq\x08\x85q\t)X\x10\x00\x00\x00testCloneFunc.pyq\nX\x0c\x00\x00\x00IAmFooCopiedq\x0bK\x05C\x02\x00\x01q\x0c))tq\rRq\x0ec__builtin__\n__main__\nh\x0bNN}q\x0ftq\x10Rq\x11.'
pickle Output:  b'\x80\x03c__main__\nIAmFooCopied\nq\x00.'

如果您使用类方法尝试此代码段,则可能会遇到 qualname 属性的问题(我认为pickle应该找不到您的函数)。我从未尝试过,但是它应该很容易修复。只需查看有关质量名称

的文档

答案 6 :(得分:0)

使用 lambda 和 rest 参数很容易做到:

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!