Skip to content
EX-406 · Python/Quick Revision Short Notes

Python (EX-406) - Unit 4 Short Notes

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 (calls object.__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
    
    
  • self keyword: 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__ or ClassName.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 for print() & str(). For end-users.

    • __repr__(self): Official, unambiguous string for debugging. Should ideally be eval(repr(obj)) == obj.

  • Object Comparison: __eq__ (==), __ne__ (!=), __lt__ (<), __le__ (<=), __gt__ (>), __ge__ (>=).

  • Container/Sequence Emulation:

    • __len__(self): For len(obj).

    • __getitem__(self, key): For obj[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
  • @property decorator: 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 is cls (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 to self or cls.


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
  1. try block executes.

  2. If exception occurs → matching except block executes.

  3. If no exception → else block executes (optional).

  4. Always → finally block 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 an except block, 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): Read size chars/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 (csv module):

    
    import csv
    
    with open('data.csv') as f:
    
        reader = csv.DictReader(f)  # Returns dict per row
    
    
  • JSON (json module):

    • json.load(f) / json.loads(str) → Python object.

    • json.dump(obj, f) / json.dumps(obj) → JSON string.

  • Pickle (pickle module): 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 .py file is a module. Its name is the filename (without .py).

  • Import Statements:

    • import module: Access via module.name.

    • from module import name: Direct access to name.

    • 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__.py file (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 sibling or from ..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.

    • yield pauses function, saves local state. Next next() 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 raises StopIteration).

  • 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.}}

Go to where you left off?

Quick Add to Notes

Save questions, your own notes and screenshots into notes filed by unit. It takes a free account.

Create free account

Have an account? Log in