gpt4 book ai didi

python - 我可以恢复闭包包含 Python 循环的函数吗?

转载 作者:太空狗 更新时间:2023-10-30 00:10:42 27 4
gpt4 key购买 nike

我正在尝试序列化 Python 函数(代码 + 闭包),并在稍后恢复它们。我正在使用本文底部的代码。

这是非常灵活的代码。它允许内部函数和闭包函数的序列化和反序列化,例如那些需要恢复其上下文的函数:

def f1(arg):
def f2():
print arg

def f3():
print arg
f2()

return f3

x = SerialiseFunction(f1(stuff)) # a string
save(x) # save it somewhere

# later, possibly in a different process

x = load() # get it from somewhere
newf2 = DeserialiseFunction(x)
newf2() # prints value of "stuff" twice

即使函数的闭包中有函数,它们的闭包中有函数等等,这些调用也会起作用(我们有一个闭包图,其中闭包包含具有包含更多函数的闭包的函数等等) .

然而,事实证明这些图可以包含循环:

def g1():
def g2():
g2()
return g2()

g = g1()

如果我查看 g2 的闭包(通过 g),我可以在其中看到 g2:

>>> g
<function g2 at 0x952033c>
>>> g.func_closure[0].cell_contents
<function g2 at 0x952033c>

当我尝试反序列化函数时,这会导致严重的问题,因为一切都是不可变的。我需要做的是使函数 newg2:

newg2 = types.FunctionType(g2code, globals, closure=newg2closure)

其中 newg2closure 的创建方式如下:

newg2closure = (make_cell(newg2),)

这当然是做不到的;每行代码都相互依赖。元胞是不可变的,元组是不可变的,函数类型是不可变的。

所以我想知道的是,有没有办法在上面创建 newg2?有什么方法可以创建一个函数类型对象,该对象在其自己的闭包图中被提及?

我正在使用 python 2.7(我在 App Engine 上,所以我不能转到 Python 3)。


作为引用,我的序列化函数:

def SerialiseFunction(aFunction):
if not aFunction or not isinstance(c, types.FunctionType):
raise Exception ("First argument required, must be a function")

def MarshalClosureValues(aClosure):
logging.debug(repr(aClosure))
lmarshalledClosureValues = []
if aClosure:
lclosureValues = [lcell.cell_contents for lcell in aClosure]
lmarshalledClosureValues = [
[marshal.dumps(litem.func_code), MarshalClosureValues(litem.func_closure)] if hasattr(litem, "func_code")
else [marshal.dumps(litem)]
for litem in lclosureValues
]
return lmarshalledClosureValues

lmarshalledFunc = marshal.dumps(aFunction.func_code)
lmarshalledClosureValues = MarshalClosureValues(aFunction.func_closure)
lmoduleName = aFunction.__module__

lcombined = (lmarshalledFunc, lmarshalledClosureValues, lmoduleName)

retval = marshal.dumps(lcombined)

return retval


def DeserialiseFunction(aSerialisedFunction):
lmarshalledFunc, lmarshalledClosureValues, lmoduleName = marshal.loads(aSerialisedFunction)

lglobals = sys.modules[lmoduleName].__dict__

def make_cell(value):
return (lambda x: lambda: x)(value).func_closure[0]

def UnmarshalClosureValues(aMarshalledClosureValues):
lclosure = None
if aMarshalledClosureValues:
lclosureValues = [
marshal.loads(item[0]) if len(item) == 1
else types.FunctionType(marshal.loads(item[0]), lglobals, closure=UnmarshalClosureValues(item[1]))
for item in aMarshalledClosureValues if len(item) >= 1 and len(item) <= 2
]
lclosure = tuple([make_cell(lvalue) for lvalue in lclosureValues])
return lclosure

lfunctionCode = marshal.loads(lmarshalledFunc)
lclosure = UnmarshalClosureValues(lmarshalledClosureValues)
lfunction = types.FunctionType(lfunctionCode, lglobals, closure=lclosure)
return lfunction

最佳答案

这是一个有效的方法。

你无法修复这些不可变对象(immutable对象),但你可以做的是用代理函数代替循环引用,并让它们在全局字典中查找真正的函数。

1:序列化时,跟踪您看到的所有函数。如果您再次看到相同的值,请不要重新序列化,而是序列化一个标记值。

我用过一套:

lfunctionHashes = set()

对于每个序列化项目,检查它是否在集合中,如果是,则使用标记,否则将其添加到集合中并正确编码:

