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
0

4 Answers 4

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
Sign up to request clarification or add additional context in comments.

2 Comments

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.
@kolin I'm not I understand what you're asking. What do you mean by 'overwriting functions'?
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)

Comments

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

6 Comments

The top code doesn't output for me what you have there. Call Test() method is printed.
you ran the exact code snippet I pasted there? Or is the code you running different?
Copied it directly. Ran it in 3.7.
Oh! I tested it out in 3.8.2 originally. But, then I tested out on colab with 3.6.9 - there also it worked! Not sure why your 3.7 would mess it up! And it's giving you Test as attr!!
I'm a bonehead. I ran this in the debugger of Pycharm instead of running it straight in the interpreter. The code works fine. Since this code is messing with __getattribute__ there isn't a clean way to differentiate the regular calls from the ones that the debugger is making.
|
-2

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

Comments

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.