2012-07-07 28 views
7

我想寫一個裝飾器,提供方法重載功能到python,類似於PEP 3124中提到的。重載裝飾器的方法

我寫的裝飾器對於普通函數非常適用,但是我無法讓它在類中爲方法工作。

這裏是裝飾:

class Overload(object): 
    def __init__(self, default): 
     self.default_function = default 
     self.type_map = {} 
     self.pos = None 

    def __call__(self, *args, **kwargs): 
     print self 
     try: 
      if self.pos is None: 
       pos = kwargs.get("pos", 0) 
      else: 
       pos = self.pos 
      print args, kwargs 
      return self.type_map[type(args[pos])](*args, **kwargs) 
     except KeyError: 
      return self.default_function(*args, **kwargs) 
     except IndexError: 
      return self.default_function(*args, **kwargs) 

    def overload(self, *d_type): 
     def wrapper(f): 
      for dt in d_type: 
       self.type_map[dt] = f 
      return self 
     return wrapper 

當我試圖實現它是這樣的:

class MyClass(object): 
    def __init__(self): 
     self.some_instance_var = 1 

    @Overload 
    def print_first_item(self, x): 
     return x[0], self.some_instance_var 

    @print_first_item.overload(str) 
    def print_first_item(self, x): 
     return x.split()[0], self.some_instance_var 

我得到一個TypeError當我運行它:

>>> m = MyClass() 
>>> m.print_first_item(1) 
<__main__.Overload object at 0x2> (1,) {} 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "overload.py", line 17, in __call__ 
    return self.default_function(*args, **kwargs) 
    TypeError: print_first_item() takes exactly 2 arguments (1 given) 
>>> 

我的問題是:如何訪問MyClass的實例(即self)從裝飾的方法?

+2

有你看在PEAK.Rules參考實現,或任何十幾連接到司法警察的其他參考實現較早的PEP和列表帖子?如果你試圖真正使用它,而不是試圖探索Python,那麼使用他的作品(至少有其他人已經使用和測試過)比重複它更有意義。 – abarnert 2012-07-07 00:38:23

+0

@abarnert:我沒有意識到這一點。感謝您的高舉。這就是說,我真的只是想知道爲什麼我的實現沒有按預期工作,我怎麼能解決它。我就像你說的「探索Python」一樣。 – 2012-07-07 00:42:24

+3

首先,你知道@ functools.wraps等嗎?他們會讓你的生活變得更輕鬆,但這不會幫助你。無論如何,這裏問題的第一部分是,你的default_function被一個類似函數的類替換,它不是一個方法(Overload .__ call__需要一個self,但這是Overload實例,而不是MyClass)。但顯然你不能只做__call __(自我,自己,* args,** kwargs),並期待這種工作。我今晚沒有時間詳談了;希望別人能夠在我回來之前提供幫助。 – abarnert 2012-07-07 01:00:49

回答

1

基本上,您Overload類需要__get__方法:

def __get__(self, obj, cls): 
    # Called on access of MyClass.print_first_item. 
    # We return a wrapper which calls our 
    print "get", self, obj, cls 
    if obj is None: 
     # a function would do some checks here, but we leave that. 
     return self 
    else: 
     return lambda *a, **k: self(obj, *a, **k) 

爲什麼?

那麼,你用你的Overload對象作爲一種函數替換。您希望它像一個函數一樣在具有不同簽名的方法上下文中表示自己。

簡短的解釋方法的訪問是如何工作的:

object.meth(1, 2) 

被翻譯成

object.__dict__['meth'].__get__(object, type(object))(1, 2) 

函數的__get__()返回其通過預先對象參數列表包裝功能的方法的對象(在那裏結果爲self):

realmethod = object.__dict__['meth'].__get__(object, type(object)) 
realmethod(1, 2) 

,其中realmethod,是考慮到它它知道要調用的函數和self的方法對象,並通過將呼叫到

meth(object, 1, 2) 

適當地稱之爲「真正」的功能。

這種行爲我們模仿在這個新的__get__方法。

+0

太棒了。根據你的建議,我已經開始工作了。查看查找工作的詳細說明請參見+1。 – 2012-07-08 00:14:59

+1

另請參閱下面的我的工作實現。 – 2012-07-08 00:16:52

1

as abarnert says as you are using a class as your decorator'self'是Overload的一個實例,而不是MyClass的實例。

我找不到一個簡單的解決方案。我可以想到的最好的事情是不使用類作爲裝飾器,而是使用函數,但使用第二個參數與默認的字典。由於這是一個可變類型,因此每次調用函數時都會使用同一個字典。我用它來存儲我的'類變量'。其餘部分與您的解決方案類似。

實施例:

import inspect 

def overload(funcOrType, map={}, type=None): 
    if not inspect.isclass(funcOrType): 
     # We have a function so we are dealing with "@overload" 
     if(type): 
      map[type] = funcOrType 
     else: 
      map['default_function'] = funcOrType 
    else: 
     def overloadWithType(func): 
      return overload(func, map, funcOrType) 
     return overloadWithType 

    def doOverload(*args, **kwargs): 
     for type in [t for t in map.keys() if t != 'default_function'] : 
      if isinstance(args[1], type): # Note args[0] is 'self' i.e. MyClass instance. 
       return map[type](*args, **kwargs) 
     return map['default_function'](*args, **kwargs) 

    return doOverload 

然後:

from overload import * 

class MyClass(object): 
    def __init__(self): 
     self.some_instance_var = 1 

    @overload 
    def print_first_item(self, x): 
     return x[0], self.some_instance_var 

    @overload(str) 
    def print_first_item(self, x): 
     return x.split()[0], self.some_instance_var 


m = MyClass() 
print (m.print_first_item(['a','b','c'])) 
print (m.print_first_item("One Two Three")) 

Yeilds:

('a', 1) 
('One', 1) 
1

僅供參考,這裏是工作的落實,這要歸功於glglgl詳細的說明:

argtype_tuple = lambda args: tuple(type(a) for a in args) 

class Overload(object):  
    def __init__(self, func): 
     self.default = func 
     self.map = {} 

    def __call__(self, *args, **kwargs): 
     key_tuple = argtype_tuple(args) 
     c_inst = kwargs.pop("c_inst", None) 
     if c_inst: 
      args = (c_inst,) + args 
     try: 
      return self.map[key_tuple](*args, **kwargs) 
     except KeyError: 
      return self.default(*args, **kwargs) 

    def __get__(self, obj, cls): 
     if obj: 
      return lambda *args, **kwargs: self(c_inst=obj, *args, **kwargs) 
     else: 
      return self 

    def overload(self, *types): 
     def wrapper(f): 
      for type_seq in types: 
       if type(type_seq) == tuple: 
        type_seq = tuple(type_seq) 
       else: 
        type_seq = (type_seq,) 
       self.map[type_seq] = f 
      return self 
     return wrapper 

#Some tests/usage examples 
class A(object): 
    @Overload 
    def print_first(self, x): 
     return x[0] 

    @print_first.overload(str) 
    def p_first(self, x): 
     return x.split()[0] 

    def __repr__(self): 
     return "class A Instance" 

a = A() 
assert a.print_first([1,2,3]) == 1 
assert a.print_first("one two three") == "one" 

@Overload 
def flatten(seq): 
    return [seq] 

@flatten.overload(list, tuple) 
def flat(seq): 
    return sum((flatten(item) for item in seq), []) 

assert flatten([1,2,[3,4]]) == [1,2,3,4] 
assert flat([1,2,[3,4]]) == [1,2,3,4]