lhash = hash(litem)
if lhash in lfunctionHashes:
lmarshalledClosureValues.append([lhash, None])
else:
lfunctionHashes.add(lhash)
lmarshalledClosureValues.append([lhash, marshal.dumps(litem.func_code), MarshalClosureValues(litem.func_closure, lfullIndex), litem.__module__])

2: 反序列化时,保留一个全局的functionhash: function字典

gfunctions = {}

在反序列化过程中,任何时候反序列化一个函数,将它添加到 gfunctions。在这里,item 是(散列、代码、闭包值、模块名称):

lfunction = types.FunctionType(marshal.loads(item[1]), globals, closure=UnmarshalClosureValues(item[2]))
gfunctions[item[0]] = lfunction

当你遇到一个函数的哨兵值时,使用代理,传入函数的哈希值:

lfunction = make_proxy(item[0])

这是代理。它根据散列查找真正的函数:

def make_proxy(f_hash):
def f_proxy(*args, **kwargs):
global gfunctions
f = lfunctions[f_hash]
f(*args, **kwargs)

return f_proxy

我还必须进行一些其他更改:

  • 我在某些地方使用了 pickle 而不是 marshal,可能会进一步检查
  • 我在序列化中包含函数的模块名称以及代码和闭包,因此我可以在反序列化时查找函数的正确全局变量。
  • 在反序列化中,元组的长度告诉您要反序列化的内容:1 表示简单值,2 表示需要代理的函数,4 表示完全序列化的函数

这是完整的新代码。

lfunctions = {}

def DeserialiseFunction(aSerialisedFunction):
lmarshalledFunc, lmarshalledClosureValues, lmoduleName = pickle.loads(aSerialisedFunction)

lglobals = sys.modules[lmoduleName].__dict__
lglobals["lfunctions"] = lfunctions

def make_proxy(f_hash):
def f_proxy(*args, **kwargs):
global lfunctions
f = lfunctions[f_hash]
f(*args, **kwargs)

return f_proxy

def make_cell(value):
return (lambda x: lambda: x)(value).func_closure[0]

def UnmarshalClosureValues(aMarshalledClosureValues):
global lfunctions

lclosure = None
if aMarshalledClosureValues:
lclosureValues = []
for item in aMarshalledClosureValues:
ltype = len(item)
if ltype == 1:
lclosureValues.append(pickle.loads(item[0]))
elif ltype == 2:
lfunction = make_proxy(item[0])
lclosureValues.append(lfunction)
elif ltype == 4:
lfuncglobals = sys.modules[item[3]].__dict__
lfuncglobals["lfunctions"] = lfunctions
lfunction = types.FunctionType(marshal.loads(item[1]), lfuncglobals, closure=UnmarshalClosureValues(item[2]))
lfunctions[item[0]] = lfunction
lclosureValues.append(lfunction)
lclosure = tuple([make_cell(lvalue) for lvalue in lclosureValues])
return lclosure

lfunctionCode = marshal.loads(lmarshalledFunc)
lclosure = UnmarshalClosureValues(lmarshalledClosureValues)
lfunction = types.FunctionType(lfunctionCode, lglobals, closure=lclosure)
return lfunction

def SerialiseFunction(aFunction):
if not aFunction or not hasattr(aFunction, "func_code"):
raise Exception ("First argument required, must be a function")

lfunctionHashes = set()

def MarshalClosureValues(aClosure, aParentIndices = []):
lmarshalledClosureValues = []
if aClosure:
lclosureValues = [lcell.cell_contents for lcell in aClosure]

lmarshalledClosureValues = []
for index, litem in enumerate(lclosureValues):
lfullIndex = list(aParentIndices)
lfullIndex.append(index)

if isinstance(litem, types.FunctionType):
lhash = hash(litem)
if lhash in lfunctionHashes:
lmarshalledClosureValues.append([lhash, None])
else:
lfunctionHashes.add(lhash)
lmarshalledClosureValues.append([lhash, marshal.dumps(litem.func_code), MarshalClosureValues(litem.func_closure, lfullIndex), litem.__module__])
else:
lmarshalledClosureValues.append([pickle.dumps(litem)])

lmarshalledFunc = marshal.dumps(aFunction.func_code)
lmarshalledClosureValues = MarshalClosureValues(aFunction.func_closure)
lmoduleName = aFunction.__module__

lcombined = (lmarshalledFunc, lmarshalledClosureValues, lmoduleName)

retval = pickle.dumps(lcombined)

return retval

关于python - 我可以恢复闭包包含 Python 循环的函数吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26250895/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com