Skip to main content

Python __code__ variable

Python __code__ variable

The __code__ variable in Python is a special attribute of function objects that grants access to their underlying code object—a compiled representation of the function’s source code. This powerful feature enables deep introspection into a function’s structure, including its bytecode, variable names, and constants, offering insights for debugging, analysis, and even dynamic modification. This article explores its mechanics, properties, and practical applications in detail.


1. What is the __code__ Variable?

The __code__ variable is an attribute of Python function objects that references a code object, encapsulating the compiled form of the function’s code. This object is created by the Python interpreter when a function is defined and contains metadata about its execution context.

  • Purpose: Facilitates introspection into function internals.
  • Contents: Includes bytecode, argument details, and variable names.
  • Type: An instance of the types.CodeType class.

Technical Note: The code object is immutable and distinct from the function’s runtime state (e.g., closures or defaults), which are stored separately in attributes like __closure__ or __defaults__.


2. How __code__ Works: A Basic Example

Every Python function has a __code__ attribute that can be accessed directly.

Script:

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

print(add.__code__)

Output:

<code object add at 0x..., file "...", line ...>

Explanation: The output shows a code object with a memory address, filename, and line number. This object is the compiled blueprint of add, ready for execution by the Python VM.


3. The __code__ Object’s Properties

The code object exposes numerous attributes for inspecting a function’s structure.

Example:

def multiply(x, y):
    z = x * y
    return z

code = multiply.__code__
print("Positional args count:", code.co_argcount)
print("Local variables:", code.co_varnames)
print("Constants:", code.co_consts)
print("Global names:", code.co_names)
print("Bytecode:", code.co_code.hex())

Output (example):

Positional args count: 2
Local variables: ('x', 'y', 'z')
Constants: (None,)
Global names: ()
Bytecode: 7c007c016a00000000007d027c025300

Key Properties:

  • co_argcount: Number of positional arguments.
  • co_varnames: Tuple of local variable names (args first, then locals).
  • co_consts: Constants used in the function (e.g., None for return).
  • co_code: Raw bytecode as bytes.

Note: Use the dis module to disassemble co_code into readable instructions.


4. Why Use __code__?

This attribute serves several critical purposes:

Benefit Description
Introspection Reveals function structure for analysis.
Debugging Helps trace execution via bytecode or variables.
Metaprogramming Allows dynamic function modification.
Tooling Supports testing frameworks and profilers.

Analogy: Think of __code__ as a function’s DNA—it holds the blueprint of its behavior.


5. Practical Applications

A. Analyzing Function Structure

Use __code__ to inspect function details programmatically.

def analyze(x, y=10):
    result = x + y
    print(result)
    return result

code = analyze.__code__
print(f"Args: {code.co_argcount}, Locals: {code.co_varnames}, Constants: {code.co_consts}")

Output:

Args: 2, Locals: ('x', 'y', 'result'), Constants: (None, 10, 'result')

Use Case: Building code analysis tools.

B. Disassembling Bytecode

Pair __code__ with the dis module to decode bytecode.

import dis

def greet(name):
    return f"Hello, {name}!"

dis.dis(greet.__code__)

Output:

  3           0 RESUME                   0

  4           2 LOAD_CONST               1 ('Hello, ')
              4 LOAD_FAST                0 (name)     
              6 FORMAT_VALUE             0
              8 LOAD_CONST               2 ('!')      
             10 BUILD_STRING             3
             12 RETURN_VALUE

Benefit: Debugging low-level execution.

C. Dynamic Modification

Swap code objects to alter function behavior (use with caution).

def old_func():
    return "Old"

def new_func():
    return "New"

old_func.__code__ = new_func.__code__
print(old_func())

Output:

New

Use Case: Metaprogramming experiments.


6. Advanced Insights

Aspect Behavior Notes
Immutability Code object is read-only Create new CodeType instances for changes.
Closures Separate from __code__ co_freevars lists closure variables.
Compatibility Version-specific Bytecode varies across Python versions.

Example (Closures):

def outer(x):
    def inner():
        return x
    return inner

print(outer(5).__code__.co_freevars)

Output:

('x',)

Tip: Use types.CodeType to construct custom code objects.


7. Golden Rules for Using __code__

  • Inspect Safely: Use for analysis, not casual tweaks.
  • Pair with dis: Decode bytecode for clarity.
  • Document Changes: Note any modifications for maintenance.
  • Avoid Overuse: Modifying code objects is fragile.
  • Don’t Assume Portability: Bytecode isn’t cross-version.

8. Conclusion

The __code__ variable offers a window into Python’s inner workings, exposing a function’s bytecode and structure for introspection and manipulation. While invaluable for debugging, profiling, and metaprogramming, it demands careful use due to its complexity and risks. Mastering __code__ empowers developers to wield Python’s low-level machinery effectively.

Final Tip: "See __code__ as your function’s blueprint—study it to understand, tweak it only if you dare."

Comments