0

I want to do some preparatory actions (e.g. logging) before function is called.

For example,

class Test(object):
  def float(self, target):
    logging.info('Call float() function')
    return float(target)

  def int(self, target):
    logging.info('Call int() function')
    return int(target)

t = Test()
a = t.float('123')
b = t.int('123')

However, now I have about 20 functions need to do the same thing. Is there any way I can use 1 function to fit all of them? Something like,

class Test(object):
  def __getattr__(self, name):
    def wrapper(args):
      return func(args) # is there any built-in funciton can get function object?
    logging.info('Call %s() function' % name)
    return wrapper
Almog
  • 452
  • 1
  • 7
  • 13
LaoDa581
  • 563
  • 4
  • 18

4 Answers4

5

You can use a decorator, to print out the name of the method before continuing to call the method:

def namer(var):
    def wrapper(*args, **kwargs):
        print(f'Call {var.__name__}() method')
        return var(*args, **kwargs)
    return wrapper

class Test(object):
    @namer
    def float(self, target):
        return float(target)

    @namer
    def int(self, target):
        return int(target)

t = Test()
a = t.float('123')
b = t.int('123')
print(a, b)

Output:

Call float() method
Call int() method
123.0 123
Hampus Larsson
  • 3,050
  • 2
  • 14
  • 20
  • Thanks for your advise. But is there any chance I can catch the function object and use it directly? Or I have to keep overwriting functions if 20 more functions are added in the future. – LaoDa581 Aug 21 '20 at 17:19
  • @kolin I'm not I understand what you're asking. What do you mean by 'overwriting functions'? – Marcel Wilson Aug 21 '20 at 21:00
0

This is what you would want (and you wouldn't need to add a decorator for every function):

class Test(object):
    def float(self, target):
        return float(target)

    def int(self, target):
        return int(target)

    def __getattribute__(self, name):
      attr = super().__getattribute__(name)
      print(f'Call {attr.__name__}() method')
      return attr

t = Test()
a = t.float('123')
b = t.int('123')
print(a, b)

Output:

Call float() method
Call int() method
123.0 123
Partha Mandal
  • 1,391
  • 8
  • 14
0

This will include the arguments used in the logging of the method.

from functools import wraps

def logit(function):
    @wraps(function)
    def traced_func(self, *args, **kwargs):
        sargs = list(args)
        sargs += ["{farg}={fval!r}".format(farg=farg, fval=fval) for farg, fval in kwargs.items()]
        print('Call {}({})'.format(function.__name__, ", ".join(map(str,sargs))))
        rt = function(self, *args, **kwargs)
        return rt
    return traced_func

class Something(object):
    @logit
    def float(self, target, arg=None):
        return float(target)
    
    @logit
    def int(self, target):
        return int(target)


t = Something()

x = t.float(4, arg=9)
z = t.float(5)
y = t.int(8)

output

Call float(4, arg=9)
Call float(5)
Call int(8)

Generally I would advise putting the decorator above each method, that's a bit more deliberate and readable. However, you did ask if there was a way to avoid doing that...

Use a decorator's decorator on the class.

# use the same decorator as the above example along with this
def decorate_methods(decorator):
    def traced_func(cls):
        for attr in cls.__dict__:
            if callable(getattr(cls, attr)):
                setattr(cls, attr, decorator(getattr(cls, attr)))
        return cls
    return traced_func

@decorate_methods(logit)
class Test(object):
    def float(self, target):
        return float(target)

    def int(self, target):
        return int(target)
Marcel Wilson
  • 3,842
  • 1
  • 26
  • 55
-2

You could do func = eval(name). It's not very elegant, nor very secure, but might do the job for what you want.

Emile
  • 2,946
  • 2
  • 19
  • 22