Search code examples
pythonfunctionclosures

Is there a difference in returning a function or a lambda function?


Is there a difference (even subtle) between those two following codes? (is there any special use case of second code?)

  • code 1
def f(n):
    def g(x):
        return x * n
    return g
  • code 2
def f(n):
    def g(x):
        return x * n
    return lambda y: g(y)

Solution

  • If we use dis, Python's builtin CodeObject disassembler module, many differences are apparent in the functions. This will be different in 3.11, but I have not updated from 3.10.

    For the first function

      2           0 LOAD_CLOSURE             0 (n)
                  2 BUILD_TUPLE              1
                  4 LOAD_CONST               1 (<code object g at 0x000001F59E179000, file "<pyshell#2>", line 2>)
                  6 LOAD_CONST               2 ('f.<locals>.g')
                  8 MAKE_FUNCTION            8 (closure)
                 10 STORE_FAST               1 (g)
    
      4          12 LOAD_FAST                1 (g)
                 14 RETURN_VALUE
    
    Disassembly of <code object g at 0x000001F59E179000, file "<pyshell#2>", line 2>:
      3           0 LOAD_FAST                0 (x)
                  2 LOAD_DEREF               0 (n)
                  4 BINARY_MULTIPLY
                  6 RETURN_VALUE
    

    And for the second:

      2           0 LOAD_CLOSURE             1 (n)
                  2 BUILD_TUPLE              1
                  4 LOAD_CONST               1 (<code object g at 0x000001F59E1A52C0, file "<pyshell#5>", line 2>)
                  6 LOAD_CONST               2 ('f.<locals>.g')
                  8 MAKE_FUNCTION            8 (closure)
                 10 STORE_DEREF              0 (g)
    
      4          12 LOAD_CLOSURE             0 (g)
                 14 BUILD_TUPLE              1
                 16 LOAD_CONST               3 (<code object <lambda> at 0x000001F59E1A5420, file "<pyshell#5>", line 4>)
                 18 LOAD_CONST               4 ('f.<locals>.<lambda>')
                 20 MAKE_FUNCTION            8 (closure)
                 22 RETURN_VALUE
    
    Disassembly of <code object g at 0x000001F59E1A52C0, file "<pyshell#5>", line 2>:
      3           0 LOAD_FAST                0 (x)
                  2 LOAD_DEREF               0 (n)
                  4 BINARY_MULTIPLY
                  6 RETURN_VALUE
    
    Disassembly of <code object <lambda> at 0x000001F59E1A5420, file "<pyshell#5>", line 4>:
      4           0 LOAD_DEREF               0 (g)
                  2 LOAD_FAST                0 (y)
                  4 CALL_FUNCTION            1
                  6 RETURN_VALUE
    

    The attributes of the __code__ attribute on each function that are different are

    • co_cellvars - 'n' is also present in the second function
    • co_code - the second function builds a lambda before returning; the first function returns g directly
    • co_consts - the second function contains two CodeObjects (and two corresponding names), whereas the first only contains one CodeObject with its name
    • co_lines - The co_lines method will return different values when iterated over as a direct consequence of co_code being different
    • co_lnotab - The (deprecated in 3.11) co_lnotab attribute is a more space-efficient version of the co_lines method
    • co_nlocals - I'm not sure why this differs
    • co_varnames - g is present in the first function, because it's directly referenced in the first function. However in the second function it's only referenced within the lambda scope, and so occurs in f.__code__.co_consts[3].co_freevars, the free variables of the lambda instead.

    Finally, as Michael notes in the comments, the __name__ of all lambda functions, such as those returned here, is '<lambda>'.