0% found this document useful (0 votes)
2 views52 pages

DevOpsShack Complete Python DeepDive

The document is a comprehensive guide to Python, covering its introduction, unique features, installation, and differences between Python 2 and 3. It also delves into variables, data types, operators, and expressions, providing examples and best practices for programming in Python. The guide emphasizes Python's readability, versatility, and extensive ecosystem, making it suitable for various applications such as web development, data science, and automation.

Uploaded by

nagururgukt
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views52 pages

DevOpsShack Complete Python DeepDive

The document is a comprehensive guide to Python, covering its introduction, unique features, installation, and differences between Python 2 and 3. It also delves into variables, data types, operators, and expressions, providing examples and best practices for programming in Python. The guide emphasizes Python's readability, versatility, and extensive ecosystem, making it suitable for various applications such as web development, data science, and automation.

Uploaded by

nagururgukt
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

DevOps Shack | Complete Python Ultra Deep Dive Guide devopsshack.

com

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 1 — Introduction to Python


Python is a high-level, interpreted, general-purpose programming language created by Guido van
Rossum and first released in 1991. Its design philosophy emphasises code readability and simplicity,
making it one of the most beginner-friendly yet powerful languages in existence.

1.1 What Makes Python Special?


Unlike languages such as C++ or Java, Python uses indentation to define code blocks instead of
braces. Its syntax reads almost like English, which drastically reduces the time from idea to working
code.

Feature Description
Interpreted Code runs line-by-line; no separate compilation step needed.
Dynamically Typed Variable types are inferred at runtime — no need to declare int, str,
etc.
Garbage Collected Memory is managed automatically; you rarely worry about allocation.
Cross-Platform Same code runs on Windows, macOS, Linux with zero changes.
Batteries Included The standard library covers networking, I/O, maths, dates, and more.
Huge Ecosystem PyPI hosts 500,000+ third-party packages for every imaginable use
case.

1.2 Where Python Is Used


Domain Examples & Key Libraries
Web Development Django, Flask, FastAPI — powers Instagram, Pinterest, Disqus.
Data Science Pandas, NumPy, Matplotlib — analysis, cleaning, visualisation.
Machine Learning TensorFlow, PyTorch, Scikit-learn — models & neural networks.
DevOps & Cloud Ansible, Boto3, Fabric — automation, infrastructure, CI/CD.
Scripting File manipulation, batch jobs, system admin tasks.
Automation Selenium, PyAutoGUI — browser and desktop automation.
Cybersecurity Scapy, Requests — pen testing, network analysis.
Game Development Pygame — 2D games and simulations.

1.3 Installing Python


Download the latest Python 3 release from [Link]. During installation on Windows, tick "Add
Python to PATH". Verify installation:
# In your terminal / command prompt

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

python --version # Python 3.12.x


python3 --version # on macOS / Linux

# Launch the interactive REPL


python
>>> print("Hello, Python!")
Hello, Python!
>>> exit()

1.4 Python 2 vs Python 3


Always use Python 3. Python 2 reached end-of-life in January 2020 and is no longer maintained.
Feature Python 2 Python 3
print print "Hello" print("Hello")
Division 5/2 = 2 (integer) 5/2 = 2.5 (float)
Unicode str is bytes by default str is Unicode by default
range() Returns a list Returns a lazy iterator
input() raw_input() for strings input() always returns str
Support End of life 2020 Active development

1.5 Your First Python Program


Save the file as [Link] and run it with: python [Link]
# [Link]
# This is a comment — Python ignores it
print("Hello, World!") # Print to console
print("Welcome to Python 3!") # Second line

# Variables — no declaration needed


name = "DevOps Shack"
year = 2024
print(f"Created by {name} in {year}")

Output
Hello, World! Welcome to Python 3! Created by DevOps Shack in 2024

1.6 Python IDEs and Editors


Tool Best For
VS Code + Python Extension Most popular all-rounder. Free, fast, excellent IntelliSense.
PyCharm Community Full IDE; excellent for larger projects.
Jupyter Notebook Data science and interactive exploration.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

IDLE Bundled with Python; great for beginners.


Google Colab Free cloud Jupyter notebook with GPU access.

Pro Tip
Use VS Code with the Pylance and Black Formatter extensions for auto-completion, linting, and auto-
formatting on save.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 2 — Variables and Data Types


Variables are containers for storing data. In Python you simply assign a value — no type declaration
required. Python infers the type from the value.

2.1 Variable Rules


• Variable names must start with a letter or underscore, never a digit.
• Names are case-sensitive: age, Age, and AGE are three different variables.
• Use snake_case for variable names: user_name, total_price.
• Avoid Python reserved keywords: if, for, while, class, def, etc.

# variable_basics.py
# Valid assignments
name = "Alice"
age = 30
price = 19.99
is_active = True
_private = "internal"

# Multiple assignment on one line


x, y, z = 10, 20, 30
print(x, y, z) # 10 20 30

# Assign same value to multiple variables


a = b = c = 0

# Check type dynamically


print(type(name)) # <class "str">
print(type(age)) # <class "int">
print(type(price)) # <class "float">

2.2 Core Data Types


Type Keyword Example Values
Integer int 0, 42, -7, 1_000_000
Floating Point float 3.14, -0.5, 2.0e8
String str "hello", "world", "DevOps"
Boolean bool True, False
NoneType None None (represents absence of value)
List list [1, 2, 3], ["a", "b"]
Tuple tuple (1, 2), ("x", "y", "z")
Set set {1, 2, 3}, {"a", "b"}
Dictionary dict {"key": "value"}, {"age": 30}

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

2.3 Integers
Python integers have unlimited precision — they can be arbitrarily large without overflow.
x = 42
big = 1_000_000_000 # underscores for readability
neg = -17

# Integer bases
binary = 0b1010 # 10 (binary prefix)
octal = 0o17 # 15 (octal prefix)
hexval = 0xFF # 255 (hex prefix)

print(binary, octal, hexval) # 10 15 255

# Big integers — Python handles them natively


factorial = 1
for i in range(1, 21):
factorial *= i
print(factorial) # 2432902008176640000

2.4 Floats
Floats follow the IEEE 754 double-precision standard. Be aware of floating-point precision issues.
pi = 3.14159
sci = 1.5e10 # scientific notation: 15000000000.0
small = 2.5e-4 # 0.00025

# Precision quirk
print(0.1 + 0.2) # 0.30000000000000004
print(round(0.1 + 0.2, 2)) # 0.3 <- use round() to fix

# Useful float functions


import math
print([Link](3.7)) # 3
print([Link](3.2)) # 4
print([Link](16)) # 4.0
print(abs(-5.5)) # 5.5

2.5 Strings
Strings are immutable sequences of Unicode characters. They can be created with single, double, or
triple quotes.
# Different quote styles
single = 'Hello'
double = "World"
multi = """This spans
multiple lines"""

# String concatenation

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

full = "Hello" + " " + "World"


print(full) # Hello World

# Repetition
line = "-" * 40
print(line) # ----------------------------------------

# f-strings (Python 3.6+) — the best way to format


name = "Alice"
age = 30
print(f"{name} is {age} years old") # Alice is 30 years old
print(f"Next year: {age + 1}") # Next year: 31

# Raw strings (ignore escape sequences)


path = r"C:\Users\Alice\Documents"
print(path) # C:\Users\Alice\Documents

String Methods
s = " Hello, Python World! "

print([Link]()) # "Hello, Python World!"


