3

Let there be a class with functions:

class Tester: @Logger() def __init__(self): print(__class__) @Logger() def func(self, num): return num**2 

where Logger is a decorator roughly defined as:

from typing import Optional, Any from logging import getLogger class Logger: def __init__(self): self.logger = getLogger() self.logging_function = getattr(self, 'function') def __call__(self, decorator: callable): def f(*args, **kwargs): return self.logging_function(decorator, *args, **kwargs) return f def function(self, func: callable, *args: Optional[Any], **kwargs: Optional[Any]): func_name = Logger.get_name(func) self.logger.info(f"Starting: {func_name}.") return func(*args, **kwargs) @staticmethod def get_name(func): return f'__init__ {func.__class__.__name__}' if func.__name__ == '__init__' else func.__name__ 

How can we edit the Logger get_name function, such that if the function being run is a class __init__ that the name returned is __init__ Tester, but if the function is named something else it merely returns the function __name__?

(AKA) Expected output:

>>> test = Tester() INFO: Starting __init__ Tester. <class '__main__.Tester'> >>> test.func(3) INFO: Starting func. 9 

Current Output:

>>> test = Tester() INFO: Starting __init__ function. <class '__main__.Tester'> >>> test.func(3) INFO: Starting func. 9 
4
  • Did you mean to decorate with Logger() instead of with Logger? Commented Sep 8, 2021 at 18:19
  • The class Logger defines the Logger decorator. Commented Sep 8, 2021 at 18:23
  • 1
    Yes I get it, but you're decorating with the class instead of with an instance of it. Which causes an error. Commented Sep 8, 2021 at 18:23
  • Your right, I just fixed it. Commented Sep 8, 2021 at 18:24

3 Answers 3

3
+50

You could use the qualified name instead like this:

@staticmethod def get_name(func): return func.__qualname__ 

Which will give you something like:

>>> test = Tester() INFO:Starting: Tester.__init__. <class '__main__.Tester'> >>> test.func(3) INFO:Starting: Tester.func. 9 

You might also be interested in the standard LogRecord attribute funcName, which does a similar thing from within the function. A basic demo of that:

import logging logging.basicConfig( level=logging.INFO, format="%(levelname)s (%(funcName)s) %(message)s", ) log = logging.getLogger() class A: def my_method(self): log.info("hello") def bar(): log.info("world") A().my_method() bar() 

Outputs this:

INFO (my_method) hello INFO (bar) world 
Sign up to request clarification or add additional context in comments.

Comments

2

Starting with Python 3.3, you should be able to use func.__qualname__ to get there.

I personally would keep it simple for consistency:

@staticmethod def get_name(func): return func.__qualname__ 

But to exactly match your expected output, I believe this will work:

@staticmethod def get_name(func): if '.__init__' in func.__qualname__: class_name = func.__qualname__.split('.')[-2] # this prints the innermost type in case of nested classes return f'__init__ {class_name}' return func.__name__ 

Comments

1

You can use the following function to get function name.

get_name Code Snippet to Produce Your Desire Output:

@staticmethod def get_name(func): func_name = func.__qualname__.split('.') if '__init__' in func.__qualname__ else func.__name__ func_name = ' '.join(func_name[::-1]) if isinstance(func_name, list) else func_name return func_name 

Output:

Starting: __init__ Tester. <class '__main__.Tester'> Starting: func. 

If you want more generic then you can use the following function.

get_name Generic Code Snippet:

@staticmethod def get_name(func): func_name = func.__qualname__.split('.') if '.' in func.__qualname__ else func.__name__ func_name = ' '.join(func_name[::-1]) if isinstance(func_name, list) else func_name return func_name 

Output:

Starting: __init__ Tester. <class '__main__.Tester'> Starting: func Tester. 

Complete Working Code As You Desired:

from typing import Optional, Any from logging import getLogger class Logger: def __init__(self): self.logger = getLogger() self.logging_function = getattr(self, 'function') def __call__(self, decorator: callable): def f(*args, **kwargs): return self.logging_function(decorator, *args, **kwargs) return f def function(self, func: callable, *args: Optional[Any], **kwargs: Optional[Any]): func_name = Logger.get_name(func) self.logger.info(f"Starting: {func_name}.") return func(*args, **kwargs) @staticmethod def get_name(func): func_name = func.__qualname__.split('.') if '__init__' in func.__qualname__ else func.__name__ func_name = ' '.join(func_name[::-1]) if isinstance(func_name, list) else func_name return func_name class Tester: @Logger() def __init__(self): print(__class__) @Logger() def func(self, num): return num**2 test = Tester() test.func(3) 

Output:

Starting: __init__ Tester. <class '__main__.Tester'> Starting: func. 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.