python decorator to check for already called func with unique arguments
Question:
I m writing python decorator to check if func was previously called with same arguments.
Below is proof of concept code
storage = list()
def f(*args, **kwargs):
s = ''
for i in range(len(args)):
s += str(args[i])
kv = ''
for k, v in kwargs.items():
kv += str(k) + str(v)
main = s+kv
if main not in storage:
storage.append(main)
else:
print('this was called!')
print('printing storage')
print(storage)
if __name__ == '__main__':
f(1, 2, 3, 4, 5, 6, 7, 8, x=10)
f(1, 2, 3, 4, 5, 6, 7, 8, x=10, z=10)
f(1, 2, 3, 4, 5, 6, 7, 8, x=10, z=10) #this combination of args, kwargs should be skipped by the function f
My actual decorator fails with this error msg:
"TypeError(‘can only concatenate tuple (not "dict") to tuple’)"
challenge website link
here i simply turn the list of args into str and **kwargs dict into string and concatenate them to create unique combination of args/kwargs called and store it in storage list
e.g. func(1,2,3,x=3,b=4) => 123x3b4
class Answer:
def RepeatDecorator(self, func):
self.storage = list()
def wrapper(*args, **kwargs):
s = ''
for i in range(len(args)):
s += str(args[i])
kv = ''
for k, v in kwargs.items():
kv += str(k) + str(v)
main = s+kv
if main not in self.storage:
self.storage.append(main)
func(args, kwargs)
else:
print("func with this args was already called, do nothing")
return wrapper
e.g.
func1(1,x=2)
func1(1,x=3,b=4)
func2(1,x=2)
func2(1,x=3,b=4
all 4 should work and be stored, since func1 and func2 are different functions
Answers:
Here are some issues I found with your code:
- Different functions given to
Answer.RepeatDecorator
will share the same storage
list, if they’re using the same Answer
instance.
- Pasting strings together is ambiguous. This will treat
func(12, 3)
the same as func(1, 23)
and func(x=4)
the same as func('x4')
.
- Looking up strings in a list is fairly slow. In this case it’s unlikely to be a problem, but you’ll probably want to use a
set
if this kinda thing is needed in the future (as long as the arguments are hashable).
- You’re calling
func(args, kwargs)
when you should call func(*args, **kwargs)
. It’s something I often overlook when writing decorators.
class Answer:
def RepeatDecorator(self, func):
# Use a local variable rather than an attribute on self
storage = []
def wrapper(*args, **kwargs):
key = (args, kwargs)
if key not in storage:
storage.append(key)
# It doesn't say you need to return the value func returns, but it can't hurt, right?
return func(*args, *kwargs)
print("func with this args was already called, do nothing")
return wrapper
For completeness sake, here’s how I would do it using a set
:
class Answer:
def RepeatDecorator(self, func):
storage = set()
def wrapper(*args, **kwargs):
key = (args, tuple(kwargs.items()))
if key not in storage:
storage.add(key)
return func(*args, *kwargs)
print("func with this args was already called, do nothing")
return wrapper
I m writing python decorator to check if func was previously called with same arguments.
Below is proof of concept code
storage = list()
def f(*args, **kwargs):
s = ''
for i in range(len(args)):
s += str(args[i])
kv = ''
for k, v in kwargs.items():
kv += str(k) + str(v)
main = s+kv
if main not in storage:
storage.append(main)
else:
print('this was called!')
print('printing storage')
print(storage)
if __name__ == '__main__':
f(1, 2, 3, 4, 5, 6, 7, 8, x=10)
f(1, 2, 3, 4, 5, 6, 7, 8, x=10, z=10)
f(1, 2, 3, 4, 5, 6, 7, 8, x=10, z=10) #this combination of args, kwargs should be skipped by the function f
My actual decorator fails with this error msg:
"TypeError(‘can only concatenate tuple (not "dict") to tuple’)"
challenge website link
here i simply turn the list of args into str and **kwargs dict into string and concatenate them to create unique combination of args/kwargs called and store it in storage list
e.g. func(1,2,3,x=3,b=4) => 123x3b4
class Answer:
def RepeatDecorator(self, func):
self.storage = list()
def wrapper(*args, **kwargs):
s = ''
for i in range(len(args)):
s += str(args[i])
kv = ''
for k, v in kwargs.items():
kv += str(k) + str(v)
main = s+kv
if main not in self.storage:
self.storage.append(main)
func(args, kwargs)
else:
print("func with this args was already called, do nothing")
return wrapper
e.g.
func1(1,x=2)
func1(1,x=3,b=4)
func2(1,x=2)
func2(1,x=3,b=4
all 4 should work and be stored, since func1 and func2 are different functions
Here are some issues I found with your code:
- Different functions given to
Answer.RepeatDecorator
will share the samestorage
list, if they’re using the sameAnswer
instance. - Pasting strings together is ambiguous. This will treat
func(12, 3)
the same asfunc(1, 23)
andfunc(x=4)
the same asfunc('x4')
. - Looking up strings in a list is fairly slow. In this case it’s unlikely to be a problem, but you’ll probably want to use a
set
if this kinda thing is needed in the future (as long as the arguments are hashable). - You’re calling
func(args, kwargs)
when you should callfunc(*args, **kwargs)
. It’s something I often overlook when writing decorators.
class Answer:
def RepeatDecorator(self, func):
# Use a local variable rather than an attribute on self
storage = []
def wrapper(*args, **kwargs):
key = (args, kwargs)
if key not in storage:
storage.append(key)
# It doesn't say you need to return the value func returns, but it can't hurt, right?
return func(*args, *kwargs)
print("func with this args was already called, do nothing")
return wrapper
For completeness sake, here’s how I would do it using a set
:
class Answer:
def RepeatDecorator(self, func):
storage = set()
def wrapper(*args, **kwargs):
key = (args, tuple(kwargs.items()))
if key not in storage:
storage.add(key)
return func(*args, *kwargs)
print("func with this args was already called, do nothing")
return wrapper