0

I am learning Python and while I was doing exercises with decorators, I found a case that I don't understand. The code bellow, inspired by the tutorial I use, is working:

>>> def maxExeTime(absMax,printMax=False):
...     def decorFun(toDo):
...         storeInfo = {"exeMax":0}
...         def modified(*p,**pp):
...             startTime = time.time()
...             returnValue = toDo(*p,**pp)
...             exeTime = time.time() - startTime
...             if exeTime > storeInfo["exeMax"]:
...                 storeInfo["exeMax"] = exeTime
...             if printMax:
...                 print("max execution time = {}".format(storeInfo["exeMax"]))
...             if exeTime > absMax:
...                 raise Exception("Max exe time reached: {}".format(exeTime))
...             return returnValue
...         return modified
...     return decorFun
...
>>> @maxExeTime(15,printMax=True)
... def mul(x,y):
...     input("Press Enter...")
...     return x*y
...
>>>
>>> mul(3,4)
Press Enter...
max execution time = 1.1439800262451172
12
>>> mul(3,5)
Press Enter...
max execution time = 2.1652064323425293
15
>>> mul(3,7)
Press Enter...
max execution time = 2.1652064323425293
21
>>> mul(3,10)
Press Enter...
max execution time = 21.074586629867554
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 13, in modified
Exception: Max exe time reached: 21.074586629867554
>>>

But I don't understand why the dictionary storeInfo is persistent and can be re-used from one call to the other. If I try to store the information directly in a variable (ie exeMax=0) and adapt the code accordingly, I get the behavior that I was expecting with an exception at run time: UnboundLocalError: local variable 'exeMax' referenced before assignment when I try to compare the current execution time with the actual max value (if exeTime > exeMax:)

Why does the dictionary persists out of the scope of the maxExeTime function execution?

Thanks to the links an the redirection to duplicate questions, I discovered closure which was a concept I never see before. Good. And I understand why it does not work in my test with the local variable exeMax since I didn't declare it as nonlocal. But I didn't do it either for the dictionary storeInfo, and it is working. Why does this type of object have a different behavior?

Edit

@kindall what you say explains why it is necessary to use nonlocal when I use and integer directly since it seems that integers have a different behavior/rules than other objects, and I want to modify the value in the inner function.

But I still don't understand why if exeTime < exeMax: fails when exeTime < storeInfo["exeMax"] does not, and then why the next code is valid.

>>> a = 12
>>> def m(x):
...     return a*x
...
>>> m(3)
36
>>> a = 15
>>> m(3)
45 

For the moment, all these differences in behavior make the variable scope definition looking fuzzy for me. I need to find a clear definition of it, with, I hope, a small set of rules and no exception.

Pascal
  • 13,977
  • 2
  • 24
  • 32
  • That `UnboundLocalError` is a completely different story: https://stackoverflow.com/a/370363/476 – deceze Oct 21 '21 at 13:17
  • You can reference closure variables, and change their contents, without them being declared `nonlocal`. The only thing you need `nonlocal` for is to change what the name points to (e.g. if you want to assign it a different dictionary, or other value, entirely). – kindall Oct 21 '21 at 15:26
  • *Assigning to* a variable and calling a method on an object are very different things… – deceze Oct 21 '21 at 15:45

0 Answers0