print([Link]()) # " hello, python world! "
print([Link]()) # " HELLO, PYTHON WORLD! "
print([Link]("Python", "DevOps")) # " Hello, DevOps World! "
print([Link](",")) # [" Hello", " Python World! "]
print([Link]("Python")) # 9 (index of first match)
print([Link]("l")) # 3
print([Link](" Hello")) # True
print([Link]("! ")) # True

# Join — opposite of split


words = ["Python", "is", "awesome"]
sentence = " ".join(words)
print(sentence) # Python is awesome

String Slicing
s = "Python"
# P y t h o n
# 0 1 2 3 4 5 (positive index)
# -6 -5 -4 -3 -2 -1 (negative index)

print(s[0]) # P
print(s[-1]) # n
print(s[0:3]) # Pyt (start:stop, stop excluded)
print(s[2:]) # thon (from index 2 to end)
print(s[:4]) # Pyth (from start to index 4)
print(s[::2]) # Pto (every 2nd character)
print(s[::-1]) # nohtyP (reversed!)

2.6 Booleans

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

is_valid = True
is_empty = False

# Booleans are subclasses of int


print(int(True)) # 1
print(int(False)) # 0
print(True + True) # 2

# Truthy / Falsy values


print(bool(0)) # False
print(bool("")) # False
print(bool([])) # False
print(bool(None)) # False
print(bool(42)) # True
print(bool("hello")) # True
print(bool([1])) # True

2.7 Type Conversion


# Explicit conversion (casting)
print(int("42")) # 42
print(float("3.14")) # 3.14
print(str(100)) # "100"
print(bool(1)) # True
print(list("abc")) # ["a", "b", "c"]

# Implicit conversion
result = 5 + 2.0 # int + float -> float
print(result) # 7.0
print(type(result)) # <class "float">

# int() truncates (does NOT round)


print(int(3.9)) # 3
print(int(-3.9)) # -3

Key Insight
Python uses duck typing: "If it looks like a duck and quacks like a duck, it is a duck." You care about
what an object CAN DO, not what TYPE it is.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 3 — Operators and Expressions


Operators are symbols that perform operations on values (operands). Python has a rich set of
operators covering arithmetic, comparison, logic, bitwise, and more.

3.1 Arithmetic Operators


Operator Name Example
+ Addition 5+3 = 8
- Subtraction 5-3 = 2
* Multiplication 5 * 3 = 15
/ Division 5 / 2 = 2.5 (always float)
// Floor Division 5 // 2 = 2 (integer result)
% Modulus 5 % 2 = 1 (remainder)
** Exponentiation 2 ** 8 = 256

