Предотвращение вызова функции дважды подряд - PullRequest
1 голос
/ 19 января 2020

Я изучаю декораторы, и у меня есть задача попросить меня создать декоратор, чтобы функция не вызывалась дважды подряд. Если та же функция вызывается снова, она должна вернуть None. Кажется, я не могу понять, как она на самом деле работает, пока я достиг этого:

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

(я знаю, что это действительно неудачная попытка , но я просто не знаю, как отследить функцию, вызываемую с заданными аргументами c, чтобы проверить, было ли это уже раньше), результат должен выглядеть примерно так:

@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

Ответы [ 2 ]

2 голосов
/ 19 января 2020

Кажется, это может вам помочь:

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

Попробуйте:

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.
1 голос
/ 19 января 2020

Вот решение, которое очень похоже на решение Андрея Беренды, но работает, назначая атрибут функциональному объекту, а не используя нелокальную переменную. Практическое отличие состоит в том, что предыдущие аргументы функции становятся доступными извне, что может помочь в целях отладки.

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

Пример:

>>> @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), {})

Обратите внимание, что оба решения имеют небольшой недостаток: вы можно вызывать с теми же значениями аргументов, если вы предоставляете их как позиционные аргументы, а затем ключевые аргументы (или наоборот):

>>> f(5, 6)
11
>>> f(x=5, y=6)
11

В качестве обходного пути вы можете объявить упакованную функцию только с позиционным (или ключевым словом) -only) аргументы:

# 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

Обратите также внимание, что если предыдущие аргументы являются изменяемыми, то могут происходить странные вещи:

>>> 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

Второй вызов функции здесь возвращает None несмотря на новые аргументы, не совпадающие с исходными аргументами ни по значению, ни по идентичности; они равны текущим значениям исходных аргументов, которые были изменены после их использования в качестве аргументов. Это может привести к довольно тонким ошибкам, но, к сожалению, нет простого способа исправить это.

...