How does Python handle common functions and lambdas functions internally?

I'm looking at Python's lambdas functions compared to the common functions that def uses to create them.

Here is an example of a common function that converts a number to binary:

def decimalParaBinario(valor, zerosEsquerda=0): 
    return format(valor, 'b').zfill(zerosEsquerda)

Her type:

print(type(decimalParaBinario))

Series:

<class 'function'>

Being that the function decimalParaBinario is an object of Class function. Now notice the same behavior with a lambda expression:

b = lambda x, n=0 : format(x, 'b').zfill(n)

The lambda type would be the same class 'function' for lambda with the name b. And unlike common functions lambda can be anonymous, for example:

print(lambda x=1, n=8 : format(x, 'b').zfill(n))

Which would return:

<function <lambda> at 0x7fae8de04158>

A reference to lambda in memory, in which there is no explicit name for lambda function. Note also that the behavior is different from a common function, when I express a lambda function as a parameter of another function that would not expect a lambda function.

Despite this, I still can't visualize in my mind how Python it treats internally a common function def with respect to lambda expressions.


Question

  • How does Python handle common functions and lambdas functions internally?
Author: gato, 2019-03-05

1 answers

Both function types are identical and treated the same way:

After being created in Python application memory, that is, since the code that creates the function by one with def when one with lambda is executed, there is no difference between a lambda function and a non - lambda function.

Both have the same type, behavior, and can be passed as parameters anywhere that accepts a "callable object" as parameter.

So much so that the "formal and well-educated" way of checking if an object is a function: importing the module types and calling isinstance(meu_objeto, types.FunctionType) uses that FunctionType which is defined in the types.py file of the Python standard library exactly like FunctionType = type(lambda: None) - that is, functions declared with def are, in well-behaved code that can be used in production, etc... compared to function type lambda.

The only difference (after created) really is that when using def, there is an implicit and mandatory name for the function, which is stored in the __name__ attribute of the function. In lambda functions, __name__ always contains the string '<lambda>'. (Note that this attribute can be written at will, however).

Why the difference shown in the question then?

Notice that in your print in the question, you printed two different things: when printing data about the "function with def", you printed only the type of the function. When printing data on the " function lambda", you printed the representation of the function itself (the instance of class 'function').

See in the interactive terminal if we print the same things about each function:

In [30]: def a(): pass                                                                                                         

In [31]: b = lambda: None                                                                                                      

In [32]: print(a, type(a), a.__name__)                                                                                         
<function a at 0x7f5c8ff466a8> <class 'function'> a

In [33]: print(b, type(b), b.__name__)                                                                                         
<function <lambda> at 0x7f5c8ff06f28> <class 'function'> <lambda>

Your big concern that "the lambda function is anonymous" makes no difference to any Python code where you go to make use of a function - and that means you probably didn't understand some other point in the language. Then comes a legal argument. Although most of the texts, and even the official documentation always speaks of "variables", formally in Python we do not have" variables": we have names for objects. An object can have several names, or none - what really counts is how many references we have to an object. The def command creates a" name "for the function that is created with the same, in the same way that a = sign creates a" name " for the object that is the result of the expression to the right of the sign. (See above, both " A "and" b " can be used the same form - each name is created in one way). Other commands that create names for objects are, for example, the import, the for, with and class. Each of these creates one or more names for objects, and during program execution, after the names have been created, there is no difference about how the name was created.

For example, I can create a function " A "with " def", associate it with another name with the sign of =, and delete the original name - it continues working:

In [34]: def a(): 
    ...:     print("função a") 
    ...:                                                                                                                       

In [35]: b = a                                                                                                                 

In [36]: del a                                                                                                                 

In [37]: a()                                                                                                                   
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-37-8d7b4527e81d> in <module>
----> 1 a()

NameError: name 'a' is not defined

In [38]: b()                                                                                                                   
função a

In fact, we can go further - a function does not need to have a name (as well as any Python object) - I can insert the function, like any other object in a list. delete the name b, and it keeps working:

In [39]: c = [b,]                                                                                                              

In [40]: del b                                                                                                                 

In [41]: c                                                                                                                     
Out[41]: [<function __main__.a()>]

In [42]: c[0]()                                                                                                                
função a

