How to remove the self argument from a decorator's arguments if there is one

Question:

Suppose I have a simple decorator, and a classes method and a function which I both decorate with that decorator:

import functools

def decorator(func):
    @functools.wraps(func)
    def call(*args):
        print(args)
        func(*args)
    return call

class cls:
    @decorator
    def meth(self, a):
        pass

@decorator
def func(c):
    pass

cls().meth("a")
func("c")

I get following output:

(<__main__.cls object at 0x7f4665c50130>, 'a')
('c',)

But I want to remove the self argument when the decorator is used on a method, so that the output becomes:

('a',)
('c',)

But, if I simply add args.pop(0), I will remove the first argument even if it is not self. How can I only remove the first argument if it is self ?

Note: I read some solutions with a long code using inspect – but there must be a shorter, easier way in this great and simple-to-use programming language ?

EDIT: Using @staticmethod is not an option for me, because I need the self parameter in the method itself. I only don’t want it to get printed.

Asked By: TheEagle

||

Answers:

The simplest way to achieve this is to explicitly tell your decorator that it’s decorating a class method. Your decorator will take a keyword argument denoting that.

def decorator(is_classmethod=False):
    def wrapper(func):
        @functools.wraps(func)
        def call(*args):
            if is_classmethod:
                print(args[1:])  # omit args[0]
            else:
                print(args)
            return func(*args)      # pass all the args to the function
        return call
    return wrapper

class cls:
    @decorator(is_classmethod=True)
    def meth(self, a):
        pass

@decorator
def func(c):
    pass

cls().meth("a")
func("c")

Since the user already knows what they’re decorating, you can avoid complicated introspection code in the decorator, and give the user flexibility of processing the first argument or not.

please take a look at my answer to this SO question: Decorators with parameters? for a cleaner (IMHO) way to write decorators with parameters. There are some really good answers in that post.

Answered By: srj

You can filter args for output by existence of some python magic. For example:

import functools

def decorator(func):
    @functools.wraps(func)
    def call(*args):
        print(list(filter(lambda x: not hasattr(x, '__dict__'), args)))
        func(*args)
    return call

class cls:
    @decorator
    def meth(self, a):
        pass

@decorator
def func(c):
    pass

cls().meth("a")  # ['a']
func("c")  # ['c']
Answered By: Andy Pavlov