【问题标题】:Method overloading decorator方法重载装饰器
【发布时间】:2012-07-07 00:31:34
【问题描述】:

我正在尝试编写一个装饰器,为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)?

【问题讨论】:

  • 您是否查看过 PEAK.Rules 中的参考实现,或 pje 早期 PEP 和列表帖子所附的十几个其他参考实现?如果您尝试实际使用它,而不是尝试探索 Python,那么使用他的工作(至少其他一些人已经使用并测试过)可能比重复它更有意义。
  • @abarnert:我不知道这一点。感谢您的提醒。话虽如此,我真的只是想知道为什么我的实现没有按预期工作以及如何解决它。正如你所说,我是“探索 Python”。
  • 首先,你知道@functools.wraps等吗?他们会让你的生活更轻松,但这对你没有帮助。无论如何,这里问题的第一部分是您的 default_function 被替换为类似函数的类,它不是方法(Overload.__call__ 需要一个自我,但那是 Overload 实例,而不是 MyClass)。但显然你不能只做 __call__(self, realself, *args, **kwargs) 并期望它起作用。我今晚没有时间详细介绍;希望其他人能在我回来之前提供帮助。

标签: python decorator


【解决方案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__ 方法中模仿了这种行为。

【讨论】:

  • 太棒了。使用你的建议,我得到了它的工作。 +1 详细说明查找的工作原理。
【解决方案2】:

正如 abarnert 所说,当您使用类作为装饰器时,“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"))

产量:

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

【讨论】:

    【解决方案3】:

    作为参考,这里是工作实现,感谢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]
    

    【讨论】:

      猜你喜欢
      • 2011-08-25
      • 2012-02-09
      • 2020-01-11
      • 2014-01-14
      • 2021-09-28
      • 2012-01-29
      • 1970-01-01
      • 2020-01-02
      • 2018-09-29
      相关资源
      最近更新 更多