a, b = 17, 5
print(a + b) # 22
print(a - b) # 12
print(a * b) # 85
print(a / b) # 3.4
print(a // b) # 3 <- floor division
print(a % b) # 2 <- remainder
print(a ** b) # 1419857

# Common pattern: check even/odd


for n in range(1, 11):
if n % 2 == 0:
print(f"{n} is even")
else:
print(f"{n} is odd")

3.2 Comparison Operators


Comparison operators always return a boolean (True or False).
x, y = 10, 20
print(x == y) # False (equal)
print(x != y) # True (not equal)
print(x < y) # True (less than)
print(x > y) # False (greater than)
print(x <= y) # True (less than or equal)
print(x >= y) # False (greater than or equal)

# Chain comparisons (unique to Python!)


age = 25
print(18 <= age < 65) # True — adult working age

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

# String comparison (lexicographic)


print("apple" < "banana") # True
print("Python" == "python") # False (case sensitive)

3.3 Logical Operators


Operator Description
and True if BOTH operands are True
or True if AT LEAST ONE operand is True
not Inverts the boolean value

age = 25
has_id = True

# and — both must be true


if age >= 18 and has_id:
print("Entry allowed")

# or — at least one must be true


is_admin = False
is_superuser = True
if is_admin or is_superuser:
print("Access granted")

# not — flip the value


is_closed = False
if not is_closed:
print("Shop is open")

# Short-circuit evaluation
# Python stops evaluating as soon as the result is known
x = None
if x is not None and x > 0: # safe — checks None first
print(x)

3.4 Assignment Operators


n = 10
n += 5 # n = n + 5 -> 15
n -= 3 # n = n - 3 -> 12
n *= 2 # n = n * 2 -> 24
n /= 4 # n = n / 4 -> 6.0
n //= 2 # n = n // 2 -> 3.0
n **= 3 # n = n ** 3 -> 27.0
n %= 5 # n = n % 5 -> 2.0

# Walrus operator := (Python 3.8+)


# Assigns AND returns the value in one expression
import re

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

data = "User: Alice, Age: 30"


if m := [Link](r"Age: (\d+)", data):
print(f"Found age: {[Link](1)}") # Found age: 30

3.5 Bitwise Operators


Operator Name Example
& AND 0b1010 & 0b1100 = 0b1000 (8)
| OR 0b1010 | 0b1100 = 0b1110 (14)
^ XOR 0b1010 ^ 0b1100 = 0b0110 (6)
~ NOT ~5 = -6
<< Left shift 1 << 3 = 8 (multiply by 2^3)
>> Right shift 16 >> 2 = 4 (divide by 2^2)

3.6 Operator Precedence


Python evaluates operators in a specific order — just like BODMAS in maths. Use parentheses to be
explicit.
# Precedence: ** > unary > * / // % > + - > comparisons > logical
print(2 + 3 * 4) # 14 (not 20!) — * before +
print((2 + 3) * 4) # 20 — parentheses first
print(2 ** 3 ** 2) # 512 — ** is right-associative: 2**(3**2)=2**9
print(True or False and False) # True — and before or

Remember
When in doubt, use parentheses. They make intent clear and prevent bugs. Readable code is better
than "clever" code.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 4 — Control Flow


Control flow determines the order in which code executes. Python provides if/elif/else for branching,
and for/while loops for repetition.

4.1 if / elif / else


score = 78

if score >= 90:


grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
else:
grade = "F"

print(f"Score: {score}, Grade: {grade}") # Score: 78, Grade: C

Ternary Expression (one-liner if)


# Syntax: value_if_true if condition else value_if_false
age = 20
status = "adult" if age >= 18 else "minor"
print(status) # adult

# Nested ternary (use sparingly — readability suffers)


x = 5
label = "positive" if x > 0 else "negative" if x < 0 else "zero"
print(label) # positive

4.2 for Loops


The for loop iterates over any iterable: lists, strings, ranges, dicts, files, and more.
# Iterate a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# apple
# banana
# cherry

# range(stop) — 0 to stop-1
for i in range(5):
print(i, end=" ") # 0 1 2 3 4

# range(start, stop, step)


for i in range(1, 11, 2):

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

print(i, end=" ") # 1 3 5 7 9

# Iterate a string
for char in "Python":
print(char, end="-") # P-y-t-h-o-n-

enumerate() — index + value together


languages = ["Python", "JavaScript", "Go", "Rust"]
for index, lang in enumerate(languages):
print(f"{index}: {lang}")
# 0: Python
# 1: JavaScript
# 2: Go
# 3: Rust

# Start counting from 1


for i, lang in enumerate(languages, start=1):
print(f"{i}. {lang}")

zip() — iterate multiple iterables in parallel


names = ["Alice", "Bob", "Carol"]
scores = [95, 87, 92]
grades = ["A", "B", "A"]

for name, score, grade in zip(names, scores, grades):


print(f"{name}: {score} ({grade})")
# Alice: 95 (A)
# Bob: 87 (B)
# Carol: 92 (A)

4.3 while Loops


The while loop runs as long as a condition remains True. Always ensure the condition eventually
becomes False to avoid infinite loops.
# Basic while loop
count = 0
while count < 5:
print(count, end=" ") # 0 1 2 3 4
count += 1

# do-while pattern (Python has no do-while keyword)


user_input = ""
while True:
user_input = input("Enter a positive number: ")
if user_input.isdigit() and int(user_input) > 0:
break
print("Invalid input, try again.")

# Fibonacci sequence with while


a, b = 0, 1
while a < 100:

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

print(a, end=" ") # 0 1 1 2 3 5 8 13 21 34 55 89


a, b = b, a + b

4.4 break, continue, pass, else


# break — exits the loop immediately
for n in range(10):
if n == 5:
break
print(n, end=" ") # 0 1 2 3 4

# continue — skips the rest of this iteration


for n in range(10):
if n % 2 == 0:
continue
print(n, end=" ") # 1 3 5 7 9

# pass — does nothing; placeholder for empty blocks


for n in range(5):
pass # will add logic later

# else on a for loop — runs if loop completed WITHOUT break


for n in range(2, 10):
for factor in range(2, n):
if n % factor == 0:
break
else:
print(f"{n} is prime", end=" ") # 2 3 5 7

4.5 match / case (Python 3.10+)


The match statement is Python's structural pattern matching — like switch/case but far more powerful.
command = "quit"

match command:
case "quit" | "exit":
print("Goodbye!")
case "help":
print("Available commands: quit, help, info")
case "info":
print("Python 3.10 pattern matching")
case _:
print(f"Unknown command: {command}")

# Match with data structures


point = (1, 0)
match point:
case (0, 0): print("Origin")
case (x, 0): print(f"On X-axis at {x}")
case (0, y): print(f"On Y-axis at {y}")
case (x, y): print(f"Point at ({x}, {y})")

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 5 — Functions
Functions are reusable blocks of code that perform a specific task. They are the cornerstone of clean,
modular, DRY (Don't Repeat Yourself) Python code.

5.1 Defining and Calling Functions


# function_basics.py
# def keyword, function name, parameters, colon
def greet(name):
"""Return a greeting message."""
return f"Hello, {name}!"

# Call the function


message = greet("Alice")
print(message) # Hello, Alice!

# Function with no return value returns None


def print_separator(char="-", length=40):
print(char * length)

print_separator() # ----------------------------------------
print_separator("=", 20) # ====================

5.2 Parameters and Arguments


Python supports multiple types of arguments, giving you enormous flexibility in how you call functions.
# 1. Positional arguments — order matters
def power(base, exponent):
return base ** exponent

print(power(2, 8)) # 256

# 2. Keyword arguments — order does not matter


print(power(exponent=3, base=5)) # 125

# 3. Default parameter values


def connect(host, port=5432, ssl=True):
print(f"Connecting to {host}:{port} (SSL={ssl})")

connect("localhost") # port=5432, ssl=True


connect("[Link]", 3306, False) # override all

# 4. *args — variable positional arguments (tuple)


def add(*numbers):
return sum(numbers)

print(add(1, 2)) # 3
print(add(1, 2, 3, 4, 5)) # 15

# 5. **kwargs — variable keyword arguments (dict)

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

def create_user(**info):
for key, value in [Link]():
print(f" {key}: {value}")

create_user(name="Alice", age=30, role="admin")


# name: Alice
# age: 30
# role: admin

# 6. Combined — positional, *args, keyword-only, **kwargs


def demo(a, b, *args, verbose=False, **kwargs):
print(a, b, args, verbose, kwargs)

demo(1, 2, 3, 4, verbose=True, x=10)


# 1 2 (3, 4) True {"x": 10}

5.3 Return Values


# Return a single value
def square(n): return n * n

# Return multiple values (actually a tuple)


def min_max(numbers):
return min(numbers), max(numbers)

lo, hi = min_max([3, 1, 7, 2, 9])


print(lo, hi) # 1 9

# Early return (guard clause)


def divide(a, b):
if b == 0:
return None # early return for invalid input
return a / b

result = divide(10, 0)
if result is None:
print("Cannot divide by zero")

5.4 Docstrings
Docstrings document what a function does. They are accessible via help() and IDE tooltips.
def calculate_bmi(weight_kg, height_m):
"""
Calculate Body Mass Index (BMI).

Args:
weight_kg (float): Weight in kilograms.
height_m (float): Height in metres.

Returns:
float: BMI value rounded to 2 decimal places.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Example:
>>> calculate_bmi(70, 1.75)
22.86
"""
bmi = weight_kg / (height_m ** 2)
return round(bmi, 2)

print(calculate_bmi(70, 1.75)) # 22.86


help(calculate_bmi) # prints the docstring

5.5 Lambda Functions


Lambda functions are anonymous one-liner functions. Best used as short callbacks, not as
replacements for full functions.
# Syntax: lambda parameters: expression
square = lambda x: x ** 2
print(square(5)) # 25

# Most useful as arguments to higher-order functions


numbers = [5, 2, 8, 1, 9, 3]
sorted_nums = sorted(numbers) # [1, 2, 3, 5, 8, 9]
sorted_desc = sorted(numbers, key=lambda x: -x) # [9, 8, 5, 3, 2, 1]

# Sort list of dicts by a field


people = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Carol", "age": 35},
]
by_age = sorted(people, key=lambda p: p["age"])
for p in by_age:
print(p["name"], p["age"])
# Bob 25 / Alice 30 / Carol 35

5.6 Scope: LEGB Rule


Python resolves names using the LEGB rule: Local → Enclosing → Global → Built-in.
x = "global" # Global scope

def outer():
x = "enclosing" # Enclosing scope

def inner():
x = "local" # Local scope
print(x) # local

inner()
print(x) # enclosing

outer()
print(x) # global

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

# global keyword — modify global variable inside function


counter = 0
def increment():
global counter
counter += 1

increment()
increment()
print(counter) # 2

# nonlocal — modify enclosing scope variable


def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment

c = make_counter()
print(c(), c(), c()) # 1 2 3

5.7 Closures and Decorators


Closures
def multiplier(factor):
"""Returns a function that multiplies by factor."""
def multiply(n):
return n * factor # factor is "closed over"
return multiply

double = multiplier(2)
triple = multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15

Decorators
A decorator wraps a function to add behaviour before/after it runs, without modifying the original code.
import time

def timer(func):
"""Decorator: measures execution time."""
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

@timer
def slow_sum(n):
return sum(range(n))

slow_sum(1_000_000) # slow_sum took 0.0312s

# Built-in decorators
class Circle:
def __init__(self, radius):
[Link] = radius

@property
def area(self):
import math
return [Link] * [Link] ** 2

@staticmethod
def describe():
return "I am a circle"

c = Circle(5)
print([Link]) # 78.539...
print([Link]()) # I am a circle

DRY Principle
If you write the same logic more than twice, move it into a function. Functions make code testable,
reusable, and readable. A good function does ONE thing and does it well.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 6 — Data Structures


Python has four essential built-in data structures: List, Tuple, Set, and Dictionary. Choosing the right
one has a huge impact on performance and code clarity.

6.1 Lists
Lists are ordered, mutable sequences. They can hold any mix of types and support indexing, slicing,
and many useful methods.
# [Link]
# Creating lists
empty = []
numbers = [1, 2, 3, 4, 5]
mixed = [42, "hello", True, 3.14, None]
nested = [[1, 2], [3, 4], [5, 6]]

# Access and slicing


print(numbers[0]) # 1 (first)
print(numbers[-1]) # 5 (last)
print(numbers[1:4]) # [2, 3, 4]
print(numbers[::2]) # [1, 3, 5] (every 2nd)
print(numbers[::-1]) # [5, 4, 3, 2, 1] (reversed)

# Mutability — lists can be changed


numbers[0] = 10
print(numbers) # [10, 2, 3, 4, 5]

List Methods
lst = [3, 1, 4, 1, 5, 9, 2, 6]

[Link](7) # Add to end: [3,1,4,1,5,9,2,6,7]


[Link](0, 0) # Insert at index: [0,3,1,4,1,5,9,2,6,7]
[Link]([8, 9]) # Add multiple: [..., 8, 9]
[Link](1) # Remove first occurrence of 1
popped = [Link]() # Remove & return last: 9
popped = [Link](0) # Remove & return at index 0
[Link]() # Sort in-place
[Link](reverse=True) # Sort descending
[Link]() # Reverse in-place
idx = [Link](5) # Find index of 5
cnt = [Link](1) # Count occurrences of 1
lst2 = [Link]() # Shallow copy
[Link]() # Empty the list

# Useful built-in functions


nums = [4, 2, 9, 1, 7]
print(len(nums)) # 5
print(sum(nums)) # 23
print(min(nums)) # 1
print(max(nums)) # 9
print(sorted(nums)) # [1, 2, 4, 7, 9] (returns new list)

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

List Comprehensions
List comprehensions are the Pythonic way to create lists. They are faster and more readable than for
loops.
# Syntax: [expression for item in iterable if condition]

# Basic
squares = [x**2 for x in range(1, 11)]
print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# With filter
evens = [x for x in range(20) if x % 2 == 0]
print(evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

# String manipulation
words = ["hello", "world", "python"]
upper = [[Link]() for w in words]
print(upper) # ["HELLO", "WORLD", "PYTHON"]

# Nested comprehension (flatten matrix)


matrix = [[1,2,3],[4,5,6],[7,8,9]]
flat = [n for row in matrix for n in row]
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]

6.2 Tuples
Tuples are ordered, immutable sequences. Use them for data that should not change: coordinates,
RGB colours, database rows, function return values.
# Creating tuples
point = (3, 7)
rgb = (255, 128, 0)
single = (42,) # trailing comma needed for single-element tuple
empty = ()

# Parentheses are optional (packing)


coords = 10, 20, 30
print(type(coords)) # <class "tuple">

# Unpacking
x, y = point
r, g, b = rgb
print(f"x={x}, y={y}") # x=3, y=7

# Extended unpacking
first, *rest = (1, 2, 3, 4, 5)
print(first) # 1
print(rest) # [2, 3, 4, 5]

# Swap values elegantly


a, b = 5, 10
a, b = b, a
print(a, b) # 10 5

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

# Named tuples — readable tuple with field names


from collections import namedtuple
Person = namedtuple("Person", ["name", "age", "role"])
alice = Person("Alice", 30, "DevOps Engineer")
print([Link]) # Alice
print([Link]) # 30

6.3 Sets
Sets are unordered collections of unique elements. Perfect for membership testing, removing
duplicates, and mathematical set operations.
# Creating sets
fruits = {"apple", "banana", "cherry", "apple"}
print(fruits) # {"apple", "banana", "cherry"} — no duplicate!

# From a list (removes duplicates)


lst = [1, 2, 2, 3, 3, 3, 4]
unique = set(lst)
print(unique) # {1, 2, 3, 4}

# Set operations
A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7, 8}

