酸洗装饰的可调用类包装器

时间:2014-12-24 20:24:07

标签: python pickle python-decorators

当我使用自定义可调用类作为包装器时,我正在努力挑选包装函数。

我有一个可赎回的班级" Dependee"使用成员变量" depends_on"跟踪包装函数的依赖关系。我想使用装饰器来包装函数,并且能够腌制生成的包装函数。

所以我定义了我的dependee类。请注意functools.update_wrapper的使用。

>>> class Dependee:
...     
...     def __init__(self, func, depends_on=None):
...         self.func = func
...         self.depends_on = depends_on or []
...         functools.update_wrapper(self, func)
...         
...     def __call__(self, *args, **kwargs):
...         return self.func(*args, **kwargs)
... 

然后我定义我的装饰器,它将返回Dependee包装类的一个实例。

>>> class depends:
...     
...     def __init__(self, on=None):
...         self.depends_on = on or []
...     
...     def __call__(self, func):
...         return Dependee(func, self.depends_on)
... 

这是一个包装函数的示例。

>>> @depends(on=["foo", "bar"])
... def sum(x, y): return x+y
... 

成员变量似乎是可访问的。

>>> print(sum.depends_on)
['foo', 'bar']

我可以按预期调用该函数。

>>> print(sum(1,2))
3

但是我无法挑选包裹的实例。

>>> print(pickle.dumps(sum))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <function sum at 0x7f543863fbf8>: it's not the same object as __main__.sum

我错过了什么?如何为pickle提供更合适的限定名称,以便它可以找到实例而不是原始函数。请注意,手动包装工作正常。

>>> def sum2_func(x,y): return x+y
... 
>>> sum2 = Dependee(sum2_func, depends_on=["foo", "bar"])
>>> print(sum2.depends_on)
['foo', 'bar']
>>> print(sum2(1,2))
3
>>> print(pickle.loads(pickle.dumps(sum2)).depends_on)
['foo', 'bar']

2 个答案:

答案 0 :(得分:5)

您只需要一个更好的序列化程序,例如dill。至于它是如何工作的,dill只是注册了相当于copy_reg的python类型 - 它也类似于模块处理__main__,最后可以通过引用或按对象。因此,如果要序列化函数或类,最后一位是相关的,并使用pickle获取类/函数定义。它比泡菜序列化要大一点,但它更强大。

这是您的代码:

>>> import dill
>>> import functors
>>> class Dependee:
...   def __init__(self, func, depends_on=None):
...     self.func = func
...     self.depends_on = depends_on or []
...     functools.update_wrapper(self, func)
...   def __call__(self, *args, **kwargs):
...     return self.func(*args, **kwargs)
... 
>>>       
>>> class depends:
...   def __init__(self, on=None):
...     self.depends_on = on or []
...   def __call__(self, func):
...     return Dependee(func, self.depends_on)
... 
>>> @depends(on=['foo','bar'])
... def sum(x,y): return x+y
... 
>>> print(sum.depends_on)
['foo', 'bar']
>>> print(sum(1,2))
3
>>> _sum = dill.dumps(sum)
>>> sum_ = dill.loads(_sum)
>>> print(sum_(1,2))
3
>>> print(sum_.depends_on)
['foo', 'bar']
>>> 

在此处获取dillhttps://github.com/uqfoundation

答案 1 :(得分:3)

是的,众所周知的pickle问题 - 无法通过模块中的名称来检索功能或类别。请参阅例如https://code.google.com/p/modwsgi/wiki/IssuesWithPickleModule以获取明确的示例(特别是这会影响modwsgi,以及一般的问题)。

在这种情况下,由于你所做的只是为函数添加属性,你可以采用简化的方法:

class depends:

def __init__(self, on=None):
    self.depends_on = on or []

def __call__(self, func):
    func.func = func
    func.depends_on = self.depends_on or []
    return func

return func是关键想法 - 返回正在装饰的相同对象(可能在装饰之后,就像这里一样,带有其他属性 - 但是,< strong>不一个不同的对象,否则名称与身份相同的问题会有所好处。)

现在这将有效(只是原始代码,只是如上所述更改depends):

$ python d.py 
['foo', 'bar']
3
c__main__
sum
p0
.

当然,这不是一般用途的解决方案(只有在装饰者可以返回它所装饰的同一个对象时才有效),只有一个在你的例子中起作用

我不知道任何序列化方法能够在没有这个限制的情况下序列化和反序列化Python对象,唉。