Callable object decorator applied to method doesn't get self argument on input

后端 未结 1 1506
再見小時候
再見小時候 2020-12-10 08:36
import functools


class Decor(object):
    def __init__(self, func):
         self.func = func

    def __call__(self, *args, **kwargs):
        def closure(*args,          


        
相关标签:
1条回答
  • 2020-12-10 08:48

    Python functions act as descriptors, which means that whenever you access a function on a class or instance, their .__get__() method is invoked and a method object is returned which keeps a reference to the original function, and for instances, a reference to the instance. Method object then acts as wrappers; when called they call the underlying function and pass in the instance reference as self.

    Your callable class object, on the other hand, does not implement the descriptor protocol, it has no .__get__() method, and thus it never is given an opportunity to bind to the instance. You'll have to implement this functionality yourself:

    class Decor(object):
        def __init__(self, func):
             self.func = func
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            d = self
            # use a lambda to produce a bound method
            mfactory = lambda self, *args, **kw: d(self, *args, **kw)
            mfactory.__name__ = self.func.__name__
            return mfactory.__get__(instance, owner)
    
        def __call__(self, instance, *args, **kwargs):
            def closure(*args, **kwargs):
                print instance, args, kwargs
                return self.func(instance, *args, **kwargs)
            return closure(*args, **kwargs)
    

    Demo:

    >>> class Victim(object):
    ...     @Decor
    ...     def sum(self, a, b):
    ...         return a+b
    ... 
    >>> v = Victim()
    >>> v.sum
    <bound method Victim.sum of <__main__.Victim object at 0x11013d850>>
    >>> v.sum(1, 2)
    <__main__.Victim object at 0x11013d850> (1, 2) {}
    3
    

    It is not a good idea to store the instance you are bound to directly on the Decor instance; this is a class attribute, shared among instances. Setting self.instance is neither thread-safe nor allows methods to be stored for later invocation; the most recent __get__ call will alter self.instance and lead to hard-to-resolve bugs.

    You can always return a custom proxy object instead of a method:

    class DecorMethod(object):
        def __init__(self, decor, instance):
            self.decor = decor
            self.instance = instance
    
        def __call__(self, *args, **kw):
            return self.decor(instance, *args, **kw)
    
        def __getattr__(self, name):
            return getattr(self.decor, name)
    
        def __repr__(self):
            return '<bound method {} of {}>'.format(self.decor, type(self))
    

    and use that in your Decor.__get__ instead of producing a method:

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return DecorMethod(self, instance)
    

    The DecorMethod here passes any requests for unknown attributes back to the Decor decorator instance:

    >>> class Victim(object):
    ...     @Decor
    ...     def sum(self, a, b):
    ...         return a + b
    ... 
    >>> v = Victim()
    >>> v.sum
    <bound method <__main__.Decor object at 0x102295390> of <class '__main__.DecorMethod'>>
    >>> v.sum.func
    <function sum at 0x102291848>
    
    0 讨论(0)
提交回复
热议问题