print(A | B) # Union: {1,2,3,4,5,6,7,8}


print(A & B) # Intersection: {4, 5}
print(A - B) # Difference: {1, 2, 3}
print(A ^ B) # Symmetric: {1,2,3,6,7,8}

# Membership testing (O(1) — very fast!)


print("apple" in fruits) # True
print("mango" not in fruits) # True

# Set methods
[Link]("mango")
[Link]("banana") # no error if not present
[Link]("cherry") # raises KeyError if not present

6.4 Dictionaries
Dictionaries store key-value pairs. In Python 3.7+ they maintain insertion order. Keys must be hashable
(strings, numbers, tuples).
# Creating dictionaries
person = {"name": "Alice", "age": 30, "role": "DevOps"}
empty = {}

# Access
print(person["name"]) # Alice
print([Link]("salary", 0)) # 0 (default if missing)

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

# Modify
person["age"] = 31 # update existing
person["city"] = "Mumbai" # add new key
del person["role"] # delete a key

# Iteration
for key in person:
print(key)

for value in [Link]():


print(value)

for key, value in [Link]():


print(f"{key}: {value}")

# Dictionary methods
keys = list([Link]()) # ["name", "age", "city"]
values = list([Link]()) # ["Alice", 31, "Mumbai"]
popped = [Link]("city") # removes and returns "Mumbai"
[Link]({"role": "SRE", "age": 32}) # merge/update

# dict comprehension
squares = {x: x**2 for x in range(1, 6)}
print(squares) # {1:1, 2:4, 3:9, 4:16, 5:25}

# Nested dictionaries
users = {
"alice": {"age": 30, "role": "admin"},
"bob": {"age": 25, "role": "user"},
}
print(users["alice"]["role"]) # admin

6.5 Choosing the Right Data Structure


Use Case Best Structure
Ordered, changeable list
collection
Fixed, ordered data (coords, tuple
records)
Unique elements, fast set
membership test
Key-value mapping, fast dict
lookup by key
Priority queue, heap heapq module
Thread-safe queue [Link]
Ordered dict with fast appends [Link]
both ends
Dict with default values [Link]

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Counting occurrences [Link]

# collections_examples.py
from collections import Counter, defaultdict, deque

# Counter — count hashable objects


words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
count = Counter(words)
print(count) # Counter({"apple":3,"banana":2,"cherry":1})
print(count.most_common(2)) # [("apple",3),("banana",2)]

# defaultdict — no KeyError on missing keys


groups = defaultdict(list)
data = [("A", 1), ("B", 2), ("A", 3), ("B", 4)]
for key, val in data:
groups[key].append(val)
print(dict(groups)) # {"A": [1, 3], "B": [2, 4]}

# deque — fast appends/pops from both ends


dq = deque([1, 2, 3])
[Link](0) # [0, 1, 2, 3]
[Link](4) # [0, 1, 2, 3, 4]
[Link]() # returns 0
print(dq) # deque([1, 2, 3, 4])

Performance Tip
Membership testing: "x in set" is O(1) — instant. "x in list" is O(n) — scans every element. For large
collections, always use a set or dict for lookups.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 7 — Object-Oriented Programming


OOP organises code into objects — bundles of data (attributes) and behaviour (methods). Python
supports full OOP with classes, inheritance, polymorphism, and encapsulation.

7.1 Classes and Objects


# oop_basics.py
class Dog:
"""Represents a dog."""

# Class attribute — shared by all instances


species = "Canis lupus familiaris"

# __init__ is the constructor


def __init__(self, name, age, breed):
# Instance attributes — unique to each object
[Link] = name
[Link] = age
[Link] = breed

# Instance method
def bark(self):
return f"{[Link]} says: Woof!"

def describe(self):
return f"{[Link]} ({[Link]}), age {[Link]}"

# __str__ — human-readable representation


def __str__(self):
return [Link]()

