问题
I'm learning decorators and I have a task asking me to create a decorator to prevent a function from being called twice in a row. If the same function is called again, it should return None I can't seem to understand how it really works, so far I've achieved this:
def dont_run_twice(f):
global counter
def wrapper(*args, **kwargs):
counter += 1
if counter == 2:
return None
else:
result = f(*args, **kwargs)
return result
counter = 0
(I know that it's a really bad attempt, but I just don't know a way to keep track of a function called with specific arguments in order to check if it was already before) the output should be something like:
@dont_run_twice
def myPrint(*args):
print(*args)
myPrint("Hello")
myPrint("Hello") #won't do anything (only return None)
myPrint("Hello") #still does nothing.
myPrint("Goodbye") #will work
myPrint("Hello") #will work
回答1:
Seems this can help you:
import functools
def do_not_run_twice(func):
prev_call = None
@functools.wraps(func) # It is good practice to use this decorator for decorators
def wrapper(*args, **kwargs):
nonlocal prev_call
if (args, kwargs) == prev_call:
return None
prev_call = args, kwargs
return func(*args, **kwargs)
return wrapper
Try this:
my_print("Hello")
my_print("Hello") # won't do anything (only return None)
my_print("Hello") # still does nothing.
my_print("Goodbye") # will work
my_print("Hello") # will work.
回答2:
Here's a solution which is very similar to Andrey Berenda's solution, but which works by assigning an attribute to the function object, rather than using a non-local variable. The practical difference is that the function's previous arguments become available externally, which might help for debugging purposes.
from functools import wraps
def dont_run_twice(func):
@wraps(func)
def wrapper(*args, **kwargs):
if (args, kwargs) == wrapper._prev_args:
return None
wrapper._prev_args = args, kwargs
return func(*args, **kwargs)
wrapper._prev_args = None
return wrapper
Example:
>>> @dont_run_twice
... def f(x, y):
... return x + y
...
>>> f(1, 2)
3
>>> f(3, 4)
7
>>> f(3, 4) # returns None
>>> f(1, 2)
3
>>> f._prev_args
((1, 2), {})
Note that both solutions have a slight flaw: you can call with the same argument values if you provide them as positional arguments then keyword arguments (or vice-versa):
>>> f(5, 6)
11
>>> f(x=5, y=6)
11
As a workaround, you can declare the wrapped function with positional-only (or keyword-only) arguments:
# positional-only, requires Python 3.8+
@dont_run_twice
def f(x, y, /):
return x + y
# keyword-only
@dont_run_twice
def g(*, x, y):
return x + y
Note also that if the previous args are mutable, then strange things can happen:
>>> a = [1, 2]
>>> b = [3, 4]
>>> f(a, b)
[1, 2, 3, 4]
>>> a[:] = [5, 6]
>>> b[:] = [7, 8]
>>> f([5, 6], [7, 8]) # returns None
The second function call here returns None
despite the new arguments not being equal to the original arguments by either value or identity; they are equal to the original arguments' current values which were changed after they were used as arguments. This could lead to rather subtle bugs, but unfortunately there's no easy way to fix it.
来源:https://stackoverflow.com/questions/59811637/prevent-a-function-from-being-called-twice-in-a-row