Skip to content
IT-605 · Programming in Python/Quick Revision Short Notes

Programming in Python (IT-605) - Unit 3 Short Notes

I. Functions - The Core Building Blocks

Defining and Calling Functions

  • Syntax: def function_name(parameters): followed by an indented block.

  • return Statement: Exits the function and passes back a value. Can return multiple values as a tuple (e.g., return x, y).

    
    def square(x):
    
        return x * x
    
    

Parameters and Arguments

Concept Description Example
Positional Arguments Must match the order of parameters in the function definition. greet("Alice", "Hello")
Keyword Arguments (kwargs) Passed by parameter name; order doesn't matter. greet(name="Bob", msg="Hi")
Default Values Parameters can have defaults; used if argument is omitted. def greet(name, msg="Hello"):
*args Captures extra positional arguments as a tuple. def func(a, *args):
**kwargs Captures extra keyword arguments as a dict. def func(a, **kwargs):

Variable Scope and Namespaces (LEGB Rule)

The LEGB Rule defines the scope resolution order:

  1. Local: Inside the current function.

  2. Enclosing: In any enclosing function (for nested functions).

  3. Global: At the top level of the module/file.

  4. Built-in: In Python's built-in names (e.g., len, print).

  • global keyword: Modifies a variable in the Global scope from within a local scope.

  • nonlocal keyword: Modifies a variable in the Enclosing (non-local) scope from within a nested function.

Function Objects and First-Class Citizenship

Functions in Python are first-class objects:

  • Can be assigned to variables: my_func = greet

  • Can be passed as arguments (Higher-Order Functions): def call_twice(func, x): return func(func(x))

  • Can be returned from other functions (Closures): A nested function remembering values from its enclosing scope.

    
    def make_multiplier(n):
    
        def multiplier(x):
    
            return x * n  # 'n' is remembered from enclosing scope
    
        return multiplier
    
    

Lambda Expressions

  • Syntax: lambda arguments: expression

  • Used for small, anonymous functions, often with map(), filter(), sorted().

  • Limitation: Can only contain a single expression (no statements like if-else blocks, for, while). Use regular def for complex logic.

    
    squares = list(map(lambda x: x**2, [1,2,3]))
    
    

Docstrings and Annotations

  • Docstring: A string literal as the first statement in a function/module/class. Accessed via function.__doc__. Crucial for documentation.

    
    def add(a, b):
    
        """Return the sum of two numbers."""
    
        return a + b
    
    
  • Annotations (Type Hints): Optional metadata about parameter/return types.

    
    def greet(name: str) -> str:
    
        return "Hello " + name
    
    # Access via: greet.__annotations__ -> {'name': <class 'str'>, 'return': <class 'str'>}
    
    

[!TIP] Exam Focus: Be prepared to trace scope in nested functions using LEGB. Understand the difference between *args (tuple) and **kwargs (dict). Lambda is for simple, one-line functions.


II. Core Data Structures - Lists, Tuples, Dictionaries, Sets

Lists (Mutable Sequences)

Operation Time Complexity (Average) Description
append(item) $O(1)$ Add item to end.
insert(i, item) $O(n)$ Insert at index i (shifts elements).
pop() / pop(-1) $O(1)$ Remove/return last item.
pop(i) $O(n)$ Remove/return item at index i.
remove(item) $O(n)$ Remove first occurrence of item.
index(item) $O(n)$ Return first index of item.
in (membership) $O(n)$ Check if item exists.
sort() $O(n \log n)$ In-place sort (Timsort).
Slicing [i:j:k] $O(k)$ Creates a new list of size k.
  • List Comprehension: [expression for item in iterable if condition]

    
    squares = [x**2 for x in range(10) if x % 2 == 0]
    
    
  • Shallow vs. Deep Copy:

    • Shallow: New outer list, but inner objects are references. new_list = old_list[:] or old_list.copy().

    • Deep: Recursively copies all nested objects. Use import copy; new_list = copy.deepcopy(old_list).

Tuples (Immutable Sequences)

  • Creation: t = (1, 2, 3) or t = 1, 2, 3 (comma creates tuple). Single-element tuple: t = (1,).

  • Immutability: Cannot change, add, or remove elements after creation. Hashable if all elements are hashable → can be dictionary keys.

  • Packing/Unpacking: a, b, c = (1, 2, 3) or t = 1, 2, 3.

  • namedtuple: Factory function for creating tuple subclasses with named fields.

    
    from collections import namedtuple
    
    Point = namedtuple('Point', ['x', 'y'])
    
    p = Point(11, y=22)
    
    

Dictionaries (Key-Value Mappings)

