I am making a simple decorator that outputs the returned value in uppercase. This is the code I tried:
class UpperDecorator:
def __init__(self, func, msg):
self.func = func
self.msg = msg
def __call__(self):
res = self.func(self.msg)
return res.upper()
#UpperDecorator
def message_app(msg):
return msg
res = message_app('Hi')
print(res)
upon running the code I get this error:
TypeError: __init__() missing 1 required positional argument: 'msg'
then I modified the constructor a bit (def __init__(self, func, msg=None):) and get this error:
TypeError: __call__() takes 1 positional argument but 2 were given
please help me solve it. Thank you
The parameters of the decorated function are passed to the __call__ method, not to the constructor __init__:
class UpperDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
res = self.func(*args, **kwargs)
return res.upper()
#UpperDecorator
def message_app(msg):
return msg
res = message_app('Hi')
print(res)
Prints:
HI
Related
I implemented two versions of the same program. The difference between them is that the first uses class variable direct access while the second the #classmethod decorator to return the class variable value.
Version 1:
class Singleton2:
obj = None
def __new__(cls, *args, **kwargs):
if getattr(cls, 'obj') is None:
cls.obj = object.__new__(cls)
return cls.obj
else:
return cls.obj
def __init__(self):
pass
singleton2 = Singleton2()
singleton3 = Singleton2()
print(singleton2 == singleton3)
Version 2
class Singleton2:
obj = None
def __new__(cls, *args, **kwargs):
if getattr(cls, 'obj') is None:
cls.obj = object.__new__(cls)
cls.obj()
else:
cls.obj()
def __init__(self):
pass
#classmethod
def obj(cls):
return cls.obj
singleton2 = Singleton2()
singleton3 = Singleton2()
print(singleton2 == singleton3)
When I debugged the first version, the if statement is executed. That's correct.
When I debugged the second version, the else statement is executed because the statement if getattr(cls, 'obj') is None is returning false. How come?
In the code below, I am using a metaclass along with a decorator to decorate all the user defined methods.
It works for all instance methods, but in cases of staticmethods it fails due to the self argument, to avoid that I am using a try and except block, which solves the problem. But in one of my projects, it's not working out.
Is there a better way of decorating the output of a staticmethod via a function decorator enclosed in a metaclass ?
from functools import wraps
import types
def decorator_function(input_function):
#wraps(input_function)
def wrapper(self, *args, **kwargs):
if kwargs.get("test_parameter"):
kwargs["test_parameter"] = 999
try:
result = input_function(self, *args, **kwargs)
except:
result = input_function(*args, **kwargs)
return result
return wrapper
class DecoratorMetaClass(type):
def __new__(meta, name, bases, class_dict):
klass = super().__new__(meta, name, bases, class_dict)
for key in dir(klass):
value = getattr(klass, key)
if isinstance(value, types.FunctionType) and "__" not in key:
wrapped = decorator_function(value)
setattr(klass, key, wrapped)
return klass
class InterfaceClass(metaclass=DecoratorMetaClass):
def function(self, test_parameter=1):
print(f"function - Test Parameter= {test_parameter}")
#staticmethod
def static_function(test_parameter=1):
print(f"static_function - Test Parameter= {test_parameter}")
class UserClass(InterfaceClass, metaclass=DecoratorMetaClass):
def __init__(self):
pass
def function_2(self, test_parameter=1):
print(f"function_2 - Test Parameter= {test_parameter}")
instance = UserClass()
instance.function(test_parameter=2)
instance.function_2(test_parameter=2)
instance.static_function(test_parameter=2)
print(isinstance(instance, InterfaceClass))
PS: I am not using a class decorator because it causes the isinstance checks to fail.
Explanation
The major problem goes down to the methods parameters. You were almost there.
You have to make the decorators arguments compatible to your methods parameters;
You can change the signature of the function wrapper from wrapper(self, *args, **kwargs) to wrapper(*args, **kwargs). Then just assign result = input_function(*args, **kwargs). You don't need the try/except block for this decorator;
def decorator_function(input_function):
#wraps(input_function)
def wrapper(*args, **kwargs):
if kwargs.get("test_parameter"):
kwargs["test_parameter"] = 999
return input_function(*args, **kwargs)
return wrapper
Ideally you should add to the methods *args (variable arguments) and **kwargs (variable named arguments) to make them compatible with your decorator;
In this case I added *args before the test_parameter=1 to the static_function in InterfaceClass.
class InterfaceClass(metaclass=DecoratorMetaClass):
#staticmethod
def static_function(*args, test_parameter=1):
print(f"static_function - Test Parameter= {test_parameter}")
Runnable Code
from functools import wraps
import types
def decorator_function(input_function):
#wraps(input_function)
def wrapper(*args, **kwargs):
if kwargs.get("test_parameter"):
kwargs["test_parameter"] = 999
return input_function(*args, **kwargs)
return wrapper
class DecoratorMetaClass(type):
def __new__(meta, name, bases, class_dict):
klass = super().__new__(meta, name, bases, class_dict)
for key in dir(klass):
value = getattr(klass, key)
if isinstance(value, types.FunctionType) and "__" not in key:
wrapped = decorator_function(value)
setattr(klass, key, wrapped)
return klass
class InterfaceClass(metaclass=DecoratorMetaClass):
def function(self, test_parameter=1):
print(f"function - Test Parameter= {test_parameter}")
#staticmethod
def static_function(*args, test_parameter=1):
print(f"static_function - Test Parameter= {test_parameter}")
class UserClass(InterfaceClass, metaclass=DecoratorMetaClass):
def __init__(self):
pass
def function_2(self, test_parameter=1):
print(f"function_2 - Test Parameter= {test_parameter}")
instance = UserClass()
instance.function(test_parameter=2)
instance.function_2(test_parameter=2)
instance.static_function(test_parameter=2)
UserClass.static_function(test_parameter=3)
print(isinstance(instance, InterfaceClass))
Output
function - Test Parameter= 999
function_2 - Test Parameter= 999
static_function - Test Parameter= 999
static_function - Test Parameter= 999
True
Addressing OP's comment
Considering test_parameter is always a named parameter, write the decorator_function as the following:
def decorator_function(input_function):
#wraps(input_function)
def wrapper(*args, **kwargs):
if kwargs.get("test_parameter"):
kwargs["test_parameter"] = 999
try:
result = input_function(*args, **kwargs)
except TypeError:
result = input_function(**kwargs)
return result
return wrapper
This way you don't need to change the methods signature.
If you call the functions also with positional arguments, you will need to check the type of the first argument inserted into args. Things get complicated and error prone.
I have decorator that I use as exceptions handler. I want to optimize it, because comparing with simple try...catch it is about 6 times slower.
The code of my decorator:
class ProcessException(object):
__slots__ = ('func', 'custom_handlers', 'exclude')
def __init__(self, custom_handlers=None):
self.func = None
self.custom_handlers: dict = custom_handlers
self.exclude = [QueueEmpty, QueueFull, TimeoutError]
def __call__(self, func):
self.func = func
return self.wrapper
def wrapper(self, *args, **kwargs):
if self.custom_handlers:
if isinstance(self.custom_handlers, property):
self.custom_handlers = self.custom_handlers.__get__(self, self.__class__)
if asyncio.iscoroutinefunction(self.func):
return self._coroutine_exception_handler(*args, **kwargs)
else:
return self._sync_exception_handler(*args, **kwargs)
async def _coroutine_exception_handler(self, *args, **kwargs):
try:
return await self.func(*args, **kwargs)
except Exception as e:
if self.custom_handlers and e.__class__ in self.custom_handlers:
return self.custom_handlers[e.__class__]()
if e.__class__ not in self.exclude:
raise e
def _sync_exception_handler(self, *args, **kwargs):
try:
return self.func(*args, **kwargs)
except Exception as e:
if self.custom_handlers and e.__class__ in self.custom_handlers:
return self.custom_handlers[e.__class__]()
if e.__class__ not in self.exclude:
raise e
As benchmark I used simple function with try...catch and function with my decorator:
# simple function
def divide(a, b):
try:
return a // b
except ZeroDivisionError:
return 'error'
# function with decorator
#ProcessException({ZeroDivisionError: lambda: 'err'})
def divide2(a, b):
return a // b
Result for 10000 iterations of simple function:
timeit.timeit('divide(1, 0)', number=10000, setup='from __main__ import divide')
0.010692662000110431
And function with decorator:
timeit.timeit('divide2(1, 0)', number=10000, setup='from __main__ import divide2')
0.053688491000002614
Help me please to optimize it and please explain where is bottleneck ?
Well, finally I have optimized my decorator. So, the code (explanation below):
from inspect import iscoroutinefunction
from asyncio import QueueEmpty, QueueFull
from concurrent.futures import TimeoutError
class ProcessException(object):
__slots__ = ('func', 'handlers')
def __init__(self, custom_handlers=None):
self.func = None
if isinstance(custom_handlers, property):
custom_handlers = custom_handlers.__get__(self, self.__class__)
def raise_exception(e: Exception):
raise e
exclude = {
QueueEmpty: lambda e: None,
QueueFull: lambda e: None,
TimeoutError: lambda e: None
}
self.handlers = {
**exclude,
**(custom_handlers or {}),
Exception: raise_exception
}
def __call__(self, func):
self.func = func
if iscoroutinefunction(self.func):
def wrapper(*args, **kwargs):
return self._coroutine_exception_handler(*args, **kwargs)
else:
def wrapper(*args, **kwargs):
return self._sync_exception_handler(*args, **kwargs)
return wrapper
async def _coroutine_exception_handler(self, *args, **kwargs):
try:
return await self.func(*args, **kwargs)
except Exception as e:
return self.handlers.get(e.__class__, Exception)(e)
def _sync_exception_handler(self, *args, **kwargs):
try:
return self.func(*args, **kwargs)
except Exception as e:
return self.handlers.get(e.__class__, Exception)(e)
First, I used cProfile to see what calls was made by decorator. I noticed that asyncio.iscoroutinefunction make extra call. I've removed it. Also I removed all extra if and created common dict for all handlers. My solution still not fast as just try...catch, but now it works more faster.
I am new to learning python, I know this kind questions asked before but i am not able to find any solutions for it. Please check my code and correct me about decorator's functionality, Thank you.
def uppercase(func_one):
func_one = func_one()
return func_one.upper()
def split(func_two):
func_two = func_two()
return func_two.split()
#split
#uppercase
def CallFunction():
return "my string was in lower case"
res = CallFunction()
print(res)
Decorators are confusing and probably should be avoided till you are super experienced with python. That being said, chaining decorators is even more tricky:
from functools import wraps
def split(fn): # fn is the passed in function
#wraps(fn) # This means we can grabs its args and kwargs
def wrapped(*args, **kwargs): # This is the new function declaration
return fn(*args, **kwargs).split()
return wrapped
def uppercase(fn):
#wraps(fn)
def wrapped(*args, **kwargs):
return fn(*args, **kwargs).upper()
return wrapped
# Order matters. You can't call .upper() on a list
#split
#uppercase
def CallFunction():
return "my string was in lower case"
res = CallFunction()
print(res)
Alternatively if you don't want the order of these two decorators to matter than you need to handle the list case:
def uppercase(fn):
#wraps(fn)
def wrapped(*args, **kwargs):
result = fn(*args, **kwargs)
if isinstance(result, list):
return [x.upper() for x in result]
return result.upper()
return wrapped
Reference: How to make a chain of function decorators?
You don't even need functools, you just need to grab the args you are passing.
Here's what you are missing: Add the args being passed inside a wrapper and define that wrapper to accept the args passed. Happy coding!
def uppercase(func_one):
def wrapper(*args):
x = func_one()
return x.upper()
return wrapper
def split(func_two):
def wrapper(*args):
y = func_two()
return y.split()
return wrapper
#split
#uppercase
def CallFunction():
return "my string was in lower case"
res = CallFunction()
print(res)
I implemented a Delegate class in Python 3, which wraps a function object in a object instance. It's possible to register multiple function objects on one delegate (in .NET terminology it's a MultiCastDelegate). Assumed all registered functions accept the same parameters, it's possible to invoke the delegate and call all functions at once.
Delegate implementation:
class Delegate:
def __init__(self, *funcs):
self.__invocationList__ = []
for func in funcs:
self.__invocationList__.append(func)
def __iadd__(self, func):
self.__invocationList__.append(func)
return self
def __isub__(self, func):
self.__invocationList__.remove(func)
return self
def __call__(self, *args, **kwargs):
if (len(self.__invocationList__) == 1):
return self.__invocationList__[0](*args, **kwargs)
else:
res = {}
for func in self.__invocationList__:
res[func] = func(*args, **kwargs)
return res
#property
def isMulticast(self):
return (len(self.__invocationList__) > 1)
Usage examples:
def test1():
return 5
def test2(a, b):
return a + b
def test3(a, b):
return a * b + 15
delegate = Delegate(test1)
result = delegate()
print("test1: {0}".format(result))
delegate = Delegate(test2)
result = delegate(3, 8)
print("test2: {0}".format(result))
delegate += test3
results = delegate(2, 9)
print("test2: {0}".format(results[test2]))
print("test3: {0}".format(results[test3]))
I would like to implement an iterator or generator on this class, so it's possible to use the delegate in for loops.
How could it look like?
# loop over every result from delegate, call with parameters 4 and 18
for result in delegate(4, 18):
print("function={0} result={1}".format(*result))
The iterators __next__() method should return a tuple consisting of the function-object and return value.
What I tried so far:
class Delegate:
# ...
# see code from above
def __iter__(self):
print("Delegate.__iter__():")
class iter:
def __init__(self2, *args, **kwargs):
print(str(args))
self2.__args = args
self2.__kwargs = kwargs
self2.__index = 0
def __iter__(self2):
return self2
def __next__(self2):
if (self2.__index == len(self.__invocationList__)):
raise StopIteration
func = self.__invocationList__[self2.__index]
self2.__index += 1
return func(*self2.__args, **self2.__kwargs)
return iter()
Because the constructor method is already in use by the Delegate creation itself, I implemented the iterator as a nested class. But unfortunately, I can not pass the call parameters *args and **kwargs to the iterator.
So my questions:
Is it possible and wise the implement a iterator / generator pattern for delegates?
What should I change to get it working?
I just tried to implement the iterator pattern. If it works, I would like to upgrade it to a generator - if possible :)
I'm not familiar with this, but I gave it a shot. It is not well tested, but it will help you on the way to solve your task. Here is the code:
class Delegate:
class IterDelegate:
def __init__(this, invocationList, *args, **kwargs):
this.__args = args
this.__kwargs = kwargs
this._invocationList = invocationList
def __iter__(this):
this.__index = 0
return this
def __next__(this):
if this.__index < len(this._invocationList):
func = this._invocationList[this.__index]
this.__index += 1
return (func.__name__, func(*this.__args, **this.__kwargs))
raise StopIteration
def __init__(self, func):
if (type(func) == 'list'):
self._invocationList = func
else:
self._invocationList = [func]
def __call__(self, *args, **kwargs):
return self.IterDelegate(self._invocationList, *args, **kwargs)
def __iadd__(self, func):
self._invocationList.append(func)
return self
def __isub__(self, func):
self._invocationList.remove(func)
return self
def test2(a, b):
return a + b
def test1(*args):
return 6
delegate = Delegate(test2)
delegate += test1
results = delegate(2,3)
for r in results:
print("function={0} result={1}".format(*r))
This will give the results
function=test2 result=5
function=test1 result=6