2012-07-07 95 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]