# __repr__ — unambiguous representation for debugging


def __repr__(self):
return f"Dog(name={[Link]!r}, age={[Link]})"

# Create instances
rex = Dog("Rex", 3, "German Shepherd")
buddy = Dog("Buddy", 5, "Golden Retriever")

print([Link]()) # Rex says: Woof!


print(buddy) # Buddy (Golden Retriever), age 5
print(repr(rex)) # Dog(name="Rex", age=3)
print([Link]) # Canis lupus familiaris

7.2 Inheritance
Inheritance allows a child class to reuse and extend the behaviour of a parent class.
class Animal:
def __init__(self, name, sound):

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

[Link] = name
[Link] = sound

def speak(self):
return f"{[Link]} says {[Link]}"

class Cat(Animal):
def __init__(self, name, indoor=True):
super().__init__(name, "Meow") # call parent __init__
[Link] = indoor

def purr(self):
return f"{[Link]} purrs..."

class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name, "Woof")
[Link] = breed

# Override parent method


def speak(self):
return f"{[Link]} ({[Link]}) barks: {[Link]}!"

cat = Cat("Whiskers")
dog = Dog("Rex", "Husky")

print([Link]()) # Whiskers says Meow


print([Link]()) # Rex (Husky) barks: Woof!
print([Link]()) # Whiskers purrs...

# isinstance — check object type


print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True (Dog is an Animal)
print(isinstance(cat, Dog)) # False

7.3 Encapsulation and Properties


class BankAccount:
def __init__(self, owner, initial_balance=0):
[Link] = owner
self._balance = initial_balance # _single = convention: "protected"
self.__pin = "0000" # __double = name-mangled "private"

@property
def balance(self):
"""Read-only property."""
return self._balance

def deposit(self, amount):


if amount <= 0:

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

raise ValueError("Deposit must be positive")


self._balance += amount
return self._balance

def withdraw(self, amount):


if amount > self._balance:
raise ValueError("Insufficient funds")
self._balance -= amount
return self._balance

acc = BankAccount("Alice", 1000)


[Link](500)
[Link](200)
print([Link]) # 1300
# acc._balance = -9999 # possible but bad practice
# acc.__pin = "1234" # AttributeError — truly private

7.4 Special (Dunder) Methods


Dunder methods let your objects work with Python's built-in operations like +, len(), [], in, and print().
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __str__(self):
return f"Vector({self.x}, {self.y})"

def __repr__(self):
return f"Vector(x={self.x}, y={self.y})"

def __add__(self, other):


return Vector(self.x + other.x, self.y + other.y)

def __mul__(self, scalar):


return Vector(self.x * scalar, self.y * scalar)

def __len__(self):
return 2

def __eq__(self, other):


return self.x == other.x and self.y == other.y

def magnitude(self):
return (self.x**2 + self.y**2) ** 0.5

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 * 3) # Vector(3, 6)
print(len(v1)) # 2

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

print(v1 == Vector(1, 2)) # True


print([Link]()) # 5.0

7.5 Dataclasses (Python 3.7+)


Dataclasses auto-generate __init__, __repr__, __eq__, and more — cutting boilerplate significantly.
from dataclasses import dataclass, field

@dataclass
class Employee:
name: str
department: str
salary: float
skills: list = field(default_factory=list)

def annual_salary(self):
return [Link] * 12

emp = Employee("Alice", "DevOps", 8000.0, ["Python", "AWS"])


print(emp) # Employee(name="Alice", ...)
print(emp.annual_salary()) # 96000.0

# Comparison works out of the box


emp2 = Employee("Alice", "DevOps", 8000.0, ["Python", "AWS"])
print(emp == emp2) # True

OOP Principle
SOLID: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency
Inversion. Start with small, focused classes. Refactor when a class grows beyond one responsibility.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 8 — File Input / Output


Python makes file reading and writing straightforward. Always use the "with" statement — it
automatically closes the file even if an exception occurs.

8.1 Reading Files


# file_read.py
# Read entire file as a string
with open("[Link]", "r") as f:
content = [Link]()
print(content)

# Read line by line (memory efficient for large files)


with open("[Link]", "r") as f:
for line in f:
print([Link]()) # strip removes trailing newline

# Read all lines into a list


with open("[Link]", "r") as f:
lines = [Link]() # ["line1\n", "line2\n", ...]

# Read with specific encoding


with open("[Link]", "r", encoding="utf-8") as f:
text = [Link]()

8.2 Writing Files


# Write (overwrites existing file)
with open("[Link]", "w") as f:
[Link]("Hello, File!\n")
[Link]("Second line\n")

# Append (does not overwrite)


with open("[Link]", "a") as f:
[Link]("2024-01-15 10:30: Server started\n")

# Write multiple lines at once


lines = ["Alice\n", "Bob\n", "Carol\n"]
with open("[Link]", "w") as f:
[Link](lines)

# Write with print()


with open("[Link]", "w") as f:
print("Section 1", file=f)
print(f"Generated on: 2024-01-15", file=f)

8.3 File Modes


Mode Meaning

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

"r" Read (default). Error if file not found.


"w" Write. Creates file; OVERWRITES if exists.
"a" Append. Creates file; adds to end if exists.
"x" Exclusive create. Error if file exists.
"rb" Read binary (images, PDFs, etc.).
"wb" Write binary.
"r+" Read and write.

8.4 CSV Files


import csv

# Write CSV
employees = [
["Name", "Department", "Salary"],
["Alice", "DevOps", 90000],
["Bob", "Backend", 85000],
["Carol", "Frontend", 80000],
]

with open("[Link]", "w", newline="") as f:


writer = [Link](f)
[Link](employees)

# Read CSV
with open("[Link]", "r") as f:
reader = [Link](f)
for row in reader:
print(f"{row['Name']:10} | {row['Department']:10} | {row['Salary']}")
# Alice | DevOps | 90000
# Bob | Backend | 85000

8.5 JSON Files


import json

# Python dict -> JSON file


config = {
"database": {"host": "localhost", "port": 5432},
"debug": True,
"allowed_hosts": ["[Link]", "[Link]"],
}

with open("[Link]", "w") as f:


[Link](config, f, indent=2)

# JSON file -> Python dict


with open("[Link]", "r") as f:

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

loaded = [Link](f)

print(loaded["database"]["port"]) # 5432

# String conversion
json_str = [Link](config) # dict -> string
data = [Link](json_str) # string -> dict

8.6 pathlib — Modern Path Handling


from pathlib import Path

# Create Path objects


p = Path("data/reports")

# Create directory tree


[Link](parents=True, exist_ok=True)

# File operations
file = p / "[Link]" # Path joining with / ← Pythonic!
file.write_text("Hello!") # Write text
content = file.read_text() # Read text

# Path info
print([Link]) # [Link]
print([Link]) # report
print([Link]) # .txt
print([Link]) # data/reports
print([Link]()) # True

# Iterate directory
for f in Path(".").glob("*.py"):
print([Link])

# Find all Python files recursively


for f in Path(".").rglob("*.py"):
print(f)

Always Use with


The with statement (context manager) ensures files are closed properly. Never use f = open(...)
without a matching [Link]() — if an exception occurs, the file may never be closed, causing data
corruption or resource leaks.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 9 — Exception Handling


Exceptions are runtime errors that interrupt normal program flow. Python's exception handling lets you
catch, handle, and recover from errors gracefully.

9.1 try / except / else / finally


# [Link]
# Basic structure
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero!")

# Catch specific exception and get message


