Skip to content Skip to sidebar Skip to footer

Passing 'self' Parameter During Methods Decorating In Python

I want to create decorator that shows which parameters were passed to function and methods. I have already written the code for functions, but methods are giving me a headaches. Th

Solution 1:

It's not working with you current design because of how classes work in Python.

When a class is instantiated, the functions on it get bound to the instance - they become bound methods, so that self is automatically passed.

You can see it happen:

classA:
    defmethod1(self):
        pass>>> A.method1
<function A.method1 at 0x7f303298ef28>
>>> a_instance = A()
>>> a_instance.method1
<bound method A.method1 of <__main__.A object at 0x7f303a36c518>>

When A is instantiated, method1 is magically transformed from a function into a bound method.

Your decorator replaces method1 - instead of a real function, it is now an instance of _PrintingArguments. The magic that turns functions into bound methods is not applied to random objects, even if they define __call__ so that they behave like a function. (But that magic can be applied, if your class implements the Descriptor protocol, see ShadowRanger's answer!).

classDecorator:
    def__init__(self, func):
        self.func = func

    def__call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)


classA:
    @Decoratordefmethod1(self):
        pass>>> A.method1
<__main__.Decorator object at 0x7f303a36cbe0>
>>> a_instance = A()
>>> a_instance.method1
<__main__.Decorator object at 0x7f303a36cbe0>

There is no magic. method1 on the instance of A is not a bound method, it's just a random object with a __call__ method, which will not have self passed automatically.

If you want to decorate methods you have to replace the decorated function with another real function, an arbitrary object with __call__ will not do.

You could adapt your current code to return a real function:

import functools

class_PrintingArguments:
    def__init__(self, default_comment, comment_variable):
        self.comment_variable = comment_variable
        self.default_comment = default_comment

    def__call__(self, function):
        @functools.wraps(function)defdecorated(*args, **kwargs):
            comment = kwargs.pop(self.comment_variable, self.default_comment)
            params_str = [repr(arg) for arg in args] + ["{}={}".format(k, repr(v)) for k, v in kwargs.items()]
            function_call_log = "{}({})".format(function.__name__, ", ".join(params_str))
            print("Function execution - '{}'\n\t{}".format(comment, function_call_log))
            function_return = function(*args, **kwargs)
            print("\tFunction executed\n")
            return function_return
        return decorated

deffunction_log(_function=None, default_comment="No comment.", comment_variable="comment"):
    decorator = _PrintingArguments(
        default_comment=default_comment,
        comment_variable=comment_variable,
    )
    if _function isNone:
        return decorator
    else:
        return decorator(_function)

Solution 2:

If you want _PrintingArguments to bind the same way as a plain function, this is actually possible, you just need to implement the descriptor protocol yourself to match the way built-in functions behave. Conveniently, Python provides types.MethodType, which can be used to create a bound method from any callable, given an instance to bind to, so we use that to implement our descriptor's __get__:

import types

class_PrintingArguments:
    # __init__ and __call__ unchangeddef__get__(self, instance, owner):
        if instance isNone:
            return self  # Accessed from class, return unchangedreturn types.MethodType(self, instance)  # Accessed from instance, bind to instance

This works as you expect on Python 3 (Try it online!). On Python 2 it's even simpler (because unbound methods exist, so the call to types.MethodType can be made unconditionally):

import types

class_PrintingArguments(object):  # Explicit inheritance from object needed for new-style class on Py2# __init__ and __call__ unchangeddef__get__(self, instance, owner):
        return types.MethodType(self, instance, owner)  # Also pass owner

Try it online!

For slightly better performance (on Python 2 only), you could instead do:

class _PrintingArguments(object):  # Explicit inheritance fromobject needed fornew-style classon Py2
    # __init__ and __call__ unchanged

# Defined outside class, immediately after dedent
_PrintingArguments.__get__ = types.MethodType(types.MethodType, None, _PrintingArguments)

which moves the implementation of __get__ to the C layer by making an unbound method out of types.MethodType itself, removing byte code interpreter overhead from each call.

Post a Comment for "Passing 'self' Parameter During Methods Decorating In Python"