
Python
Master safe input handling, duck typing philosophy, object references, and memory management patterns.
import re
from typing import Union, Optional
# Robust input parsing
def get_positive_integer(prompt: str) -> int:
"""Get and validate a positive integer from user."""
while True:
try:
value = int(input(prompt))
if value <= 0:
print("Please enter a positive number.")
continue
return value
except ValueError:
print("Please enter a valid integer.")
# Advanced: Regex validation
def get_email(prompt: str) -> str:
"""Get valid email address."""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
while True:
email = input(prompt).strip()
if re.match(pattern, email):
return email
print("Invalid email format.")
# Safe choice selection
def choose_option(options: list, prompt: str) -> str:
"""Let user choose from list of options."""
while True:
print("\nOptions:")
for i, option in enumerate(options, 1):
print(f"{i}. {option}")
try:
choice = int(input(prompt))
if 1 <= choice <= len(options):
return options[choice - 1]
print(f"Please enter 1-{len(options)}")
except ValueError:
print("Please enter a valid number.")
# Parse CSV input
def get_numbers_list(prompt: str) -> list:
"""Parse comma-separated numbers from user."""
while True:
try:
input_str = input(prompt)
values = [float(x.strip()) for x in input_str.split(',')]
return values
except ValueError:
print("Please enter valid numbers separated by commas.")Python embraces duck typing: "If it walks like a duck and quacks like a duck, it's a duck."
# Rather than checking types, check for behaviors
# ✗ Type checking approach
def process_sequence_strict(obj):
if type(obj) == list or type(obj) == tuple:
return len(obj)
raise TypeError("Must be list or tuple")
# ✓ Duck typing approach
def process_sequence(obj):
"""Works with any object that has __len__."""
return len(obj)
# Works with multiple types!
print(process_sequence([1, 2, 3])) # 3
print(process_sequence((1, 2, 3))) # 3
print(process_sequence("hello")) # 5
print(process_sequence({1, 2, 3})) # 3
# EAFP: Easier to Ask for Forgiveness than Permission
def get_item_eafp(obj, index):
"""EAFP approach: try first, catch errors."""
try:
return obj[index]
except (IndexError, KeyError, TypeError):
return None
# vs LBYL: Look Before You Leap
def get_item_lbyl(obj, index):
"""LBYL approach: check first."""
if hasattr(obj, '__getitem__'):
try:
return obj[index]
except (IndexError, KeyError):
return None
return None
# EAFP is more Pythonic!Protocol-based design:
from typing import Protocol
class Drawable(Protocol):
"""Anything with draw() method."""
def draw(self) -> None: ...
class Saveable(Protocol):
"""Anything with save() method."""
def save(self) -> None: ...
def draw_all(objects: list[Drawable]) -> None:
"""Draw any objects that have draw()."""
for obj in objects:
obj.draw()
# Works with any class implementing draw()
class Circle:
def draw(self): print("●")
class Square:
def draw(self): print("■")
draw_all([Circle(), Square()]) # Works!# Identity vs Equality
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True (same content)
print(a is b) # False (different objects)
print(a is c) # True (same object)
# id() returns object's memory address
print(id(a)) # Different
print(id(b)) # Different
print(id(c)) # Same as a
# Integers have special caching
x = 5
y = 5
print(x is y) # True (small ints cached)
x = 300
y = 300
print(x is y) # False (large ints not cached)# Immutable: int, str, tuple, frozenset
x = 5
y = x
y = 10
print(x) # Still 5 (y points to new object)
# Mutable: list, dict, set
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4] (modified!)
# Consequence: mutable default arguments
def append_to_list(item, items=None): # ✓ Right
if items is None:
items = []
items.append(item)
return items
def bad_append(item, items=[]): # ✗ Wrong: shared!
items.append(item)
return items
# Demonstrate the problem
print(bad_append(1)) # [1]
print(bad_append(2)) # [1, 2] ← Unexpected!import copy
# Shallow copy: copies outer object, not contents
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
original[0][0] = 99
print(shallow) # [[99, 2], [3, 4]] ← Inner list modified!
# Deep copy: recursively copies everything
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
original[0][0] = 99
print(deep) # [[1, 2], [3, 4]] ← Not affected
# When to use
def process_data(data):
"""Safe: doesn't modify input."""
copy_data = copy.deepcopy(data)
# Modify copy, not original
copy_data[0] = "modified"
return copy_data
original = [1, 2, 3]
result = process_data(original)
print(original) # [1, 2, 3] ← Unchangedimport gc
import sys
# Reference counting
x = [1, 2, 3]
print(sys.getrefcount(x)) # Number of references
y = x # Increment reference count
print(sys.getrefcount(x)) # Increased
# Garbage collection: automatic when references drop to 0
def create_large_object():
large_list = [0] * 1_000_000
return len(large_list)
result = create_large_object()
# large_list automatically freed when function ends
# Circular references
class Node:
def __init__(self, value):
self.value = value
self.next = None
a = Node(1)
b = Node(2)
a.next = b
b.next = a # Circular reference!
# Python's garbage collector handles this
del a, b
gc.collect() # Explicitly trigger collection
# Memory profiling
from tracemalloc import start, get_traced_memory
start()
data = [i**2 for i in range(100_000)]
current, peak = get_traced_memory()
print(f"Current: {current / 1024:.1f} KB") # Memory used# ✓ Use try-except for input parsing
def safe_get_float(prompt: str) -> Optional[float]:
try:
return float(input(prompt))
except ValueError:
return None
# ✓ Validate input before using
user_age = get_positive_integer("Enter age: ")
assert 0 < user_age < 150, "Age out of valid range"
# ✓ Use duck typing for flexibility
def count_items(container):
"""Works with any container that has __len__."""
return len(container)
# ✓ Deep copy when modifying received data
def modify_user_data(user_data):
safe_copy = copy.deepcopy(user_data)
safe_copy['modified'] = True
return safe_copy
# ✗ Don't assume type, use hasattr or duck typing
def process(obj):
if hasattr(obj, '__len__'): # Duck typing
return len(obj)
return None
# ✗ Don't rely on object identity for values
if x is "hello": # Wrong
pass
if x == "hello": # Right
pass| Concept | Remember |
|---|---|
| Input Validation | Always validate user input before using |
| Duck Typing | Check behaviors, not types (Pythonic) |
| References | Understanding `is` vs `==` prevents bugs |
| Mutable Defaults | Use `None` as default for mutable arguments |
| Deep Copy | Use when modifying received objects |
| Garbage Collection | Python handles automatically via reference counting |
Consolidate your knowledge with Best Practices.
Ready to practice? Challenges | Quiz
Resources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep • Build Real • Verify Skills • Launch Forward