Fundamentals 22 min read

How Python Functions Are Created: From def to PyFunctionObject

The article explains step‑by‑step how a Python function is turned into a PyFunctionObject at runtime, covering the role of PyCodeObject, the MAKE_FUNCTION bytecode instruction, attribute handling such as defaults and annotations, name resolution, variable binding, and techniques for inspecting and copying functions.

Satori Komeiji's Programming Classroom
Satori Komeiji's Programming Classroom
Satori Komeiji's Programming Classroom
How Python Functions Are Created: From def to PyFunctionObject

When the interpreter encounters a def statement it first creates a PyCodeObject that statically stores the function’s bytecode, constants, and symbol table. Each assignment in the function body (e.g., a = 1) becomes entries in co_consts and co_varnames, and the corresponding bytecode instructions ( LOAD_CONST, STORE_FAST) are emitted.

During execution of the def statement the virtual machine loads the just‑created PyCodeObject onto the stack and executes the MAKE_FUNCTION opcode. This opcode extracts optional attributes (closure, annotations, keyword defaults, positional defaults) from the stack based on the oparg bitmask, then calls PyFunction_New (or PyFunction_NewWithQualName) to wrap the code object together with the current global namespace.

PyFunctionObject *func_obj = (PyFunctionObject *)
    PyFunction_New(codeobj, GLOBALS());
// set closure, annotations, defaults according to oparg bits

The newly created PyFunctionObject receives references to its globals, builtins, name, qualified name, code object, and a freshly allocated attribute dictionary. After initialization the function object is pushed back onto the stack, and the subsequent STORE_NAME binds it to the identifier that appeared after def (e.g., foo) in the local namespace.

When the function is later called, the interpreter retrieves the PyFunctionObject from the local dictionary, creates a stack frame, and executes the stored bytecode. The article demonstrates this flow with a concrete example that prints the locals after defining foo and shows the resulting bytecode disassembly.

Adding type annotations or default values changes the bytecode: default values are loaded as a tuple and pushed before the MAKE_FUNCTION opcode, while annotations are collected into a separate tuple. The resulting function object stores defaults in func_defaults and annotations in func_annotations, which can be inspected via __defaults__ and __annotations__.

The article also clarifies the distinction between a variable name and the function’s intrinsic name ( __name__). The identifier used in the source code is merely a key in the module’s dictionary; the actual name lives inside the function object and can be changed independently of the variable that references it.

To discover a function’s parameters the article inspects the code object’s symbol table ( co_varnames) together with the counts co_posonlyargcount, co_argcount, co_kwonlyargcount, and the flag bits CO_VARARGS (0x04) and CO_VARKEYWORDS (0x08). It shows how to separate positional‑only, positional‑or‑keyword, keyword‑only, *args, and **kwargs parameters programmatically.

Finally, the article discusses why the standard copy module cannot deep‑copy functions and presents a manual approach using types.FunctionType to recreate a function with the same code object, globals, name, defaults, and closure, optionally copying the attribute dictionary.

Overall, the piece provides a thorough, low‑level walkthrough of Python function creation, the bytecode that drives it, and practical tricks for introspection and duplication.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Pythonbytecodefunctioninspectdefaultspyfunctionobjectmake_functionpycodeobject
Satori Komeiji's Programming Classroom
Written by

Satori Komeiji's Programming Classroom

Python and Rust developer; I write about any topics you're interested in. Follow me! (#^.^#)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.