11. Chapter 11: Additional Topics

11.1. Precedence and Associativity of Operators in Python

11.1.1. 1. What is Operator Precedence

Operator precedence determines the order in which operators are evaluated in an expression.

result = 10 + 5 * 2
print(result)  # 20, not 30

Multiplication (*) has higher precedence than addition (+).

11.1.2. 2. What is Operator Associativity

Associativity determines the direction of evaluation when operators have the same precedence.

result = 100 / 10 / 2
print(result)  # 5.0

Division is left-to-right associative: (100 / 10) / 2

11.1.3. 3. Using Parentheses to Override Precedence

result = (10 + 5) * 2
print(result)  # 30

Parentheses explicitly control evaluation order.

11.1.4. 4. Arithmetic Operator Precedence

result = 2 + 3 * 4 - 5
print(result)  # 9

Evaluation order: * → + → -

11.1.5. 5. Comparison and Logical Precedence

result = 10 > 5 and 5 > 2
print(result)  # True

Order: Comparison (>) → Logical AND (and)

11.1.6. 6. Logical Operator Hierarchy

result = True or False and False
print(result)  # True

Order: not → and → or

11.1.7. 7. Assignment Precedence

x = y = 10 + 5
print(x, y)  # 15 15

Assignment (=) has lower precedence than arithmetic operators.

11.1.8. 8. Exponentiation Associativity (Right to Left)

result = 2 ** 3 ** 2
print(result)  # 512

Evaluated as: 2 ** (3 ** 2) = 2 ** 9

11.1.9. 9. Bitwise Operator Precedence

result = 5 & 3 | 2
print(result)

Bitwise operations follow strict precedence for predictable results.

11.1.10. 10. Complex Expression Evaluation Example

result = 5 + 2 ** 3 * 4 > 20 and not False
print(result)  # True

Step-by-step: Exponentiation → Multiplication → Addition → Comparison → Logical NOT → AND

11.2. Python Keywords and Identifiers

11.2.1. 1. What are Python Keywords

Keywords are reserved words in Python with predefined meanings that cannot be used as variable or function names.

import keyword
print(keyword.kwlist)

These words define Python’s syntax and control structures.

11.2.2. 2. List of Python Keywords (Core Examples)

import keyword
for word in keyword.kwlist:
    print(word)

Common keywords:

  • Control Flow: if, elif, else, for, while, break, continue

  • Function & Class: def, return, class, lambda

  • Exception Handling: try, except, finally, raise

  • Logical: and, or, not, is, in

11.2.3. 3. What are Identifiers

Identifiers are names used to identify variables, functions, classes, modules, or objects.

total = 100
def calculate_sum():
    pass
class User:
    pass

Here, total, calculate_sum, and User are identifiers.

11.2.4. 4. Rules for Identifiers

An identifier must:

  • Start with a letter (a-z, A-Z) or underscore _

  • Not start with a digit

  • Contain letters, digits, or underscores only

  • Not be a Python keyword

valid_name = 10
_invalid = 20
# 1value = 30  # Invalid

11.2.5. 5. Case Sensitivity in Identifiers

Python identifiers are case-sensitive.

value = 10
Value = 20
print(value)  # 10
print(Value)  # 20

value and Value are treated as two distinct identifiers.

11.2.6. 6. Invalid Identifier Examples

Python enforces strict syntax rules for naming.

# data = 100      # Starts with digit (Invalid)
# class = 10       # Keyword (Invalid)
# my-name = 5      # Special character (Invalid)

11.2.7. 7. Naming Conventions (PEP 8 Standard)

Standard practices:

  • Variables & functions → snake_case

  • Classes → PascalCase

  • Constants → UPPER_CASE

# Variable
total_price = 500

# Function
def calculate_total():
    pass

# Class
class UserAccount:
    pass

11.2.8. 8. Difference Between Keywords and Identifiers

Keywords are reserved words in Python, while identifiers are user-defined names.

  • Keywords: if, class, return

  • Identifiers: count, UserData, my_function

11.2.9. 9. Checking if a Word is a Keyword

import keyword
print(keyword.iskeyword("if"))      # True
print(keyword.iskeyword("value"))   # False

Useful for compilers, linters, and code validators.

11.2.10. 10. Real-World Naming Example

class OrderProcessor:
    def calculate_tax(self, amount):
        tax_rate = 0.18
        return amount * tax_rate

processor = OrderProcessor()
print(processor.calculate_tax(1000))

