Prevent a function from being called twice in a row

邮差的信 提交于 2020-04-16 05:54:12

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!