0% found this document useful (0 votes)
2 views4 pages

Python Visual Flow

The document provides an overview of Python execution flow, focusing on loop iteration, memory management, and function call behavior. It compares the efficiency of list mutation using append() versus concatenation, highlights the dangers of mutable default arguments, and explains nested loop execution with examples. Key takeaways include performance implications and best practices for using loops and function arguments in Python.

Uploaded by

ksanket153
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views4 pages

Python Visual Flow

The document provides an overview of Python execution flow, focusing on loop iteration, memory management, and function call behavior. It compares the efficiency of list mutation using append() versus concatenation, highlights the dangers of mutable default arguments, and explains nested loop execution with examples. Key takeaways include performance implications and best practices for using loops and function arguments in Python.

Uploaded by

ksanket153
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Python Execution Flow PAGE 1 — Loop Iteration Trace

Visual diagrams for loops, lists, mutation & memory s picks 'error', then 'warning'

Source Code Loop Unroll


logs = []
words = ['error', 'warning'] Iteration 1
s = 'error'
logs = [] + ['error'] => ['error']
for s in words:
logs = logs + [s]
Iteration 2
# Result: logs = ['error', 'warning'] s = 'warning'
logs = ['error'] + ['warning'] => ['error','warning']

State Trace (what lives in memory each step)


Step s logs (before) logs (after) New list?

start — — [] no

iter 1 'error' [] ['error'] YES

iter 2 'warning' ['error'] ['error','warning'] YES

done — ['error','warning'] same —

Memory Diagram — why logs = logs + [s] creates a NEW list every time

BEFORE iter 1 AFTER iter 1 OLD list (abandoned)


logs [] logs ['error'] []
logs + ['error']
creates NEW object
id: 0x001 object @ 0x001 id: 0x002 NEW object @ 0x002 id: 0x001
empty list logs reassigned no variable points here
=> garbage collected

Python Execution Flow | Page 1 of 4


append() vs logs = logs + [s] PAGE 2 — In-place vs Concatenation
Side-by-side execution and memory comparison

Method A — .append() Method B — logs = logs + [s]


MUTATES the existing list in-place Creates a BRAND NEW list every iteration

append() code concat code


logs = [] logs = []
for s in words: for s in words:
[Link](s) logs = logs + [s]

Memory — only ONE list object exists throughout: Memory — a NEW list object created each time:
start start
logs -> [ ] id: 0xAAA (unchanged!) logs -> [ ] id: 0xAAA (different!)

iter 1 iter 1
logs -> ['error'] id: 0xAAA (unchanged!) logs -> ['error'] NEW id: 0xBBB (different!)

iter 2 iter 2
logs -> ['error','warn'] id: 0xAAA (unchanged!) logs -> ['error','warn'] NEW id: 0xCCC (different!)

SAME object throughout DIFFERENT object each time


No copies made. Efficient! Old lists orphaned, then GC'd

Performance & Behavior Comparison


Property append() logs + [s] Winner

Creates new list? NO YES, every loop append

Memory usage O(1) amortized O(n) per step append

Original list changed YES (mutates) NO (new binding) depends

Result identical? YES YES tie

Use when... building list immutability needed both valid

Python Execution Flow | Page 2 of 4


Default Argument Mutation Trap PAGE 3 — Function Call Memory
Why mutable defaults are dangerous — and how Python really handles them

The Bug (mutable default) The Fix (None sentinel)


def add_item(item, bag=[]): def add_item(item, bag=None):
[Link](item) if bag is None:
return bag bag = []
[Link](item)
r1 = add_item('apple') return bag
r2 = add_item('banana')
r3 = add_item('cherry') r1 = add_item('apple')
r2 = add_item('banana')
# SURPRISE!
# r1 = ['apple','banana','cherry'] # CORRECT
# r2 = ['apple','banana','cherry'] # r1 = ['apple']
# r3 = ['apple','banana','cherry'] # r2 = ['banana']

Why it happens — the default list lives inside the FUNCTION OBJECT
Function Object: add_item
Created ONCE at def time

__name__: 'add_item'

__code__: <compiled bytecode>

__defaults__: ( [ ] ) <-- lives HERE


same list object, never reset

Call trace (all calls share the same default list object):
call 1: add_item('apple')
bag = <default> -> ['apple']

call 2: add_item('banana')
bag = SAME default -> ['apple','banana']

call 3: add_item('cherry')
bag = SAME default -> ['apple','banana','cherry']

The None fix — each call gets its OWN fresh list:
call 1: add_item('apple')
bag=None -> bag=[] -> ['apple'] new id: 0x111

call 2: add_item('banana')
bag=None -> bag=[] -> ['banana'] new id: 0x222
Golden Rule
NEVER use mutable objects (list, dict, set) as default arguments.
They are evaluated ONCE when the function is defined, not each time it is called.
Always use None, then assign inside the function body.

Python Execution Flow | Page 3 of 4


Nested Loop Execution Map PAGE 4 — Execution Order Grid
Exactly which (i,j) pairs execute and in what order

Nested Loop Execution grid — each cell shows (i, j) and call order
for i in range(3):
for j in range(4):
print(i, j)

j=0 j=1 j=2 j=3

i=0
(0,0) (0,1) (0,2) (0,3)
call #1 call #2 call #3 call #4

i=1
(1,0) (1,1) (1,2) (1,3)
call #5 call #6 call #7 call #8

i=2
(2,0) (2,1) (2,2) (2,3)
call #9 call #10 call #11 call #12

Execution order (reading left to right, top to bottom):

1 2 3 4 5 6 7 8 9 10 11 12
(0,0) (0,1) (0,2) (0,3) (1,0) (1,1) (1,2) (1,3) (2,0) (2,1) (2,2) (2,3)

How break and continue change execution:


Pairs printed with break at j==2:
break example
(0,0) (0,1) j==2 (0,3)
for i in range(3): i=0 printed printed BREAK skipped
for j in range(4):
if j == 2: (1,0) (1,1) j==2 (1,3)
break # stops INNER loop i=1 printed printed BREAK skipped
print(i, j)

(2,0) (2,1) j==2 (2,3)


i=2 printed printed BREAK skipped

break only exits INNER loop. Outer loop still runs for all i.

Key Takeaways
Outer loop runs n times, inner loop runs m times PER outer iteration => n x m total calls
break stops only the loop it lives in — not any outer loops
continue skips the rest of that iteration and moves to the next one
Use enumerate() to track iteration index without a manual counter variable

Python Execution Flow | Page 4 of 4

You might also like