try:
number = int("not_a_number")
except ValueError as e:
print(f"ValueError: {e}")

# Catch multiple exception types


try:
data = {"a": 1}
x = data["b"] # KeyError
y = int(x) # ValueError
except (KeyError, ValueError) as e:
print(f"Data error: {e}")

# else — runs if NO exception occurred


# finally — runs ALWAYS (cleanup)
try:
file = open("[Link]")
content = [Link]()
except FileNotFoundError:
print("File not found")
else:
print("File read successfully")
print(content)
finally:
print("This always runs")

9.2 Common Built-in Exceptions


Exception When It Occurs
ValueError Wrong value type: int("hello")
TypeError Wrong data type: "2" + 2
KeyError Dict key not found: d["missing"]
IndexError List index out of range: lst[100]
AttributeError Attribute not found: [Link]()

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

FileNotFoundError File does not exist


ZeroDivisionError Division or modulo by zero
ImportError Module not found
PermissionError No permission for file/resource
RecursionError Maximum recursion depth exceeded
StopIteration Iterator is exhausted
OSError System-level error (I/O, process)

9.3 Raising Exceptions


# Raise a built-in exception
def set_age(age):
if not isinstance(age, int):
raise TypeError(f"Age must be int, got {type(age).__name__}")
if age < 0 or age > 150:
raise ValueError(f"Age {age} is out of valid range 0-150")
return age

# Re-raise inside except


def load_config(path):
try:
with open(path) as f:
import json
return [Link](f)
except FileNotFoundError:
raise # re-raise the same exception
except [Link] as e:
raise ValueError(f"Invalid config: {e}") from e

9.4 Custom Exceptions


Define your own exception classes for domain-specific errors. This makes errors more descriptive and
catchable.
class AppError(Exception):
"""Base class for all application errors."""
pass

class AuthenticationError(AppError):
def __init__(self, username):
[Link] = username
super().__init__(f"Authentication failed for user: {username}")

class InsufficientBalanceError(AppError):
def __init__(self, available, required):
super().__init__(
f"Need ${required:.2f}, only ${available:.2f} available"
)
[Link] = available

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

[Link] = required

def withdraw(account, amount):


if account["balance"] < amount:
raise InsufficientBalanceError(account["balance"], amount)
account["balance"] -= amount

try:
acc = {"balance": 100.0}
withdraw(acc, 200.0)
except InsufficientBalanceError as e:
print(e) # Need $200.00, only $100.00 available

9.5 Context Managers


Context managers handle setup/teardown patterns (open, close; acquire lock, release lock). Build your
own with __enter__ / __exit__ or @contextmanager.
from contextlib import contextmanager

@contextmanager
def managed_db_connection(db_url):
"""Context manager for database connections."""
print(f"Connecting to {db_url}")
connection = {"url": db_url, "open": True} # simulated
try:
yield connection
except Exception as e:
print(f"Rolling back transaction: {e}")
raise
finally:
connection["open"] = False
print("Connection closed")

with managed_db_connection("postgresql://localhost/mydb") as conn:


print(f"Using connection: {conn}")
# do database work here
# Connection closed — automatically!

Best Practice
Be specific in except clauses. Never use bare "except:" — it catches everything including
KeyboardInterrupt and SystemExit. Always catch the most specific exception you can handle.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 10 — Modules, Packages and the Standard


Library
A module is a .py file containing Python code. A package is a directory of modules. The standard library
ships hundreds of modules with Python.

10.1 Importing Modules


# Import entire module
import math
print([Link](144)) # 12.0
print([Link]) # 3.141592653589793

# Import specific names


from math import sqrt, pi, floor, ceil
print(sqrt(81)) # 9.0

# Import with alias


import numpy as np # common convention
import pandas as pd

# Import all (avoid in production — pollutes namespace)


from math import *

# Conditional import
try:
import ujson as json # faster alternative
except ImportError:
import json # fallback to standard

10.2 Creating Your Own Module


# [Link]
"""Utility functions for DevOps Shack projects."""

DEFAULT_TIMEOUT = 30

def slugify(text):
"""Convert text to URL-friendly slug."""
return [Link]().strip().replace(" ", "-")

def chunk_list(lst, size):


"""Split list into chunks of given size."""
return [lst[i:i+size] for i in range(0, len(lst), size)]

# Only runs when this file is executed directly, not imported


if __name__ == "__main__":
print(slugify("Hello World")) # hello-world
print(chunk_list([1,2,3,4,5], 2)) # [[1,2],[3,4],[5]]

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

# [Link]
from utils import slugify, chunk_list

title = "My DevOps Blog Post"


url = slugify(title)
print(url) # my-devops-blog-post

10.3 Key Standard Library Modules


Module Purpose Key Items
os OS interaction [Link](), [Link](), [Link]
sys Python runtime [Link], [Link], [Link]()
pathlib File paths Path(), mkdir(), glob(), read_text()
datetime Dates and times [Link](), timedelta, strftime()
re Regular [Link](), [Link](), [Link]()
expressions
json JSON [Link](), [Link]()
encode/decode
csv CSV files [Link](), [Link](), DictReader
collections Specialised Counter, defaultdict, deque, OrderedDict
containers
itertools Iterator functions chain, product, combinations, groupby
functools Higher-order lru_cache, partial, reduce, wraps
functions
threading Threads Thread, Lock, Event
subprocess Run shell [Link](), Popen
commands
argparse CLI argument ArgumentParser, add_argument()
parsing
logging Logging framework [Link](), basicConfig()
hashlib Cryptographic md5(), sha256()
hashes
[Link] Simple HTTP HTTPServer, SimpleHTTPRequestHandler
server

# stdlib_examples.py
import os, sys, re
from datetime import datetime, timedelta

# os — environment and files


print([Link]()) # current directory
print([Link]("HOME", "/tmp")) # env variable

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

# datetime
now = [Link]()
print([Link]("%Y-%m-%d %H:%M")) # 2024-01-15 14:30
tomorrow = now + timedelta(days=1)
print([Link]()) # 2024-01-16

# re — regular expressions
emails = "Contact us: alice@[Link] or bob@[Link]"
found = [Link](r"[\w.+-]+@[\w-]+\.[\w.]+", emails)
print(found) # ["alice@[Link]", "bob@[Link]"]

# sys — CLI args


# python [Link] arg1 arg2
script_name = [Link][0]
args = [Link][1:]

10.4 Virtual Environments and pip


# Create a virtual environment
python -m venv venv

# Activate it
source venv/bin/activate # macOS / Linux
venv\Scripts\activate # Windows

# Install packages
pip install requests pandas flask

# Save dependencies
pip freeze > [Link]

# Install from requirements


pip install -r [Link]

# Deactivate
deactivate

Best Practice
Always use a virtual environment for every project. Never install packages globally. Use
[Link] (or [Link] with Poetry/uv) to make your project reproducible on any machine.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 11 — Advanced Python: Generators,


Iterators & Comprehensions
These features are what separate good Python code from great Python code — they enable memory-
efficient, elegant, and highly readable solutions.

11.1 Generators
A generator is a function that yields values one at a time, on demand. Unlike lists, generators don't
store all values in memory — they compute each value lazily.
# [Link]
# Regular function — builds entire list in memory
def squares_list(n):
return [x**2 for x in range(n)]

# Generator function — computes on demand


def squares_gen(n):
for x in range(n):
yield x**2 # yield pauses and returns value

# Usage looks identical!


for sq in squares_gen(5):
print(sq, end=" ") # 0 1 4 9 16

