2014-11-04 54 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