4

I have a decorator to control time limit, if the function execution exceeds limit, an error is raised.

def timeout(seconds=10):
    def decorator(func):
        # a timeout decorator
    return decorator

And I want to build a class, using the constructor to pass the time limit into the class.

def myClass:
    def __init__(self,time_limit):
        self.time_limit = time_limit

    @timeout(self.time_limit)
    def do_something(self):
        #do something

But this does not work.

File "XX.py", line YY, in myClass
    @timeout(self.tlimit)
NameError: name 'self' is not defined

What's the correct way to implement this?

MTANG
  • 508
  • 5
  • 16
  • 1
    Welcome to SO. The phrase "This does not work" is useless. It helps no one and doesn't describe any issue. What doesn't work, what happens instead, what did you expect to happen? – dfundako Oct 31 '18 at 17:19
  • 1
    You *could* do something like `self.do_something = timeout(time_limit)(self.do_something)`, assuming your decorator works. This adds a function object to each instance of your class. But `self` is not in the class body's scope – juanpa.arrivillaga Oct 31 '18 at 17:22
  • @dfundako I updated and 'does not work' means that the problem does not run, with the error I posted. – MTANG Oct 31 '18 at 17:23
  • @juanpa.arrivillaga Just curious why the decorator way won't work. – MTANG Oct 31 '18 at 17:24
  • @MTANG because `self` is not in scope. It's still a decorator, it's just not using the syntactic sugar. – juanpa.arrivillaga Oct 31 '18 at 17:25
  • 3
    @MTANG That is not how you define a class.... – dfundako Oct 31 '18 at 17:25
  • @juanpa.arrivillaga Does that mean Python check if '@timeout(self.time_limit)' is valid before my class instantialized? – MTANG Oct 31 '18 at 17:27
  • @dfundako Simply saying 'That is not how you define a class' is useless. It helps no one and doesn't describe any possible solution/improvement to the problem. – MTANG Oct 31 '18 at 17:28
  • 2
    You mean instance initialization? Yes. That is in the class body, and all that code is executed at *class definition time* i.e. during *class initialization*, before you've created any instances. `self` is *merely* the first argument to a function. It works like any other argument, i.e. it creates a local variable when *the function is called*. – juanpa.arrivillaga Oct 31 '18 at 17:29
  • @MTANG "Why does this not work" vs "How to define a class in Python". Apples and oranges, dude. I'm not sure what to say if you don't find the feedback of your incorrect class declaration helpful. Not to mention your entire post boils down to "Python how to make a decorator" in Google. – dfundako Oct 31 '18 at 17:30
  • @dfundako I think fundamentally it is a misunderstanding of how classes work in Python (i.e. trying to access `self` in the class scope). – juanpa.arrivillaga Oct 31 '18 at 17:33
  • Does this answer your question? [How can I use a class instance variable as an argument for a method decorator in Python?](https://stackoverflow.com/questions/1231950/how-can-i-use-a-class-instance-variable-as-an-argument-for-a-method-decorator-in) – Kiran Kumar Kotari Jul 12 '21 at 14:44

2 Answers2

3

self.time_limit is only available when a method in an instance of your class is called.

The decorator statement, prefixing the methods, on the other hand is run when the class body is parsed.

However, the inner part of your decorator, if it will always be applied to methods, will get self as its first parameter - and there you can simply make use of any instance attribute:

def timeout(**decorator_parms):
    def decorator(func):
        def wrapper(self, *args, **kwargs):
             time_limit = self.time_limit
             now = time.time()
             result = func(self, *args, **kwargs)
             # code to check timeout
             ..
             return result
        return wrapper
    return decorator

If your decorator is expected to work with other time limits than always self.limit you could always pass a string or other constant object, and check it inside the innermost decorator with a simple if statement. In case the timeout is a certain string or object, you use the instance attribute, otherwise you use the passed in value;

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • It works! Thanks. Also I wonder why I need to use @timeout() instead of @timeout to make it work? – MTANG Oct 31 '18 at 17:36
  • You need the extra `()` because it was first designed as a "decorator that would take parameters" - that requires an outer level of functions (the function called `timeout` above) just to take note of these parameters. If you are using none, then the outer level is not needed at all, and the current intermediary function (`def decorator(func):`) can be placed on the outer level. If you do that, the extra parenthesis are no longer needed. – jsbueno Oct 31 '18 at 17:50
  • Thanks a lot! I'm not that familiar with decorator. – MTANG Oct 31 '18 at 17:55
0

You can also decorate a method in the constructor:

def myClass:
    def __init__(self,time_limit):
        self.do_something = timeout(time_limit)(self.do_something)
    
    def do_something(self):
        #do something
Роман Коптев
  • 1,555
  • 1
  • 13
  • 35