Demonstrates professionalism and readability using proper identifiers.

11.3. Python Asserts

11.3.1. 1. What is an Assertion

An assertion is a debugging aid that tests a condition and raises an error if the condition evaluates to False.

x = 10
assert x > 0

If the condition fails, Python raises an AssertionError.

11.3.2. 2. Basic Assertion Syntax

assert condition

Example:

age = 18
assert age >= 18

Program continues only if the condition is true.

11.3.3. 3. Assertion with Custom Messages

score = 40
assert score >= 50, "Score must be at least 50 to pass"

Provides clear contextual error information.

11.3.4. 4. Assertion Failure Example

value = -5
assert value > 0, "Value must be positive"
# AssertionError: Value must be positive

Useful during validation and debugging phases.

11.3.5. 5. Using Assertions in Functions

def calculate_square(num):
    assert isinstance(num, int), "Input must be an integer"
    return num * num

print(calculate_square(5))

Ensures correct parameter usage.

11.3.6. 6. Assertions for Invariant Checks

def withdraw(balance, amount):
    assert amount <= balance, "Insufficient balance"
    return balance - amount

print(withdraw(500, 600))

Guarantees logical consistency during execution.

11.3.7. 7. Assertions vs Exception Handling

Assertions are debugging aids for internal logic, while exceptions handle user input validation.

  • Assertions: Can be disabled, for internal logic

  • Exceptions: Always active, for user input validation

11.3.8. 8. Disabling Assertions in Production

Assertions can be disabled by running Python with optimization flag:

python -O script.py

This removes all assert statements from execution.

11.3.9. 9. Advanced Assertion with Complex Logic

def process_data(data):
    assert data is not None and len(data) > 0, "Data cannot be empty"
    return sum(data)

print(process_data([10, 20, 30]))

Combines multiple conditions in one assertion.

11.3.10. 10. Enterprise Use Case: Defensive Programming

def process_order(order):
    assert "id" in order, "Order must contain ID"
    assert order["amount"] > 0, "Order amount must be positive"
    return "Order Processed"

order = {"id": 101, "amount": 250}
print(process_order(order))

Ensures internal system contracts remain valid.

11.4. Python JSON

11.4.1. 1. What is JSON

JSON (JavaScript Object Notation) is a lightweight data-interchange format used for storing and exchanging structured data.

{
  "name": "Alice",
  "age": 30,
  "active": true
}

In Python, JSON is handled using the built-in json module.

11.4.2. 2. Importing the JSON Module

import json

Provides methods to convert between Python objects and JSON format.

11.4.3. 3. Convert Python Object → JSON String (json.dumps)

import json

data = {"name": "Alice", "age": 30, "city": "Toronto"}
json_string = json.dumps(data)
print(json_string)

Serializes Python objects into a JSON-formatted string.

11.4.4. 4. Convert JSON String → Python Object (json.loads)

import json

json_text = '{"name": "Bob", "age": 25}'
python_obj = json.loads(json_text)
print(python_obj)
print(type(python_obj))  # dict

Deserializes JSON string into Python data structures.

11.4.5. 5. Writing JSON to a File (json.dump)

import json

data = {
    "product": "Laptop",
    "price": 1200,
    "in_stock": True
}

with open("product.json", "w") as file:
    json.dump(data, file)

Stores structured data persistently.

11.4.6. 6. Reading JSON from a File (json.load)

import json

with open("product.json", "r") as file:
    data = json.load(file)

print(data)

Loads JSON data into Python objects.

11.4.7. 7. Pretty Printing JSON

import json

data = {"name": "Alice", "skills": ["Python", "AI", "ML"]}
pretty_json = json.dumps(data, indent=4)
print(pretty_json)

Improves readability for logs and debugging.

11.4.8. 8. Handling Complex Data Types

import json
from datetime import datetime

data = {"event": "login", "time": str(datetime.now())}
json_string = json.dumps(data)
print(json_string)

Non-serializable objects must be converted manually.

11.4.9. 9. Sorting JSON Keys

import json

data = {"b": 2, "a": 1, "c": 3}
sorted_json = json.dumps(data, sort_keys=True)
print(sorted_json)

Useful for consistent API responses and hashing.

11.4.10. 10. Enterprise Example: API Response Handling

import json

def parse_api_response(response):
    try:
        data = json.loads(response)
        return data.get("status"), data.get("payload")
    except json.JSONDecodeError:
        return "error", None

