Search code examples
pythonlogicpython-decorators

Function returned by decorator isn't being executed when calling the decorated function. Am I missing something obvious?


I had a go at writing a little function for making it easier to create decorators with arguments:

def decoratorAddSupportForOptionalArguments(addToDict: typing.Callable):
    """Takes a function with arguments, where the first argument is callable and returns a modified version that enables decorator like syntax.
    """
    def addToDictDecorator(*argumentsPassedTo_addToDict, **kargumentsPassedTo_addToDict):
        kargumentsPassedTo_addToDict_keys = kargumentsPassedTo_addToDict.keys()
        if len(argumentsPassedTo_addToDict) == 1 and callable(argumentsPassedTo_addToDict[0]) and 0==len(kargumentsPassedTo_addToDict_keys):
            returnVal= addToDict(argumentsPassedTo_addToDict[0])
            return returnVal if returnVal!=None else addToDict
        # addToDict is being called as a function
        def decFinal(func):
            returnVal = addToDict(func,*argumentsPassedTo_addToDict, **kargumentsPassedTo_addToDict)
            print(f"decFinal {func.__name__} {returnVal}")
            return returnVal if returnVal!=None else addToDict
        return decFinal
    return addToDictDecorator

Which is supposed to be used as following:


myDict=dict()
import random
@decoratorAddSupportForOptionalArguments
def addToDict(func, key=None, log=None):
    print("addToDict")
    myDict[key if key else str(random.random())]=func
    def wrapper(*args, **kwargs): # @note This is supposed to be called every time the decorated function gets called.
        print("foo")              # It never gets called however - and I cant figure out whats wrong
        if log:
            print(log)
        func(*args,**kwargs)
    return wrapper

@addToDict
def isValid(value):
    return value != None
@addToDict("myFancyKey")
def isPrivate(value):
    return value.__name__.startswith("_")
@addToDict()
def printRandom(value):
    print(random.random())
@addToDict("notepad",log="Starting notepad")
def startNotepad():
    import os
    print("notepad")
    os.system("notepad.exe")
print(myDict)
myDict["notepad"]()
myDict["notepad"]()
myDict["notepad"]()

Every time I attempt executing any of the functions prefixed with addToDict, "decFinal" gets executed like intended, but its return value (which in my example is wrapper inside of addToDict never gets called. To my understanding, the @ decorator syntax should execute any function returned by what is returned by the decorator. What am I missing here? Every time startNotepad gets executed, I expect to see "foo" from wrapper get printed.

Interestingly, "foo" does get printed when I try invoking "isValid", which was declared with @addToDict without the parenthesis (unlike startNotepad ). It's just that, the logic does basically the same thing in both cases, except startNotepad first needs to call decFinal before foo finally gets called.


Solution

  • Your problem is that you add func to the dictionary, instead of the wrapped version. So only startNotepad() actually prints foo - because it's decorated version, not myDict['notepad']() - because it's original function

    So instead of this:

    def addToDict(func, key=None, log=None):
        print("addToDict")
        myDict[key if key else str(random.random())]=func # here, func is original undecorated version! 
        def wrapper(*args, **kwargs):
            print("foo")              
            if log:
                print(log)
            func(*args,**kwargs)
        return wrapper
    

    You gotta add wrapper. So that line had to be below the local function:

    def addToDict(func, key=None, log=None):
        def wrapper(*args, **kwargs):
            print("foo")              
            if log:
                print(log)
            func(*args,**kwargs)
        print("addToDict")
        myDict[key if key else str(random.random())]=wrapper # fixed!  
        return wrapper