Operation Average Time Complexity
d[key] / d.get(key) $O(1)$
d[key] = value $O(1)$
key in d $O(1)$
del d[key] $O(1)$
keys(), values(), items() $O(1)$ (return view objects, not lists)
  • Dictionary Comprehension: {key_expr: value_expr for item in iterable}

    
    {x: x**2 for x in range(5)}
    
    
  • dict.get(key, default): Safer access; returns default if key missing (no KeyError).

  • collections.defaultdict: Subclass of dict; calls a factory function to provide default values for missing keys.

  • collections.OrderedDict (Python 3.7+): Standard dict preserves insertion order by default. OrderedDict has additional ordering methods.

Sets (Unordered, Unique Elements)

  • Creation: s = {1, 2, 3} or s = set([1, 2, 2]) → {1, 2}. Empty set is set(), not {} (which is an empty dict).

  • Mathematical Operations:

    | Operation | Symbol | Method | | :--- | :--- | :--- | | Union | \| | s1.union(s2) | | Intersection | & | s1.intersection(s2) | | Difference | - | s1.difference(s2) | | Symmetric Difference | ^ | s1.symmetric_difference(s2) |

  • frozenset: Immutable version of a set. Hashable → can be a dictionary key or element of another set.

[!TIP] Exam Focus: Know time complexities for list/dict operations. Distinguish mutable (list, dict, set) vs immutable (tuple, frozenset) types. Remember set requires hashable elements. Shallow copy pitfalls with nested lists are a classic question.


III. Working with Files and I/O

File Opening Modes

Mode Meaning File Pointer Position
'r' Read (default) Start
'w' Write (truncates) Start (creates new)
'a' Append End
'x' Exclusive creation Start (fails if exists)
'b' Binary mode -
't' Text mode (default) -
'+' Open for updating (read/write) -
  • Common Combinations: 'r+' (read/write), 'w+' (write/read, truncates), 'a+' (append/read).

The with Statement (Context Manager)

  • Purpose: Ensures proper acquisition and release of resources (like files) automatically, even if exceptions occur.

  • Syntax:

    
    with open('file.txt', 'r') as f:
    
        data = f.read()
    
    # File is automatically closed here
    
    

Why it's best practice: Eliminates the need for explicit try-finally blocks to close files.

Reading from Files

  • f.read(size=-1): Read size bytes/characters. -1 or omitted reads entire file.

  • f.readline(): Read a single line (including trailing newline).

  • f.readlines(): Read all lines into a list.

  • Iterating: for line in f: is memory-efficient (reads line by line).

Writing to Files

  • f.write(str): Write a string to the file.

  • f.writelines(list_of_strings): Write a sequence of strings. Does not add newlines automatically.

  • Newline Handling: In text mode, Python translates \n to the OS-specific newline on write, and vice-versa on read.

File and Directory Operations (os & pathlib)

  • os Module (Procedural):

    
    import os
    
    os.getcwd()       # Current working directory
    
    os.listdir('.')   # List files in directory
    
    os.mkdir('new_dir')
    
    os.remove('file.txt')
    
    os.rename('old.txt', 'new.txt')
    
    
  • pathlib.Path (Object-Oriented - Recommended):

    
    from pathlib import Path
    
    p = Path('folder/file.txt')
    
    p.exists()
    
    p.is_file()
    
    p.is_dir()
    
    p.mkdir(parents=True, exist_ok=True)
    
    p.unlink()  # Delete file
    
    p.rename('new_file.txt')
    
    

[!TIP] Exam Focus: Know the with statement syntax and its advantage. Distinguish between read(), readline(), and readlines(). Understand that writelines() needs explicit newlines. Be able to convert basic os calls to pathlib equivalents.


IV. Error Handling and Exceptions

Exceptions vs. Syntax Errors

  • Syntax Errors: Detected at compile-time (parsing). Code won't run.

  • Exceptions: Detected at runtime. Can be caught and handled.

Common Built-in Exceptions (Overview)

Exception Typical Cause
ValueError Right type, inappropriate value (e.g., int('abc'))
TypeError Operation/function applied to wrong type (e.g., 'a' + 1)
IndexError Sequence subscript out of range
KeyError Dictionary key not found
FileNotFoundError open() on non-existent file (mode 'r')
ZeroDivisionError Division/modulo by zero
AttributeError Attribute reference/assignment fails
ImportError / ModuleNotFoundError import fails

try, except, else, finally Blocks


try:

    # Risky code

    result = 10 / 0

except ZeroDivisionError as e:

    # Handle specific exception

    print(f"Error: {e}")

except (TypeError, ValueError):

    # Handle multiple exceptions

    print("Type or Value error")

except Exception as e:

    # Catch-all (use sparingly)

    print(f"Unexpected: {e}")

else:

    # Executes if NO exception raised in try block

    print("Operation successful")

finally:

    # ALWAYS executes (cleanup: close files, release locks)

    print("Cleanup complete")

  • Order: Specific exceptions before general ones. except: without type catches all exceptions (including system-exiting ones like KeyboardInterrupt).