(a list or any other data structure - dictionary, attribute in an instance, etc...). The only thing, which turns out to be just a very detail, is that the function is an object that has the attribute __name__, that when it is created with def contains the name given in the command def and when created with lambda always contains ". But once the function is created, it is an almost normal attribute, the only restriction is that it must be set to a string. (That is: if you try to do a.__name__ = None, for example, Python Gives a TypeError)

Differences between functions before are created:

In the syntax of the Python language yes, there is a difference between functions with def and functions lambda: the former can contain a block of arbitrary code, with lines containing commands (such asfor, if, raise), and, to return a value, they must contain the return command. Already functions lambda must necessarily be written in a single expression : that is, the body of a lambda function can call functions, do operations, use the if in ternary mode, contain generator expressions, etc... but it cannot contain any command. The result of this expression is automatically the value of lambda function return. The Python compiler, which can be called manually for any string with the function compile, has the distinction of 3 modes: the mode that executes blocks, the mode that executes expressions and the "command line" mode, made to compile expressions typed in the interactive environment. The first mode is used by exec and the second by eval - which are generally used instead of compile, which is lower level - but you can check in the documentation: https://docs.python.org/3/library/functions.html#compile

Follow advanced information - feel free to skip to the next session if it's too complicated: the interesting thing is that this difference only exists when a file is compiled - that is, the moment Python reads the ".py" file as a text file and processes it, it makes this distinction - then the bytecode file (".pyc", which in the versions Recents is inside the __pycache__ folders.) In these files, the function body is already compiled into an object called code object (types.CodeType), and lambda functions are already indistinguishable from functions with "def", (remembering: except for the attribute __name__). Detail that the functions even exist in the files .pyc-function objects are only created in memory when the program execution arrives in the lines with the Block def or with the keyword lambda, but the sequence of operations in the bytecode to creating them is identical.

You can see this by using the standard library "disassembler" in a function that internally defines a function of each:

In [57]: def exemplo(): 
    ...:     def a(): 
    ...:         pass 
    ...:     b = lambda: None 
    ...:                                                                                                                       

In [58]: import dis                                                                                                            

In [59]: dis.dis(exemplo)                                                                                                      
  2           0 LOAD_CONST               1 (<code object a at 0x7f5c9c738b70, file "<ipython-input-57-4845f3e66234>", line 2>)
              2 LOAD_CONST               2 ('exemplo.<locals>.a')
              4 MAKE_FUNCTION            0
              6 STORE_FAST               0 (a)

  4           8 LOAD_CONST               3 (<code object <lambda> at 0x7f5c8fe44270, file "<ipython-input-57-4845f3e66234>", line 4>)
             10 LOAD_CONST               4 ('exemplo.<locals>.<lambda>')
             12 MAKE_FUNCTION            0
             14 STORE_FAST               1 (b)
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE

Disassembly of <code object a at 0x7f5c9c738b70, file "<ipython-input-57-4845f3e66234>", line 2>:
  3           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE

Disassembly of <code object <lambda> at 0x7f5c8fe44270, file "<ipython-input-57-4845f3e66234>", line 4>:
  4           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE

See that the sequence is: put in the VM stack the "Code object", put in the stack the name - and the lambda name already appears there - and call the opcode MAKE_FUNCTION.

But what then, are there "fundamentally different functions"?

The answer is yes . A few days ago you yourself posted a question about how yield modifies a function, and the answer I elaborated to that question demonstrates these differences. The keyword yield, and it, as well as the async def, changes one thing an information in the function object when it is created that makes these functions completely different from normal functions when called. Unlike functions defined with def and with lambda, which are identical in operation. It is worth reading the answer there:

Like Python do you treat the "yield" command internally?

It is interesting to note that the markup that the language does to distinguish these functions is in the attribute .__code__.co_flags of a function - and, again, for functions def and lambda the value of these flags is the same, reflecting once again that they are the same thing:

In [60]: def a(): pass                                                                                                         

In [61]: b = lambda : None                                                                                                     

In [62]: a.__code__.co_flags                                                                                                   
Out[62]: 67

In [63]: b.__code__.co_flags                                                                                                   
Out[63]: 67

In [64]: def c(): yield                                                                                                        

In [65]: c.__code__.co_flags                                                                                                   
Out[65]: 99
 5
Author: jsbueno, 2019-03-07 04:11:25