response = '{"status": "success", "payload": {"id": 101}}'
print(parse_api_response(response))

Standard pattern for microservices and REST APIs.

11.5. Python pip

11.5.1. 1. What is pip

pip is Python’s official package manager used to install, upgrade, and manage external libraries and dependencies.

pip --version

It connects to the Python Package Index (PyPI) to fetch and install packages.

11.5.2. 2. Installing a Package

pip install requests

Downloads and installs the requests package and its dependencies into the environment.

11.5.3. 3. Upgrading a Package

pip install --upgrade requests

Ensures the latest stable version of a package is installed.

11.5.4. 4. Uninstalling a Package

pip uninstall requests

Removes the package and related files cleanly.

11.5.5. 5. Listing Installed Packages

pip list

Displays all currently installed Python packages in the environment.

11.5.6. 6. Viewing Package Information

pip show requests

Provides metadata such as version, location, dependencies, and author details.

11.5.7. 7. Installing Specific Package Version

pip install numpy==1.24.0

Locks installation to an exact version for reproducibility.

11.5.8. 8. Using requirements.txt

Example requirements.txt:

flask==2.2.3
requests>=2.25
numpy

Essential for dependency management in team environments.

11.5.9. 9. Freezing Dependencies

pip freeze > requirements.txt

Exports exact installed package versions for deployment or CI/CD pipelines.

11.5.10. 10. Enterprise Deployment Example

# Step 1: Create virtual environment
python -m venv venv

# Step 2: Activate environment
source venv/bin/activate   # macOS/Linux
venv\Scripts\activate      # Windows

# Step 3: Install dependencies
pip install -r requirements.txt

This ensures isolated, repeatable, production-grade deployments.

11.6. Python *args and **kwargs

11.6.1. 1. What are *args and **kwargs

*args and **kwargs allow a function to accept a variable number of arguments.

  • *args → Variable positional arguments

  • **kwargs → Variable keyword arguments (key-value pairs)

def demo(*args, **kwargs):
    print(args)
    print(kwargs)

demo(1, 2, 3, name="Alice", age=30)

Enables highly flexible function signatures.

11.6.2. 2. Using *args (Variable Positional Arguments)

def add_numbers(*args):
    return sum(args)

print(add_numbers(1, 2, 3, 4))  # 10

All positional arguments are captured as a tuple.

11.6.3. 3. Iterating Over *args

def display_values(*args):
    for value in args:
        print(value)

display_values("Python", "AI", "ML")

Ideal for functions handling unknown input sizes.

11.6.4. 4. Using **kwargs (Variable Keyword Arguments)

def display_profile(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

display_profile(name="Alice", role="Engineer", location="Toronto")

All keyword arguments are captured as a dictionary.

11.6.5. 5. Combining *args and **kwargs

def process_data(*args, **kwargs):
    print("Positional:", args)
    print("Keyword:", kwargs)

process_data(10, 20, user="CSP", status="Active")

Supports highly dynamic functions.

11.6.6. 6. Order of Parameters

def example(a, b, *args, c=10, **kwargs):
    print(a, b, args, c, kwargs)

example(1, 2, 3, 4, key="value")

Correct function signature order: Normal parameters → *args → Default parameters → **kwargs

11.6.7. 7. Unpacking Arguments with * and **

def greet(name, age):
    print(f"{name} is {age} years old")

data = ("Alice", 30)
greet(*data)

info = {"name": "Bob", "age": 25}
greet(**info)

Used for data-driven function execution.

11.6.8. 8. Forwarding Arguments to Another Function

def wrapper(*args, **kwargs):
    return calculate(*args, **kwargs)

def calculate(a, b):
    return a + b

print(wrapper(5, 10))

Common in decorators and middleware.

11.6.9. 9. Dynamic API Handler Example

def api_handler(endpoint, *args, **kwargs):
    print(f"Calling {endpoint}")
    print("Params:", args)
    print("Options:", kwargs)

api_handler("/users", 1, 2, limit=10, sort="asc")

Used in REST clients and flexible service routers.

11.6.10. 10. Enterprise-Grade Example

def log_event(event_type, *args, **kwargs):
    print(f"Event: {event_type}")
    if args:
        print("Details:", args)
    if kwargs:
        print("Metadata:", kwargs)

log_event("LOGIN", "UserID:101", ip="192.168.1.1", status="success")

Ideal for dynamic logging, analytics, and telemetry systems.