The raise Statement

  • Raising Built-in: raise ValueError("Invalid input")

  • Custom Exceptions: Define by inheriting from Exception (or a subclass).

    
    class MyCustomError(Exception):
    
        def __init__(self, message):
    
            super().__init__(message)
    
    raise MyCustomError("Something went wrong")
    
    

Exception Chaining and Context

  • raise ... from ...: Explicitly sets the __cause__ attribute, showing the original exception that led to the current one.

    
    try:
    
        int('abc')
    
    except ValueError as e:
    
        raise RuntimeError("Failed conversion") from e
    
    
  • __context__: Automatically set when an exception occurs in an except block.

[!TIP] Exam Focus: Structure of try-except-else-finally. Know when else vs finally runs. Custom exception definition is a common coding question. Understand raise from for preserving traceback.


V. Modules and Packages

The Module System

  • Module: A single .py file containing Python definitions and statements.

  • import Variations:

    
    import module           # Access via module.name
    
    from module import name # Access via name directly
    
    from module import *    # Imports all public names (discouraged)
    
    
  • sys.path: List of directories Python searches for modules. Current directory is first.

  • __name__ Variable:

    • When a module is run directly: __name__ == "__main__"

    • When a module is imported: __name__ == "module_name"

    • ** idiom:** if __name__ == "__main__": to put test/example code that runs only on direct execution.

Creating and Using Packages

  • Package: A directory containing a special __init__.py file (can be empty) and module files/sub-packages.

    
    mypackage/
    
        __init__.py
    
        module1.py
    
        subpackage/
    
            __init__.py
    
            module2.py
    
    
  • Absolute Import: from mypackage import module1 (from project root).

  • Relative Import: (Inside a package) from . import module1 (current package), from .. import module2 (parent package). Only works within packages.

Standard Library Highlights

  • math: sqrt(), pi, ceil(), floor(), log().

  • random: randint(a, b), choice(seq), shuffle(seq), random().

  • datetime: datetime.now(), date, time, timedelta.

  • json: json.dumps(obj) (to string), json.loads(str) (from string), json.dump(obj, file), json.load(file).

  • collections: namedtuple, defaultdict, Counter, deque, OrderedDict.

[!TIP] Exam Focus: Purpose of __name__ == "__main__". Difference between absolute and relative imports. Know basic usage of at least 3 standard library modules (e.g., json for serialization, datetime for dates, collections.Counter for counting).


VI. Intermediate Concepts & Best Practices

Iterators and the iter()/next() Protocol

  • Iterable: An object that can return an iterator. Must implement __iter__() (returns an iterator object). Examples: list, tuple, dict, set, str.

  • Iterator: An object that maintains state and produces the next value on demand. Must implement:

    1. __iter__(): Returns the iterator object itself.

    2. __next__(): Returns the next item. Raises StopIteration when exhausted.

  • Relationship: iter(iterable) calls iterable.__iter__() to get an iterator. next(iterator) calls iterator.__next__().

    
    it = iter([1, 2, 3])
    
    print(next(it))  # 1
    
    print(next(it))  # 2
    
    

Generators and yield

  • Generator Function: Defined with def but contains yield expression(s). Returns a generator iterator (a type of iterator).

  • yield: Pauses function execution, saves local state, and returns a value. On next next(), resumes from where it left off.

  • Generator Expression: Similar to list comprehension but with () instead of []. Returns a generator object (lazy evaluation).

    
    gen_exp = (x**2 for x in range(1000000))  # No list created in memory
    
    
  • Advantage: Memory efficient for large/streaming data. Values are generated on-the-fly.

Decorators (Conceptual Introduction)

  • A decorator is a function that takes another function as an argument and returns a new function (usually a wrapper) to extend/modify its behavior without permanently modifying it.

  • @decorator syntax: Syntactic sugar for func = decorator(func).

    
    def my_decorator(func):
    
        def wrapper():
    
            print("Before function")
    
            func()
    
            print("After function")
    
        return wrapper
    
    @my_decorator
    
    def say_hello():
    
        print("Hello!")
    
    # say_hello() now prints:
    
    # Before function
    
    # Hello!
    
    # After function
    
    

Code Quality and Style (PEP 8)

Convention Example
Naming snake_case for functions/variables, PascalCase for classes, UPPER_CASE for constants.
Indentation 4 spaces per level (no tabs).
Line Length Max 79 characters (code), 72 (docstrings/comments).
Imports Standard library → third-party → local, each group separated by a blank line.
Whitespace Around operators (except high-priority like **), after commas, but not inside parentheses/brackets.
Docstrings Triple double-quotes ("""). One-liner on same line, multi-line with summary line, blank line, then description.

[!TIP] Exam Focus: Be able to write a simple generator function using yield. Understand the basic decorator pattern (wrapper function). Know PEP 8 naming conventions and the purpose of if __name__ == "__main__". Distinguish between an iterable (has __iter__) and an iterator (has __next__).

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