Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do these two approaches print different results?

def once(fcn):
        func = [fcn]
        def inner(*args):
            return func.pop()(*args) if len(func) else None
        return inner


def add(a,b):
    return a+b


oneAddition = once(add)
print(oneAddition(2,2)) # 4
print(oneAddition(2,2)) # None
print(oneAddition(12,200)) # None

print(once(add)(2,2)) # 4
print(once(add)(2,2)) # Should return None, returns 4
print(once(add)(12,200)) # Should return None, returns 212

So the purpose of this nested function is to keep track of how many times the outer has been called. It returns the result of add only the first time that it is being called. After that, whenever it's called it returns None.

What really piqued my interest is that oneAddition=once(add)->oneAddition(2,2) and once(add)(x,y) behave differently.

In the second method, it seems that the outer function is executed as well. With the first method, the outer function is only executed upon the construction (much like decorators).

Can someone explain to me why that is? Thanks very much.

P.S. I know that using nonlocal vars would be a much more appropriate solution, I just included the function-in-a-list approach because it looks quite cool (found it online).

like image 378
Orestes Papanastassiou Avatar asked Dec 21 '25 00:12

Orestes Papanastassiou


1 Answers

Each once(add) invocation creates a new inner closure, with a new func reference, and returns it. So each of your last three prints operate on totally independent func lists, hence the output.

An easy way to see this is to look at the bytecode:

>>> dis(once)
  2           0 LOAD_FAST                0 (fcn)
              2 BUILD_LIST               1
              4 STORE_DEREF              0 (func)

  3           6 LOAD_CLOSURE             0 (func)
              8 BUILD_TUPLE              1
             10 LOAD_CONST               1 (<code object inner at 0x10c64c300, file "<stdin>", line 3>)
             12 LOAD_CONST               2 ('once.<locals>.inner')
             14 MAKE_FUNCTION            8
             16 STORE_FAST               1 (inner)

  5          18 LOAD_FAST                1 (inner)
             20 RETURN_VALUE

Notice that once builds a new func list on each invocation (BUILD_LIST), which is in turn used by the closure that gets returned (MAKE_FUNCTION).

Another way to see this is to print id(func) inside inner():

>>> def once(fcn):
...     func = [fcn]
...     def inner(*args):
...         print(id(func))  # <-- here
...         return func.pop()(*args) if len(func) else None
...     return inner
...
>>> a1 = once(add)  # assign these so they don't get GC'd
>>> a2 = once(add)
>>> a1(2,2)
4503952392
4
>>> a2(2,2)
4503934024
4

Notice that the IDs are different.

like image 168
arshajii Avatar answered Dec 22 '25 15:12

arshajii