I have a few methods that require a certain condition to be applied to function properly. To avoid code repetition I've created a decorator to check if this condition is applied and only then execute the method. Here is the code:
class ClientHandler:
def __init__(self):
self.__is_dead = False
self.c_ident = c_ident
def __s_operation(func):
@functools.wraps(func)
def check(*args):
self = args[0]
if not self.__is_dead:
func(*args)
else:
logger.error(
"couldn't proceed - client {} is dead!".format(
self.c_ident
)
)
return check
@__s_operation
def __receive_m(self, size) -> bytes:
...
@__s_operation
def close_c(self, message: str) -> None:
...
I was following this answer to be able to use class attributes and call class methods from my decorator, but I ran into 2 errors:
'ClientHandler' object is not callableon thefunc(*args)line, when I'm trying to call a class method from a decorator.function '__s_operation' lacks a positional argument, when I'm trying to add my decorator@__s_operationover a class method.
I don't understand what is the problem and how to solve it, because in the other answer on stack overflow everything seems working. Also, I'm not sure if I will be able to reach my private attributes and private methods from this decorator. I would be grateful for your help. Thank you.
2 Answers 2
@Yzkodot's solution is good.
But I miss a @staticmethod in his example - to make explicitly clear that no self in the first argument position is needed.
import functools
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ClientHandler:
# Make this a staticmethod so it doesn't expect self
@staticmethod
def _s_operation(func):
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
if not self._is_dead:
return func(self, *args, **kwargs)
else:
logger.error(f"Couldn't proceed - client {self.c_ident} is dead!")
return None
return wrapper
def __init__(self, c_ident: str):
self._is_dead = False
self.c_ident = c_ident
@_s_operation
def receive_message(self, size: int) -> bytes:
logger.info(f"{self.c_ident} received {size} bytes.")
return b"x" * size
@_s_operation
def close_connection(self, message: str) -> None:
logger.info(f"{self.c_ident} closed with message: {message}")
self._is_dead = True
Use it as:
client = ClientHandler("alpha")
client.receive_message(10) ## => Will work
client.close_connection("done") ## => Will close
client.receive_message(5) ## => Blocked by the decorator
Output:
INFO:__main__:alpha received 10 bytes.
INFO:__main__:alpha closed with message: done
ERROR:__main__:Couldn't proceed - client alpha is dead!
1 Comment
I think your problem lies on calling @functools.wraps(func) over the check() function. This one is already supposed to be the wrapper.
I slightly modified your code as following (only for testing) and it seems to work:
class ClientHandler:
def __init__(self):
self.__is_dead = False
def __s_operation(func):
def check(*args):
print("The decorator code has worked!")
self = args[0]
if not self.__is_dead:
func(*args)
else:
logging.error(
"couldn't proceed - client {} is dead!".format(
self.c_ident
)
)
return check
@__s_operation
def receive_m(self) -> bytes:
print("message received")
if __name__ == "__main__":
ch = ClientHandler()
ch.receive_m()
Over the terminal I get correctly both print statements.
1 Comment
functools.wraps to not lose information about original function. As explained here: stackoverflow.com/a/309000/19329071 Explore related questions
See similar questions with these tags.