# Memory difference for large n:


# squares_list(10_000_000) uses ~80 MB
# squares_gen(10_000_000) uses ~200 bytes!

# Generator expression (like list comp but with ())


gen = (x**2 for x in range(1_000_000))
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 4

# Infinite generator
def counter(start=0, step=1):
current = start
while True:
yield current
current += step

c = counter(10, 5)
print([next(c) for _ in range(5)]) # [10, 15, 20, 25, 30]

# Pipeline using generators (very memory efficient)


def read_lines(filename):
with open(filename) as f:
for line in f:
yield [Link]()

def filter_errors(lines):
for line in lines:

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

if "ERROR" in line:
yield line

def parse_timestamp(lines):
for line in lines:
yield line[:19], line[20:] # (timestamp, message)

# Each stage processes one line at a time:


# lines = parse_timestamp(filter_errors(read_lines("[Link]")))

11.2 Iterators
An iterator is any object with __iter__() and __next__() methods. All Python for loops work with
iterators.
# Create a custom iterator
class CountDown:
"""Counts down from n to 0."""
def __init__(self, n):
self.n = n

def __iter__(self):
return self

def __next__(self):
if self.n < 0:
raise StopIteration
value = self.n
self.n -= 1
return value

for num in CountDown(5):


print(num, end=" ") # 5 4 3 2 1 0

# itertools — powerful iterator tools


from itertools import chain, product, combinations, islice

# chain — combine multiple iterables


result = list(chain([1,2], [3,4], [5,6]))
print(result) # [1, 2, 3, 4, 5, 6]

# combinations
items = list(combinations("ABC", 2))
print(items) # [("A","B"), ("A","C"), ("B","C")]

# product — Cartesian product


for x, y in product([1,2], ["a","b"]):
print(x, y)
# 1 a / 1 b / 2 a / 2 b

# islice — lazy slice of any iterator


first_5 = list(islice(squares_gen(1_000_000), 5))
print(first_5) # [0, 1, 4, 9, 16]

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

11.3 All Comprehension Types


# List comprehension
squares = [x**2 for x in range(10)]

# Dict comprehension
word_len = {word: len(word) for word in ["Python", "is", "great"]}
print(word_len) # {"Python": 6, "is": 2, "great": 5}

# Set comprehension
unique_first_chars = {word[0] for word in ["apple","avocado","banana","blueberry"]}
print(unique_first_chars) # {"a", "b"}

# Generator expression
total = sum(x**2 for x in range(1000)) # no list created

# Nested comprehension with condition


matrix = [[1,2,3],[4,5,6],[7,8,9]]
odd_flat = [n for row in matrix for n in row if n % 2 != 0]
print(odd_flat) # [1, 3, 5, 7, 9]

11.4 functools — Functional Tools


from functools import lru_cache, partial, reduce

# lru_cache — memoisation (cache function results)


@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(50)) # 12586269025 — instant with cache!


print(fibonacci.cache_info()) # CacheInfo(hits=48, misses=51, ...)

# partial — fix some arguments of a function


from functools import partial
power = lambda base, exp: base ** exp
square = partial(power, exp=2)
cube = partial(power, exp=3)
print(square(5)) # 25
print(cube(3)) # 27

# reduce — accumulate a sequence to a single value


numbers = [1, 2, 3, 4, 5]
product = reduce(lambda acc, x: acc * x, numbers)
print(product) # 120 (1*2*3*4*5)

# map() and filter()


nums = [1, 2, 3, 4, 5, 6]
doubled = list(map(lambda x: x * 2, nums))
evens = list(filter(lambda x: x % 2 == 0, nums))

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

print(doubled) # [2, 4, 6, 8, 10, 12]


print(evens) # [2, 4, 6]

Performance Rule
Use generators when processing large datasets or streaming data — they use O(1) memory
regardless of input size. Use list comprehensions when you need the full list. Use dict/set
comprehensions to replace verbose loops.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 12 — Concurrency: Threads, Processes


and Async
Python offers three concurrency models. Choosing the right one depends on whether your bottleneck is
I/O (network, files) or CPU (computation).

Model Best For Module


Threading I/O-bound tasks: web requests, file threading
reads
Multiprocess CPU-bound tasks: number multiprocessing
crunching, images
Async/Await High-concurrency I/O: thousands of asyncio
requests

12.1 Threading
# threading_example.py
import threading, time

def download_file(url, result_list, index):


print(f" Starting: {url}")
[Link](1) # simulate network I/O
result_list[index] = f"Downloaded: {url}"
print(f" Done: {url}")

urls = [
"[Link]
"[Link]
"[Link]
]

results = [None] * len(urls)


threads = []

start = time.perf_counter()

for i, url in enumerate(urls):


t = [Link](target=download_file, args=(url, results, i))
[Link](t)
[Link]()

for t in threads:
[Link]() # wait for all threads to finish

elapsed = time.perf_counter() - start


print(f"All downloads done in {elapsed:.2f}s") # ~1.0s (not 3.0s!)

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

# Thread safety with Lock


counter = 0
lock = [Link]()

def safe_increment():
global counter
with lock:
counter += 1

12.2 Multiprocessing
from multiprocessing import Pool, cpu_count

def cpu_heavy_task(n):
"""Compute sum of squares — CPU intensive."""
return sum(x**2 for x in range(n))

if __name__ == "__main__":
tasks = [5_000_000] * 8

# Sequential
start = time.perf_counter()
results = [cpu_heavy_task(n) for n in tasks]
seq_time = time.perf_counter() - start

# Parallel (uses all CPU cores)


start = time.perf_counter()
with Pool(processes=cpu_count()) as pool:
results = [Link](cpu_heavy_task, tasks)
par_time = time.perf_counter() - start

print(f"Sequential: {seq_time:.2f}s")
print(f"Parallel: {par_time:.2f}s")
print(f"Speedup: {seq_time/par_time:.1f}x")

12.3 Async / Await (asyncio)


asyncio enables cooperative multitasking. Tasks voluntarily yield control at await points, allowing the
event loop to run other tasks. Ideal for high-concurrency I/O.
import asyncio, aiohttp

async def fetch_url(session, url):


"""Fetch a URL asynchronously."""
async with [Link](url) as response:
text = await [Link]()
return len(text)

async def main():


urls = [

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

"[Link]
"[Link]
"[Link]
]

async with [Link]() as session:


# Create all tasks at once
tasks = [fetch_url(session, url) for url in urls]
# Run all concurrently
sizes = await [Link](*tasks)

for url, size in zip(urls, sizes):


print(f"{url}: {size} bytes")

# Run the event loop


[Link](main()) # All 3 requests complete in ~1s, not 3s!

GIL Note
The Global Interpreter Lock (GIL) means only one thread runs Python bytecode at a time. Threading
still helps for I/O-bound tasks (threads release the GIL during I/O). For CPU-bound work, use
multiprocessing or C extensions.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 13 — Testing Your Code


Tests are not optional — they are the foundation of maintainable code. A good test suite lets you
refactor fearlessly and catch bugs before users do.

13.1 unittest — Built-in Testing


# test_calculator.py
import unittest

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


def subtract(a, b): return a - b
def multiply(a, b): return a * b
def divide(a, b):
if b == 0: raise ValueError("Cannot divide by zero")
return a / b

class TestCalculator([Link]):

def test_add(self):
[Link](add(2, 3), 5)
[Link](add(-1, 1), 0)
[Link](add(0, 0), 0)

