I have written a class like so:
class AbstractBigHandler(AbstractHandler, ActionTimer):
def __init__(self, path, data_container):
AbstractHandler.__init__(self)
ActionTimer.__init__(self, data_container)
@time_action
def handle(self, request):
# Do something
I wanted to time handle() method in it, so I came up with an idea to write a mixin class, which would provide a decorator class in it, as in my mind it would provide a nice separation of concerns in the code. Moreover, I wanted this mixin class to be reusable for other classes I will write later:
class ActionTimer:
def __init__(self, data_container):
self.data_container = data_container
self.class_name = self.__class__.__name__
def time_action(self, func):
def wrapper(self, func, *args, **kwargs):
start_time = datetime.now()
func_output = func.handle(args, kwargs)
end_time = datetime.now()
execution_time = end_time - start_time
print(self.__class__.__name__, " execution time: ", execution_time)
self.data_container.add_data(self.class_name, execution_time)
return func_output
return wrapper
The data_container argument in it, is just a singleton I intend to have floating around, and collect data in a its dictionary attribute (through the .add_data() method).
Now, when I try to run tests for the AbstractBigHandler class, it fails immediately on instantiation with the following error stack:
C:\project\tests>python -m unittest test_abstract_big_handler
Traceback (most recent call last):
File "C:\Users\***\AppData\Local\Continuum\anaconda3\lib\runpy.py", line 193, in _run_module_as_main
"__main__", mod_spec)
File "C:\Users\***\AppData\Local\Continuum\anaconda3\lib\runpy.py", line 85, in _run_code
exec(code, run_globals)
File "C:\Users\***\AppData\Local\Continuum\anaconda3\lib\unittest\__main__.py", line 18, in <module>
main(module=None)
File "C:\Users\***\AppData\Local\Continuum\anaconda3\lib\unittest\main.py", line 100, in __init__
self.parseArgs(argv)
File "C:\Users\***\AppData\Local\Continuum\anaconda3\lib\unittest\main.py", line 147, in parseArgs
self.createTests()
File "C:\Users\***\AppData\Local\Continuum\anaconda3\lib\unittest\main.py", line 159, in createTests
self.module)
File "C:\Users\***\AppData\Local\Continuum\anaconda3\lib\unittest\loader.py", line 220, in loadTestsFromNames
suites = [self.loadTestsFromName(name, module) for name in names]
File "C:\Users\***\AppData\Local\Continuum\anaconda3\lib\unittest\loader.py", line 220, in <listcomp>
suites = [self.loadTestsFromName(name, module) for name in names]
File "C:\Users\***\AppData\Local\Continuum\anaconda3\lib\unittest\loader.py", line 154, in loadTestsFromName
module = __import__(module_name)
File "C:\project\tests\test_abstract_big_handler.py", line 5, in <module>
from project.common.handlers.AbstractBigHandler import AbstractBigHandler
File "C:\project\common\handlers\AbstractBigHandler.py", line 17, in <module>
class AbstractBigHandler(AbstractHandler, ActionTimer):
File "C:\project\common\handlers\AbstractBigHandler.py", line 69, in AbstractBigHandler
@time_action
NameError: name 'time_action' is not defined
I am not sure why this is happening - since ActionTimer class is added as a second inherited class in AbstractBigHandler class, its method time_action() should be (and is, when I check with PDB) present in the methods of the AbstractBigHandler.
Help?
Because time_action
is the decorator, you don't need to pass func
again in wrapped
. Since the container is Singleton, you can employ classmethod and reference it directly.
from datetime import datetime
import random
class AbstractHandler(object):
pass
class ActionTimer(object):
data_container = []
def __init__(self, data_container):
self.data_container = data_container
self.class_name = self.__class__.__name__
@classmethod
def time_action(cls, func):
def wrapper(*args, **kwargs):
start_time = datetime.now()
func_output = func(*args, **kwargs)
end_time = datetime.now()
execution_time = end_time - start_time
# just some random value to show results (remove)
execution_time = execution_time.total_seconds() + random.randrange(1, 10, 1)
print(cls.__name__, " execution time: ", execution_time)
cls.data_container.append((cls.__name__, execution_time))
return func_output
return wrapper
class AbstractBigHandler(AbstractHandler, ActionTimer):
def __init__(self, path, data_container):
AbstractHandler.__init__(self)
ActionTimer.__init__(self, data_container)
@classmethod
@ActionTimer.time_action
def handle(cls, request):
print(request, "CONTAINER:", cls.data_container)
a = AbstractBigHandler("path", [])
for i in range(5):
a.handle("some-request-" + str(i))
print("FINAL:", ActionTimer.data_container)
Produces the following:
some-request-0 CONTAINER: []
ActionTimer execution time: 9.0
some-request-1 CONTAINER: [('ActionTimer', 9.0)]
ActionTimer execution time: 6.0
some-request-2 CONTAINER: [('ActionTimer', 9.0), ('ActionTimer', 6.0)]
ActionTimer execution time: 9.0
some-request-3 CONTAINER: [('ActionTimer', 9.0), ('ActionTimer', 6.0), ('ActionTimer', 9.0)]
ActionTimer execution time: 3.0
some-request-4 CONTAINER: [('ActionTimer', 9.0), ('ActionTimer', 6.0), ('ActionTimer', 9.0), ('ActionTimer', 3.0)]
ActionTimer execution time: 5.0
FINAL: [('ActionTimer', 9.0), ('ActionTimer', 6.0), ('ActionTimer', 9.0), ('ActionTimer', 3.0), ('ActionTimer', 5.0)]
Unfortunately, inherited namespaces are handled only after a class body has been fully defined. There's no easy way for a child class like AbstractBigHandler
to reference attributes of its parent classes at class definition time. A much simpler example of the issue would be:
class A:
foo = 1
class B(A): # inherit the foo class attribute
bar = foo + 1 # but looking it up here fails, with the same NameError you're seeing
To fix this, you'll either need to reference the parent class explicitly (eg A.foo
from within B
) or move the value you wanted to inherit out of the parent class to be a global variable instead. This often is the best approach for decorators that need to be applied at definition time of the child class, not when there are instances to be passed as self
to a method.
Indeed, this is the second issue I see with your code, that your decorator has some messed up arguments (both in the decorator itself, and in the wrapper function inside). I'd suggest a structure like this:
class ActionTimer:
def __init__(self, data_container):
self.data_container = data_container
self.class_name = self.__class__.__name__
def time_action(func): # top-level function, no self arg here
def wrapper(self, *args, **kwargs): # no func arg here, but we *do* have a self arg
start_time = datetime.now()
func_output = func(self, args, kwargs) # don't call handle, *we're* handle!
end_time = datetime.now()
execution_time = end_time - start_time
print(self.__class__.__name__, " execution time: ", execution_time)
self.data_container.add_data(self.class_name, execution_time)
return func_output
return wrapper
Now the AbstractBigHandler
class will work as you've written it.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.