2014-11-04 100 views
1

我想能够调用根据一些标准格式的方法:使用Python装饰器的方法添加到方法

outputs = obj.meth(in_0, in_1, ...) 

,其中输出是阵列的一个元组,并且每个输入是一个数组。

但是,在大多数情况下,我只返回一个数组,并且不想为了标准格式而强制返回长度为1的元组。 (我的实际的格式问题更复杂,但可以坚持使用这种解释现在。)

我希望能够定义一个类,如:

class _SomeClass(object): 

    def __init__(self): 
     self._amount_to_add = 1 

    @single_return_format 
    def add_one(self, x): 
     return x+self._amount_to_add 

然后可以调用它,如下所示:

obj = _SomeClass() 
assert obj.add_one(3) == 4 
assert obj.add_one.standard_format(3)==(4,) 

问题是:我如何定义装饰器以允许这种行为?

我尝试:

def single_return_format(fcn): 
    fcn.standard_format = lambda *args: (fcn(*args),) 
    return fcn 

,但就行失败,并与所述第二断言:

TypeError: add_one() takes exactly 2 arguments (1 given) 

由于add_one需要“自我”作为参数,并且该对象具有甚至在装饰者修改该功能时尚未创建。

So Stack,我该怎么做?


注:

1)我知道我可以与基类和继承,而不是这样做,而是成为一个问题,当你在课堂上不止一个方法,你想装饰这个办法。

2)实际的问题来自于使用theano - 标准格式为outputs, updates = fcn(*inputs),但大多数函数不会返回任何更新,因此您希望能够以自然的方式定义这些函数,但仍然有选项根据这个标准接口调用它们。

+0

为什么在这里标记theano? – eickenberg 2014-11-04 08:41:34

+1

请参阅底部的注释 - 问题源于事实:在theano中,您需要表示返回值中的任何状态更改。 – Peter 2014-11-04 18:47:03

+0

感谢和抱歉,我应该刚刚为word – eickenberg 2014-11-04 19:49:49

回答

0

沙丘给出了正确的答案。我已经将它剥离到骨头,以便解决问题中的问题。精简代码在这里:

class single_return_format(object): 

    def __init__(self, func): 
     self._func = func 

    def __get__(self, instance, owner): 
     return SimpleFormMethod(instance, self._func) 


class SimpleFormMethod(object): 

    def __init__(self, instance, func): 
     self._instance = instance 
     self._func = func 

    def __call__(self, *args, **kwargs): 
     return self._func(self._instance, *args, **kwargs) 

    @property 
    def standard_format(self): 
     return lambda *args, **kwargs: (self._func(self._instance, *args, **kwargs),) 


class _SomeClass(object): 

    def __init__(self): 
     self._amount_to_add = 1 

    @single_return_format 
    def add_one(self, x): 
     return x+self._amount_to_add 


obj = _SomeClass() 
assert obj.add_one(3) == 4 
assert obj.add_one.standard_format(3) == (4,) 
2

这确实是一个问题,因为从函数中检索“bound”方法的方式不考虑这种方式。

我看到有两种方式:

  1. 你可以只包住功能:

    def single_return_format(fcn): 
        # TODO Do some functools.wraps here... 
        return lambda *args, **kwargs: (fcn(*args, **kwargs),) 
    

    没有打打闹闹与.standard_format,但仅仅更换功能。所以函数可以自己定义为返回一个值,但只能被称为返回元组。

  2. 如果这不是你想要的,你可以定义一个装饰方法的类,它会覆盖__get__,并以“现场时尚”的方式进行包装。当然,它也可以重新定义__call__,以便它可以用于(独立的,非方法的)函数。

2

为了得到你想要的东西,你必须为你的函数写一个非数据描述符和一组包装类。原因在于从对象获取函数的过程是高度优化的,并且不可能劫持这种机制。相反,您必须编写自己的模拟此机制的类 - 如果您正在进行大量小型方法调用,则会降低代码速度。

我认为获得所需功能的最佳方式不是使用您描述的任何方法,而是编写一个在需要调用标准格式的普通函数时使用的包装函数。例如。

def vectorise(method, *args, **kwargs): 
    return tuple(method(arg, **kwargs) for arg in args) 

obj = _SomeClass() 

result = vectorise(obj.add_one, 1, 2, 3) 

事实上,这是多么numpy需要上一个参数操作功能,并把它们成列上工作的功能。

import numpy 

def add_one(x): 
    return x + 1 

arr = numpy.vectorize(add_one)([1, 2, 3]) 

如果你确实真的想要使用非数据描述符,那么下面的工作将会起作用。被警告这些方法调用相当慢。在我的计算机上,一个普通的方法调用需要188纳秒,而对于“简单”方法调用则需要1.53微秒 - 相差10倍。和vectorise通话需要一半的时间拨打standard_form。那时绝大多数是查找方法。实际的方法调用非常快。

class simple_form: 
    """Allows a simple function to be called in a standard way.""" 

    def __init__(self, func): 
     self.func = func 

    def __get__(self, instance, owner): 
     if instance is None: 
      return self.func 
     return SimpleFormMethod(self.func, instance) 


class MethodBase: 
    """Provides support for getting the string representation of methods.""" 

    def __init__(self, func, instance): 
     self.func = func 
     self.instance = instance 

    def _format(self): 
     return "<bound {method_class} {obj_class}.{func} of {obj}>".format(
      method_class=self.__class__.__name__, 
      obj_class=self.instance.__class__.__name__, 
      func=self.func.__name__, 
      obj=self.instance) 

    def __str__(self): 
     return self._format() 

    def __repr__(self): 
     return self._format() 


class SimpleFormMethod(MethodBase): 

    def __call__(self, *args, **kwargs): 
     return self.func(self.instance, *args, **kwargs) 

    @property 
    def standard_form(self): 
     return StandardFormMethod(self.func, self.instance) 


class StandardFormMethod(MethodBase): 

    def __call__(self, *args, **kwargs): 
     return tuple(self.func(self.instance, arg, **kwargs) for arg in args) 


class Number(object): 

    def __init__(self, value): 
     self.value = value 

    def add_to(self, *values): 
     return tuple(val + self.value for val in values) 

    @simple_form 
    def divide_into(self, value): 
     return value/self.value 


num = Number(2) 
print("normal method access:", num.add_to, sep="\n") 
print("simple form method access:", num.divide_into, sep="\n") 
print("standard form method access:", num.divide_into.standard_form, sep="\n") 
print("access to underlying function:", Number.divide_into, sep="\n") 
print("simple example usage:", num.divide_into(3)) 
print("standard example usage:", num.divide_into.standard_form(*range(3))) 
+0

写了很好的。这样可行。 – Peter 2014-11-06 17:35:59