def test_subtract(self):
[Link](subtract(10, 4), 6)

def test_multiply(self):
[Link](multiply(3, 4), 12)
[Link](multiply(0, 100), 0)

def test_divide(self):
[Link](divide(10, 3), 3.333, places=3)

def test_divide_by_zero(self):
with [Link](ValueError):
divide(5, 0)

def test_types(self):
[Link](add(1, 2), int)
[Link](divide(1, 2), float)

if __name__ == "__main__":
[Link]()

13.2 pytest — The Modern Way


pytest is the industry standard. Less boilerplate, better output, powerful fixtures and plugins.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

# install: pip install pytest pytest-cov

# test_math.py
import pytest

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

# No class needed!
def test_add_positive():
assert add(2, 3) == 5

def test_add_negative():
assert add(-1, -1) == -2

# Parametrize — run same test with many inputs


@[Link]("a, b, expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
(100, -50, 50),
])
def test_add_parametrized(a, b, expected):
assert add(a, b) == expected

# Fixtures — reusable setup/teardown


@[Link]
def sample_data():
return {"users": ["Alice", "Bob", "Carol"], "count": 3}

def test_user_count(sample_data):
assert sample_data["count"] == len(sample_data["users"])

# Test exceptions
def divide(a, b):
if b == 0: raise ValueError("Cannot divide by zero")
return a / b

def test_divide_by_zero():
with [Link](ValueError, match="Cannot divide by zero"):
divide(10, 0)

# Run tests
pytest # run all tests
pytest test_math.py # specific file
pytest -v # verbose output
pytest -k "add" # only tests with "add" in name
pytest --cov=mymodule # with coverage report
pytest --cov=mymodule --cov-report=html # HTML coverage

13.3 Mocking
from [Link] import Mock, patch, MagicMock

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

# Mock an external API call


def get_user_data(user_id):
import requests
response = [Link](f"[Link]
return [Link]()

def test_get_user_data():
with patch("[Link]") as mock_get:
mock_response = Mock()
mock_response.json.return_value = {"id": 1, "name": "Alice"}
mock_get.return_value = mock_response

result = get_user_data(1)

mock_get.assert_called_once_with("[Link]
assert result["name"] == "Alice"

Test-Driven Development
Write the test BEFORE the code. Red -> Green -> Refactor. TDD forces you to think about API
design first, leads to smaller functions, and gives you 100% coverage automatically.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Chapter 14 — Real-World Python: Web Requests,


CLIs and Best Practices
Putting it all together — patterns and tools used in production Python code every day.

14.1 HTTP Requests with requests


# http_client.py
import requests

# GET request
response = [Link](
"[Link]
timeout=10
)
response.raise_for_status() # raises HTTPError for 4xx/5xx
post = [Link]()
print(f"Title: {post['title']}")

# POST request
new_post = [Link](
"[Link]
json={"title": "DevOps Guide", "body": "content", "userId": 1},
headers={"Content-Type": "application/json"},
timeout=10
)
print(new_post.status_code) # 201

# Session for multiple requests (connection reuse)


with [Link]() as session:
[Link]({"Authorization": "Bearer TOKEN"})
r1 = [Link]("[Link]
r2 = [Link]("[Link]

14.2 CLI Tools with argparse


# deploy_tool.py
import argparse

def main():
parser = [Link](
description="DevOps Shack Deployment Tool"
)

parser.add_argument("environment",
choices=["dev", "staging", "prod"],
help="Target environment")

parser.add_argument("--image",
required=True,
help="Docker image tag to deploy")

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

parser.add_argument("--replicas",
type=int,
default=2,
help="Number of replicas (default: 2)")

parser.add_argument("-v", "--verbose",
action="store_true",
help="Enable verbose output")

args = parser.parse_args()

if [Link]:
print(f"Deploying {[Link]} to {[Link]}")
print(f"Replicas: {[Link]}")

# ... deployment logic here

if __name__ == "__main__":
main()

# Usage:
python deploy_tool.py prod --image myapp:v1.2.3 --replicas 4 -v
# Deploying myapp:v1.2.3 to prod
# Replicas: 4

14.3 Logging
import logging

# Configure logging
[Link](
level=[Link],
format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
handlers=[
[Link](), # console
[Link]("[Link]"), # file
]
)

logger = [Link](__name__)

# Log levels (DEBUG < INFO < WARNING < ERROR < CRITICAL)
[Link]("Detailed debug info — development only")
[Link]("Server started on port 8080")
[Link]("Memory usage at 85%")
[Link]("Failed to connect to database")
[Link]("System out of disk space!")

# Log exceptions with traceback


try:
risky_operation()
except Exception as e:

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

[Link]("Unexpected error in risky_operation")

14.4 Type Hints (Python 3.5+)


Type hints improve readability, enable IDE auto-completion, and allow static analysis tools like mypy to
catch type errors before runtime.
from typing import Optional, List, Dict, Tuple, Union, Any

# Function signatures with type hints


def greet(name: str, times: int = 1) -> str:
return (f"Hello, {name}! " * times).strip()

# Complex types
def process_users(users: List[Dict[str, Any]]) -> List[str]:
return [u["name"] for u in users if [Link]("active")]

# Optional — value or None


def find_user(user_id: int) -> Optional[Dict]:
# returns dict if found, None otherwise
...

# Union — one of several types


def parse_value(val: Union[str, int, float]) -> float:
return float(val)

# Python 3.10+ — use | instead of Union


def parse_v2(val: str | int | float) -> float:
return float(val)

# Dataclass with types


from dataclasses import dataclass

@dataclass
class ServerConfig:
host: str
port: int
ssl: bool = True
timeout: float = 30.0
tags: List[str] = None

14.5 Python Best Practices Summary


Practice Guideline
PEP 8 Follow the official style guide. Use a linter (flake8) and formatter
(black).
Naming snake_case for variables/functions, PascalCase for classes,
UPPER_CASE for constants.
Functions Small and focused — one function, one job. Max ~20 lines as a rule
of thumb.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

Docstrings Every public function/class deserves a docstring. Use Google or


NumPy format.
Type hints Add type hints to all function signatures. Run mypy in CI.
DRY Don't Repeat Yourself. Extract repeated logic into functions or
classes.
EAFP vs LBYL Python prefers "Easier to Ask Forgiveness than Permission":
try/except over if/exists.
Virtual Envs Always use venv. Never install packages globally.
Tests Write tests. Aim for 80%+ coverage. Use pytest.
Magic Numbers Replace literals with named constants: MAX_RETRY = 3, not if
retries > 3.
Logging Use logging, not print(), in production code.
f-strings Use f-strings for formatting. Avoid % formatting and .format() in new
code.
Context Managers Use "with" for file I/O, locks, DB connections — always.
Comprehensions Prefer list/dict/set comprehensions over loops for building collections.
Generators Use generators for large datasets. Memory is precious.

@devopsshack Code. Create. Conquer.


DevOps Shack | Complete Python Ultra Deep Dive Guide [Link]

CONGRATULATIONS!
You have completed the Complete Python Ultra Deep Dive Guide
What you have mastered:
Variables & Types | Operators | Control Flow | Functions | Data Structures
OOP | File I/O | Exception Handling | Modules | Advanced Python
Generators | Concurrency | Testing | Real-World Best Practices

Code is not just what it does -- it's how it does it. -- Robert C. Martin

[Link] | @devopsshack
Keep Learning. Keep Building. Keep Shining.

@devopsshack Code. Create. Conquer.

You might also like