I would recommend to use a class to achieve your goal. 
Here are my approaches:
- 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
- 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.