1

I'm working on a decorator that runs a function on the first argument of the decorated method that is not self. To accomplish this, I am currently using two decorators with basically the same code. The only difference is that one has a function that contains self before the first argument.

import functools

def wrap(function):
    @functools.wraps(function)
    def wrapper(first_argument, *args, **kwargs):
        someFuntion(first_argument)
        return function(first_argument, *args, **kwargs)
    return wrapper

def wrapClass(function):
    @functools.wraps(function)
    def wrapper(self, first_argument, *args, **kwargs):
        someFuntion(first_argument)
        return function(self, first_argument, *args, **kwargs)
    return wrapper

I would like to combine the two decorators into a single one in a format like this:

import functools

def wrap(function):
    if isPartOfClass(function):
        @functools.wraps(function)
        def wrapper(self, first_argument, *args, **kwargs):
            someFuntion(first_argument)
            return function(self, first_argument, *args, **kwargs)
    else:
        @functools.wraps(function)
        def wrapper(first_argument, *args, **kwargs):
            someFuntion(first_argument)
            return function(first_argument, *args, **kwargs)
    return wrapper

In Python 2 I used to be able to simply detect if the function was an unbound method. However, I know that that concept was removed in Python 3.

I've already come up with several detection methods that won't work.

  1. I could first check if the first argument is called self, but that is not the required name.
  2. I could try to detect whether the function has 1 (regular) or 2 (class) arguments, but the regular function might contain more than 1 argument. Regardless of the number of arguments, I only care about the first argument that is not self.
  3. I could attempt to detect the first argument based on type, but there is no guarantee of exactly what type it will be.

Is there any practical way for me to do this detection, or am I going to be stuck with the two decorators?

1
  • At the time that the decorator is applied to a method written in a class block, the method isn't "part of" a class. The object representing the class isn't created until after all the objects representing the methods (which are at this point still just ordinary functions) are. Commented Nov 26, 2020 at 13:43

1 Answer 1

2

Would checking the __qualname__ for your usecase?

In [19]: class Foo: 
    ...:     def __init__(self, x): 
    ...:         self.x = x 
    ...:     def __str__(self): return "foo" 
    ...:     def __repr__(self): return f"Foo({self.x})" 
    ...:     def bar(self): return 1 
    ...:                                                                                                                                                                                                                                                      

In [20]: b = Foo.bar                                                                                                                                                                                                                                          

In [21]: b                                                                                                                                                                                                                                                    
Out[21]: <function __main__.Foo.bar(self)>

In [22]: b.__qualname__                                                                                                                                                                                                                                       
Out[22]: 'Foo.bar'

The presence of the . in the __qualname__ should be a dead giveaway for a method in a class. Other functions don't have that:

In [24]: print.__qualname__                                                                                                                                                                                                                                   
Out[24]: 'print'

For a non-builtin function

In [25]: def foo(x): return ''                                                                                                                                                                                                                                

In [26]: foo.__qualname__                                                                                                                                                                                                                                     
Out[26]: 'foo'

For an imported function:

In [23]: inspect.getargs.__qualname__                                                                                                                                                                                                                         
Out[23]: 'getargs'
Sign up to request clarification or add additional context in comments.

1 Comment

That'll do it, as long as something else that wraps it properly preserves the qualified name, and also keeps the property in the first place. I found that one of the wrappers I actually use (thankfully the one that is always last to be executed, so it doesn't affect this) actually removed the property entirely. Thank you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.