8

PEP 3124で言及されているものと同様に、メソッドのオーバーロード機能を Python に提供するデコレータを作成しようとしています。

私が書いたデコレータは、通常の関数ではうまく機能しますが、クラスのメソッドでは機能しません。

デコレータは次のとおりです。

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)
>>>

私の質問は次のとおりです:装飾されたメソッド内から(つまり)のインスタンスにアクセスするにはどうすればよいですか?MyClassself

4

3 に答える 3

2

基本的に、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__方法では、この動作を模倣しています。

于 2012-07-07T22:29:02.257 に答える
1

abarnert が言うように、クラスをデコレーターとして使用しているため、「自己」は、期待/期待どおり、MyClass ではなく Overload のインスタンスです。

簡単な解決策が見つかりませんでした。私が思いついた最善の方法は、クラスをデコレータとして使用せず、代わりに関数を使用することですが、デフォルトの辞書を持つ2番目の引数を使用します。これは変更可能な型であるため、関数が呼び出されるたびに同じ辞書になります。これを使用して、「クラス変数」を保存します。残りは、ソリューションと同様のパターンに従います。

例:

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)
于 2012-07-07T20:03:22.110 に答える
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]
于 2012-07-08T00:16:01.010 に答える