简体   繁体   中英

How does nested function work for decorator?

I do not really understand the behaviour of the following code. The goal of this code was to allow only one processing of the function decorated by the once decorator. Since I return onceResult in the try block I do not get why on the second call I don't get a second "Hello william". Therefor i tried to print onceResult and get None as output. I expected a string containing "Hello william".

Could someone explain me this behaviour?

Here is the output:

Before first call
Exception
Hello William !
Before second call
None
Finished

Here is the code:

def once(func):
    """once is a decorator which allows only one execution of the function"""
    def nested(*args,**kargs):
        try:
            print(nested.onceResult)
            return nested.onceResult
        except AttributeError:
            print("### EXCEPTION###")
            nested.onceResult = func(*args,**kargs)
            return nested.onceResult
    return nested

@once
def hello(name):
    print(f"Hello {str(name)} !")

print("Before first call")
hello("William")
print("Before second call")
hello("Roger")
print("Finished")

I would recommend to use a class to achieve your goal.

Here are my approaches:

  1. If you want to execute the function only once no matter waht arguments you pass:
class ExecuteOnceClass:
    called = []

    @staticmethod
    def execute(function):
        def wrapper(*args, **kwargs):
            if function.__name__ in ExecuteOnceClass.called:
                print("Function already called once")
            else:
                function(*args, **kwargs)
                ExecuteOnceClass.called.append(function.__name__)
                print("called function")
        return wrapper

@ExecuteOnceClass.execute
def hello(name):
    print(f"Hello {name}.")

hello("Neuneu")
hello("Sofian")

Expected output:

Hello Neuneu.
called function
Function already called once
  1. If you want the function to get called once with a view on the arguments:
class ExecuteOnceClass:
    called = {}

    @staticmethod
    def execute(function):
        def wrapper(*args, **kwargs):
            if function.__name__ in ExecuteOnceClass.called and {"args": args, "kwargs": kwargs} in ExecuteOnceClass.called[function.__name__]:
                print("Function already called once with the same arguments")
            else:
                function(*args, **kwargs)
                if ExecuteOnceClass.called.get(function.__name__, None):
                    ExecuteOnceClass.called[function.__name__].append({"args": args, "kwargs": kwargs})
                else:
                    ExecuteOnceClass.called[function.__name__] = [{"args": args, "kwargs": kwargs}]
                print("called function")
        return wrapper

@ExecuteOnceClass.execute
def hello(name):
    print(f"Hello {name}.")

hello("Neuneu")
hello("Sofien")
hello("Neuneu")
hello("Sofien")

Expected output:

Hello Neuneu.
called function
Hello Sofien.
called function
Function already called once with the same arguments
Function already called once with the same arguments

EDIT: if your function returns something you would need to edit the code a bit:

class ExecuteOnceClass:
    called = {}

    @staticmethod
    def execute(function):
        def wrapper(*args, **kwargs):
            if function.__name__ in ExecuteOnceClass.called and {"args": args, "kwargs": kwargs} in ExecuteOnceClass.called[function.__name__]:
                print("Function already called once with the same arguments")
            else:
                functionResult = function(*args, **kwargs)
                if ExecuteOnceClass.called.get(function.__name__, None):
                    ExecuteOnceClass.called[function.__name__].append({"args": args, "kwargs": kwargs})
                else:
                    ExecuteOnceClass.called[function.__name__] = [{"args": args, "kwargs": kwargs}]
                print("called function")
            return functionResult
        return wrapper

@ExecuteOnceClass.execute
def hello(name):
    return f"Hello {name}."

print(hello("Neuneu"))

Expected Output:

Hello Neuneu.

good luck!

SECOND EDIT: for a better understanding of python at all (and of course decorators too) I would highly recommend you this video:
What Does It Take To Be An Expert At Python?
I have learned a lot watching this video.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM