UNIT 4: Advanced Python Concepts & Robust Programming - Short Notes
1.0 Object-Oriented Programming (OOP) - Deep Dive
1.1 Classes & Objects (Recap & Advanced)
-
__init__vs__new__:-
__new__: Static method, creates a new instance (callsobject.__new__). Rarely overridden; used for immutable types or metaclasses. -
__init__: Instance method, initializes the created instance. Most common for setup. -
Lifecycle:
__new__→__init__.
-
-
Instance vs. Class Variables:
-
Instance variables: Defined in
__init__(e.g.,self.x). Unique to each object. -
Class variables: Defined in class body. Shared across all instances.
class Dog: species = "Canis lupus" # Class variable def __init__(self, name): self.name = name # Instance variable -
-
selfkeyword: Explicit reference to the current instance. Required for instance variable/method access.
1.2 Inheritance & Polymorphism
-
Method Resolution Order (MRO): The order Python searches for methods in a class hierarchy. Use
ClassName.__mro__orClassName.mro(). -
super(): Used to call methods from a parent/sibling class in a cooperative multiple inheritance setup.class Child(Parent): def method(self): super().method() # Calls Parent.method[!TIP] Common Pitfall: In Python 3,
super()without arguments works correctly only inside a class method. -
Polymorphism:
-
Duck Typing: "If it walks like a duck...". Object suitability determined by behavior (methods/attributes), not type.
-
Formal Inheritance: Achieved via method overriding in an inheritance hierarchy.
-
1.3 Special (Dunder/Magic) Methods
-
String Representation:
-
__str__(self): Informal, readable string forprint()&str(). For end-users. -
__repr__(self): Official, unambiguous string for debugging. Should ideally beeval(repr(obj)) == obj.
-
-
Object Comparison:
__eq__(==),__ne__(!=),__lt__(<),__le__(<=),__gt__(>),__ge__(>=). -
Container/Sequence Emulation:
-
__len__(self): Forlen(obj). -
__getitem__(self, key): Forobj[key](indexing/slicing). -
__setitem__,__delitem__for assignment/deletion.
-
-
Callable Objects:
__call__(self, *args, **kwargs)makes an instance callable like a function.
1.4 Properties, Getters & Setters
-
@propertydecorator: Turns a method into a read-only attribute.class Circle: def __init__(self, radius): self._radius = radius @property def radius(self): return self._radius @radius.setter def radius(self, value): if value > 0: self._radius = value -
Encapsulation:
-
_var: Conventionally private (internal use). -
__var: Name mangling (_ClassName__var). Not truly private, harder to access accidentally.
-
1.5 Class & Static Methods
-
@classmethod: First argument iscls(the class). Used for factory methods or operating on class state.class MyClass: @classmethod def from_string(cls, data_str): return cls(*parse(data_str)) -
@staticmethod: No implicit first argument. A utility function logically grouped in the class namespace. No access toselforcls.
2.0 Exception Handling & Custom Exceptions
2.1 Exception Hierarchy
-
Base class:
BaseException→Exception(most built-in exceptions inherit from this). -
Key Subclasses:
-
ValueError,TypeError -
IOError/OSError(file/system errors) -
KeyError,IndexError -
AttributeError
[!TIP] Best Practice: Catch the most specific exception you can handle. Avoid bare
except:. -
2.2 try, except, else, finally Flow
-
tryblock executes. -
If exception occurs → matching
exceptblock executes. -
If no exception →
elseblock executes (optional). -
Always →
finallyblock executes (optional), regardless of exception.
try:
# risky code
except SpecificError:
# handler
else:
# runs if try succeeded (no exception)
finally:
# cleanup (always runs)
2.3 Raising Exceptions
-
raise ValueError("message"): Raise a new exception. -
raise: Inside anexceptblock, re-raises the caught exception. -
Custom Exceptions: Inherit from
Exception(or a relevant subclass).class InsufficientFundsError(Exception): pass raise InsufficientFundsError("Balance too low")
2.4 Context Managers & with Statement
-
Protocol: Object must have
__enter__()(returns resource) and__exit__()(handles cleanup). -
@contextlib.contextmanager: Decorator to create a context manager from a generator function.from contextlib import contextmanager @contextmanager def managed_file(name): f = open(name, 'w') try: yield f finally: f.close()
3.0 File I/O and Data Persistence
3.1 File Operations & Modes
| Mode | Meaning | File Position | Creates New? |
|---|---|---|---|
'r' |
Read (default) | Start | No |
'w' |
Write | Start (truncates) | Yes |
'a' |
Append | End | Yes |
'x' |
Exclusive creation | Start | Fails if exists |
'b' |
Binary mode | - | - |
't' |
Text mode (default) | - | - |
'+' |
Update (read/write) | - | - |
Always use
with open(...) as f:for automatic closure.
3.2 Reading & Writing
-
Text Reading:
-
f.read(size): Readsizechars/bytes. -
f.readline(): Read one line. -
f.readlines(): Read all lines into list. -
Iteration:
for line in f:(most memory-efficient).
-
-
Writing:
-
f.write(str): Write a string. -
f.writelines(list_of_strings): Write iterable of strings (no newlines added automatically).
-
3.3 Standard Streams (sys)
-
sys.stdin: Standard input (keyboard). -
sys.stdout: Standard output (console). -
sys.stderr: Standard error (unbuffered, for errors). -
Can be redirected:
sys.stdout = open('file.txt', 'w').
3.4 Common File Formats
-
CSV (
csvmodule):import csv with open('data.csv') as f: reader = csv.DictReader(f) # Returns dict per row -
JSON (
jsonmodule):-
json.load(f)/json.loads(str)→ Python object. -
json.dump(obj, f)/json.dumps(obj)→ JSON string.
-
-
Pickle (
picklemodule): Serializes any Python object.⚠️ SECURITY WARNING: Never unpickle data from untrusted sources. Arbitrary code execution risk.
4.0 Modules, Packages & Namespaces
4.1 Modules
-
A
.pyfile is a module. Its name is the filename (without.py). -
Import Statements:
-
import module: Access viamodule.name. -
from module import name: Direct access toname. -
from module import *: Imports all public names (names not starting with_). Avoid (namespace pollution).
-
-
__name__ == "__main__": Code under this guard runs only when the file is executed directly, not when imported. -
dir([object]): Returns a list of names in the current namespace or specified object's namespace.
4.2 Packages
-
A directory containing an
__init__.pyfile (can be empty). In Python 3.3+, namespace packages can omit__init__.py. -
Absolute vs. Relative Imports:
-
Absolute:
from package.sub import module -
Relative (inside package):
from . import siblingorfrom ..parent import module
-
4.3 Key Standard Library Modules
| Module | Purpose | Key Items |
|---|---|---|
os / os.path |
OS interaction | os.getcwd(), os.listdir(), os.path.join(), os.path.exists() |
sys |
System params | sys.argv, sys.path, sys.exit() |
datetime |
Dates & times | datetime.datetime, datetime.timedelta |
math |
Math functions | math.pi, math.sqrt(), math.floor() |
random |
Random numbers | random.random(), random.choice(), random.shuffle() |
collections |
Specialized containers | namedtuple, deque, Counter, defaultdict |
itertools |
Efficient iteration | chain(), cycle(), combinations(), permutations() |
5.0 Advanced Topics (High Exam Probability)
5.1 Decorators
-
Concept: A function that takes a function and returns a modified function.
-
Basic Syntax:
def my_decorator(func): def wrapper(*args, **kwargs): # do something before result = func(*args, **kwargs) # do something after return result return wrapper @my_decorator def say_hello(): print("Hello") -
functools.wraps: Decorator to preserve original function's__name__,__doc__, etc.from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper -
Decorators with Arguments:
def repeat(n): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for _ in range(n): func(*args, **kwargs) return wrapper return decorator @repeat(n=3) def greet(): print("Hi")
5.2 Generators & yield
-
Generator Function: Contains
yield. Returns a generator iterator (lazy evaluation).-
Memory Efficient: Produces values on-demand.
-
yieldpauses function, saves local state. Nextnext()resumes.
def count_up_to(n): i = 0 while i < n: yield i i += 1 -
-
Generator Expression:
(x*2 for x in range(10)). Like list comprehension but with().
5.3 Iterators & the Iterator Protocol
-
An object implementing
__iter__()(returns iterator object) and__next__()(returns next value or raisesStopIteration). -
Making Custom Iterable:
class Count: def __init__(self, max): self.max = max def __iter__(self): self.n = 0 return self def __next__(self): if self.n >= self.max: raise StopIteration self.n += 1 return self.n - 1 -
Use
iter(obj)to get iterator,next(iterator)to get next item.
6.0 Common Exam Traps & Best Practices
| Trap / Concept | Explanation & Correct Approach |
|---|---|
| Mutable Default Arguments | def func(lst=[]): Default list created once at function definition. Use None and create inside. |
| LEGB Rule | Name resolution order: Local → Enclosing → Global → Built-in. |
| Shallow vs. Deep Copy | copy.copy(): New container, references same nested objects. copy.deepcopy(): Recursively copies all nested objects. |
== vs is |
==: Value equality (__eq__). is: Object identity (same memory object). Use is for None, True, False. |
| List Comp. vs. Gen. Exp. | [x for x in ...]: Builds full list in memory. (x for x in ...): Returns generator (lazy, memory-efficient). |
tuple vs list vs namedtuple |
tuple: Immutable, ordered. list: Mutable. namedtuple: Immutable tuple with named fields (lightweight class alternative). |
__str__ vs __repr__ |
__str__: For print()/end-user. __repr__: For debugging, should be unambiguous. |
super() in Multiple Inheritance |
Follows MRO. Use super() consistently in all classes for cooperative methods. |
finally with return/break |
finally always executes, even if try or except has return. Can override return value. |
Importing * |
Pollutes namespace, makes code unclear. Use explicit imports. |
isinstance() vs type() |
isinstance(obj, Class) respects inheritance (True for subclasses). type(obj) is Class is exact match only. |
Final Boxed Principle: \boxed{\text{Write explicit, readable code. Prefer composition over inheritance. Use context managers for resource cleanup.}}