🐍
PYTHON FOUNDATIONS
A .NET Developer's Fast-Track Guide
Phase 1 of Your Python + LLM Journey
Introduction
Welcome, .NET developer! This guide is designed specifically for you — it skips the absolute basics
and focuses on what's different in Python vs C#, along with everything you need to build real projects
and ace interviews.
⏱ Timeline
Estimated completion: 2–3 weeks (1–2 hours/day). Each section includes exercises. Build a small
project at the end of each week.
What You'll Cover
• Week 1 — Python syntax, data types, functions, and OOP
• Week 2 — File I/O, error handling, modules, virtual environments
• Week 2–3 — FastAPI, async/await, Pydantic, and HTTP clients
💡 Your Advantage
You already understand APIs, backend architecture, async patterns, and OOP. Python will feel
familiar fast. The key is learning Python's idioms — not re-learning programming.
Week 1 — Python Syntax & Core Concepts
1.1 — Setting Up Your Environment
Install Python 3.11+ from [Link]. Then set up your dev environment:
# Check Python version
python --version
# Create a virtual environment (do this for every project)
python -m venv venv
# Activate it (Windows)
venv\Scripts\activate
# Activate it (Mac/Linux)
source venv/bin/activate
# Install packages
pip install requests fastapi uvicorn pydantic
# Save dependencies (like [Link] or .csproj)
pip freeze > [Link]
# Recreate from requirements
pip install -r [Link]
🔧 Recommended IDE
Use VS Code with the Python extension, or PyCharm Community Edition. Both have great .NET
developer integrations.
1.2 — Python vs C# — Key Syntax Differences
Let's get the translation out of the way first. Here's a direct comparison of common patterns:
Concept C# (.NET) Python
Variable declaration string name = "Naman"; name = "Naman"
Type hints (optional) string name = "Naman"; name: str = "Naman"
Null check if (x == null) if x is None:
String interpolation $"Hello {name}" f"Hello {name}"
List List<int> nums = new List<int>() nums: list[int] = []
Dictionary Dictionary<string, int>() d: dict[str, int] = {}
Print [Link](x) print(x)
Concept C# (.NET) Python
Try/Catch try { } catch (Exception e) try: ... except Exception as e:
Async method async Task<T> Method() async def method() -> T:
Lambda x => x * 2 lambda x: x * 2
1.3 — Variables, Types & Type Hints
Python is dynamically typed but supports optional type hints (strongly recommended for LLM work and
interviews):
# Basic types
name: str = "Naman"
age: int = 28
score: float = 9.5
is_active: bool = True
nothing: None = None
# Python has no separate char type — use str
letter: str = "A"
# Type checking at runtime (useful for debugging)
print(type(name)) # <class 'str'>
print(isinstance(age, int)) # True
# f-strings (your new best friend)
greeting = f"Hello, {name}! You are {age} years old."
formatted = f"Score: {score:.2f}" # Score: 9.50
1.4 — Collections: Lists, Dicts, Tuples, Sets
Lists (equivalent to List<T>)
# List creation and operations
fruits: list[str] = ["apple", "banana", "cherry"]
[Link]("mango") # Add to end
[Link](0, "avocado") # Insert at index
[Link]("banana") # Remove by value
popped = [Link]() # Remove and return last item
# Slicing (powerful — no equivalent in C# arrays)
first_two = fruits[0:2] # ['avocado', 'apple']
last_two = fruits[-2:] # last 2 items
reversed_list = fruits[::-1] # reverse
# List comprehension (key Python pattern!)
numbers = [1, 2, 3, 4, 5]
squares = [n ** 2 for n in numbers] # [1, 4, 9, 16, 25]
evens = [n for n in numbers if n % 2 == 0] # [2, 4]
Dictionaries (equivalent to Dictionary<K,V>)
# Dictionary creation
user: dict[str, any] = {
"name": "Naman",
"age": 28,
"role": "Developer"
}
# Safe access (like TryGetValue)
name = [Link]("name", "Unknown") # Default if key missing
# Looping over dict
for key, value in [Link]():
print(f"{key}: {value}")
# Dict comprehension
lengths = {word: len(word) for word in ["hello", "world"]}
# {'hello': 5, 'world': 5}
# Merging dicts (Python 3.9+)
defaults = {"theme": "dark", "lang": "en"}
overrides = {"lang": "hi"}
merged = defaults | overrides # {'theme': 'dark', 'lang': 'hi'}
Tuples & Sets
# Tuples — immutable, ordered (like readonly structs)
point: tuple[int, int] = (10, 20)
x, y = point # unpacking
# Named tuple (more readable)
from typing import NamedTuple
class Point(NamedTuple):
x: int
y: int
p = Point(10, 20)
print(p.x, p.y)
# Sets — unique unordered values
skills = {"python", "csharp", "python"}
print(skills) # {'python', 'csharp'} — duplicates removed
a = {1, 2, 3}
b = {2, 3, 4}
print(a & b) # {2, 3} — intersection
print(a | b) # {1, 2, 3, 4} — union
print(a - b) # {1} — difference
1.5 — Functions
# Basic function with type hints
def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}!"
print(greet("Naman")) # Hello, Naman!
print(greet("Naman", "Hey")) # Hey, Naman!
print(greet(greeting="Hi", name="Naman")) # keyword args
# *args and **kwargs (variable arguments)
def log(*messages: str, level: str = "INFO") -> None:
for msg in messages:
print(f"[{level}] {msg}")
log("Starting", "Processing", "Done", level="DEBUG")
# Lambda (inline functions)
double = lambda x: x * 2
add = lambda x, y: x + y
# Using built-in higher-order functions
nums = [3, 1, 4, 1, 5, 9]
sorted_nums = sorted(nums) # [1, 1, 3, 4, 5, 9]
sorted_desc = sorted(nums, reverse=True) # [9, 5, 4, 3, 1, 1]
people = [{"name": "Bob", "age": 30}, {"name": "Alice", "age": 25}]
by_age = sorted(people, key=lambda p: p["age"]) # sort by age
1.6 — Object-Oriented Programming
Python OOP is similar to C# but with some key differences. The most important: self is explicit (like this
but you write it).
from dataclasses import dataclass
from typing import Optional
# Regular class
class Animal:
# Class variable (shared across instances)
kingdom: str = "Animalia"
def __init__(self, name: str, sound: str):
# Instance variables
[Link] = name
[Link] = sound
self._secret = "hidden" # _ prefix = private by convention
def speak(self) -> str:
return f"{[Link]} says {[Link]}"
# String representation (like ToString())
def __repr__(self) -> str:
return f"Animal(name={[Link]!r})"
# Inheritance
class Dog(Animal):
def __init__(self, name: str, breed: str):
super().__init__(name, "Woof") # super() like base()
[Link] = breed
def fetch(self, item: str) -> str:
return f"{[Link]} fetches the {item}!"
dog = Dog("Rex", "Labrador")
print([Link]()) # Rex says Woof
print([Link]("ball")) # Rex fetches the ball!
Dataclasses (your new favorite — like C# records)
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class User:
name: str
email: str
age: int = 0
tags: list[str] = field(default_factory=list)
is_active: bool = True
def display_name(self) -> str:
return [Link]()
# Auto-generates __init__, __repr__, __eq__
user1 = User(name="naman", email="n@[Link]")
user2 = User(name="naman", email="n@[Link]")
print(user1 == user2) # True — value equality!
print(user1) # User(name='naman', email='n@[Link]', ...)
Week 2 — Python in Practice
2.1 — Error Handling
# Basic try/except (like try/catch)
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Math error: {e}")
except (TypeError, ValueError) as e:
print(f"Type/Value error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
raise # re-raise the exception
else:
print("No error occurred") # runs if no exception
finally:
print("Always runs") # like C# finally
# Custom exceptions
class ValidationError(Exception):
def __init__(self, field: str, message: str):
[Link] = field
super().__init__(f"Validation failed for {field}: {message}")
def validate_age(age: int) -> None:
if age < 0 or age > 150:
raise ValidationError("age", "Must be between 0 and 150")
try:
validate_age(-5)
except ValidationError as e:
print([Link], str(e)) # age Validation failed for age: ...
2.2 — File I/O
# Reading files (context manager = using in C#)
with open("[Link]", "r", encoding="utf-8") as f:
content = [Link]() # entire file as string
# OR
lines = [Link]() # list of lines
# OR
for line in f: # memory efficient for large files
print([Link]())
# Writing files
with open("[Link]", "w", encoding="utf-8") as f:
[Link]("Hello World\n")
[Link](["Line 1\n", "Line 2\n"])
# Appending
with open("[Link]", "a") as f:
[Link]("New log entry\n")
# JSON files (very common in LLM work)
import json
# Write JSON
data = {"name": "Naman", "scores": [90, 85, 92]}
with open("[Link]", "w") as f:
[Link](data, f, indent=2)
# Read JSON
with open("[Link]", "r") as f:
loaded = [Link](f)
# Path operations (use pathlib — not [Link])
from pathlib import Path
base = Path("./data")
file_path = base / "results" / "[Link]" # path joining
file_path.[Link](parents=True, exist_ok=True) # create dirs
exists = file_path.exists()
2.3 — Decorators
Decorators are a key Python pattern — you'll see them everywhere in FastAPI and LLM frameworks.
They're similar to C# attributes but they actually execute code.
import time
from functools import wraps
# A decorator is just a function that wraps another function
def timer(func):
@wraps(func) # preserves function metadata
def wrapper(*args, **kwargs):
start = [Link]()
result = func(*args, **kwargs)
end = [Link]()
print(f"{func.__name__} took {end - start:.3f}s")
return result
return wrapper
@timer # same as: process_data = timer(process_data)
def process_data(n: int) -> list[int]:
return [i ** 2 for i in range(n)]
result = process_data(10000) # process_data took 0.002s
# Decorator with parameters
def retry(max_attempts: int = 3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
print(f"Attempt {attempt + 1} failed: {e}")
return wrapper
return decorator
@retry(max_attempts=3)
def call_api(url: str) -> dict:
import requests
return [Link](url).json()
2.4 — Async / Await in Python
Good news: async/await in Python works almost identically to C#. The main difference is you need
asyncio to run it.
import asyncio
import httpx # async HTTP client (pip install httpx)
# Async function
async def fetch_data(url: str) -> dict:
async with [Link]() as client:
response = await [Link](url)
response.raise_for_status()
return [Link]()
# Running multiple async tasks concurrently
async def fetch_multiple(urls: list[str]) -> list[dict]:
tasks = [fetch_data(url) for url in urls]
results = await [Link](*tasks) # like [Link]()
return results
# Entry point
async def main():
urls = [
"[Link]
"[Link]
]
results = await fetch_multiple(urls)
for r in results:
print(r["login"], r["public_repos"])
# Run the event loop
if __name__ == "__main__":
[Link](main())
2.5 — Comprehensions & Generators
These are distinctly Pythonic — interviewers love asking about these.
# List comprehension (already covered)
squares = [x**2 for x in range(10)]
# Dict comprehension
word_lengths = {word: len(word) for word in ["hello", "world", "python"]}
# Set comprehension
unique_lengths = {len(word) for word in ["hi", "hello", "hey"]}
# Nested comprehension
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [num for row in matrix for num in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Generator (memory-efficient, lazy evaluation)
# Use () instead of [] — doesn't load all into memory
big_squares = (x**2 for x in range(1_000_000)) # only computes on demand
# Generator function (like IEnumerable<T> yield return)
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
first_10 = [next(fib) for _ in range(10)]
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Week 2–3 — FastAPI & Real-World Python
3.1 — FastAPI Basics
FastAPI is your entry point to Python backend work. If you know [Link] Core, you'll feel right at
home — it uses decorators instead of attributes, and Pydantic instead of data annotations.
# pip install fastapi uvicorn pydantic
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, EmailStr, validator
from typing import Optional
app = FastAPI(title="My API", version="1.0")
# Pydantic model (like C# DTO with data annotations)
class UserCreate(BaseModel):
name: str
email: str
age: int
@validator("age")
def age_must_be_positive(cls, v):
if v < 0:
raise ValueError("Age must be positive")
return v
class UserResponse(BaseModel):
id: int
name: str
email: str
# GET endpoint
@[Link]("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
# Simulated DB lookup
if user_id != 1:
raise HTTPException(status_code=404, detail="User not found")
return {"id": 1, "name": "Naman", "email": "n@[Link]"}
# POST endpoint
@[Link]("/users", response_model=UserResponse, status_code=201)
async def create_user(user: UserCreate):
# user is auto-validated and parsed
return {"id": 100, "name": [Link], "email": [Link]}
# Run: uvicorn main:app --reload
# Swagger UI auto-generated at: [Link]
3.2 — Pydantic (Deep Dive)
Pydantic is everywhere in the LLM ecosystem. Master this — it's used by LangChain, FastAPI,
OpenAI's SDK, and more.
from pydantic import BaseModel, Field, validator, model_validator
from typing import Optional, Literal
from datetime import datetime
class Address(BaseModel):
street: str
city: str
country: str = "India"
class Profile(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
email: str = Field(..., pattern=r"^[\w.-]+@[\w.-]+\.\w+$")
age: int = Field(..., ge=0, le=150)
role: Literal["admin", "user", "guest"] = "user"
address: Optional[Address] = None
created_at: datetime = Field(default_factory=[Link])
@validator("name")
def name_must_be_capitalized(cls, v):
return [Link]()
# Usage
p = Profile(name="naman mittal", email="n@[Link]", age=28)
print([Link]) # Naman Mittal (auto-capitalized)
print(p.model_dump()) # dict representation
print(p.model_dump_json()) # JSON string
# Parsing from dict (common when receiving API responses)
data = {"name": "test user", "email": "t@[Link]", "age": 25}
profile = Profile.model_validate(data)
3.3 — Making HTTP Requests
import httpx
import asyncio
# Sync requests (simple scripts)
import requests
response = [Link](
"[Link]
headers={"Authorization": "Bearer your-api-key"},
timeout=30
)
response.raise_for_status() # raises exception if 4xx/5xx
data = [Link]()
# POST with JSON body
response = [Link](
"[Link]
json={"key": "value"}, # auto sets Content-Type: application/json
headers={"Authorization": "Bearer token"}
)
# Async HTTP (use in FastAPI / LLM apps)
async def call_llm_api(prompt: str) -> str:
async with [Link](timeout=60) as client:
response = await [Link](
"[Link]
headers={
"x-api-key": "your-key",
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
json={
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"messages": [{"role": "user", "content": prompt}]
}
)
result = [Link]()
return result["content"][0]["text"]
Key Python Patterns for Interviews
4.1 — The Walrus Operator & Modern Python
# Walrus operator := (Python 3.8+) — assign and use in expression
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Instead of:
n = len(data)
if n > 5:
print(f"Long list: {n}")
# You can write:
if (n := len(data)) > 5:
print(f"Long list: {n}")
# Useful in while loops
import re
text = "The price is 42 dollars"
if match := [Link](r"\d+", text):
print(f"Found number: {[Link]()}") # Found number: 42
4.2 — Context Managers (with statement)
# Built-in: files, locks, DB connections
with open("[Link]") as f:
data = [Link]() # file auto-closes after block
# Custom context manager using class
class Timer:
def __enter__(self):
import time
[Link] = [Link]()
return self
def __exit__(self, *args):
import time
[Link] = [Link]() - [Link]
print(f"Elapsed: {[Link]:.3f}s")
with Timer() as t:
result = sum(range(1_000_000))
# Elapsed: 0.032s
# Using contextlib (easier)
from contextlib import contextmanager
@contextmanager
def managed_resource(name: str):
print(f"Acquiring {name}")
try:
yield name
finally:
print(f"Releasing {name}")
with managed_resource("DB connection") as conn:
print(f"Using {conn}")
4.3 — String Operations (Interview Favorites)
# Common string methods
text = " Hello, World! "
print([Link]()) # 'Hello, World!'
print([Link]()) # ' hello, world! '
print([Link]("World", "Python")) # ' Hello, Python! '
print([Link](",")) # [' Hello', ' World! ']
# Join (opposite of split)
words = ["Python", "is", "awesome"]
sentence = " ".join(words) # 'Python is awesome'
csv_line = ",".join(words) # 'Python,is,awesome'
# Check contents
print("hello".startswith("he")) # True
print("hello123".isalnum()) # True
print(" ".isspace()) # True
# f-string formatting
pi = 3.14159
print(f"{pi:.2f}") # 3.14
print(f"{pi:10.2f}") # ' 3.14' (right aligned, width 10)
print(f"{1000000:,}") # 1,000,000
# Regular expressions
import re
email = "user@[Link]"
is_valid = bool([Link](r"^[\w.-]+@[\w.-]+\.\w+$", email))
all_numbers = [Link](r"\d+", "I have 3 cats and 2 dogs")
# ['3', '2']
Your 3-Week Study Plan
Week Topics Daily Goal Project
Week 1 Days 1-7 Syntax, Types, 1 section/day + 5 CLI tool: JSON data
Collections, Functions, exercises processor
OOP, Dataclasses
Week 2 Days 8-14 Error Handling, File 1 section/day + build Async web scraper or
I/O, Decorators, something API client
Async/Await,
Comprehensions
Week 2-3 Days 12-21 FastAPI, Pydantic, Build and break things Full FastAPI CRUD
httpx, Virtual Envs, REST API
Project structure
Daily Exercise Template
For each section, do this:
1. Read the section and run all code examples yourself
2. Modify the examples — break them intentionally, then fix them
3. Write 1 small function/class using the concept from scratch
4. Compare: how would I have done this in C#?
Recommended Tools & Resources
Resource Use For Link
Official Python Docs Reference, stdlib modules [Link]
Real Python Tutorials with .NET comparison [Link]
FastAPI Docs Best API framework docs ever [Link]
Pydantic Docs Essential for LLM work [Link]
Python Exercises Interview prep exercises [Link]/tracks/python
🎯 Interview Tip
Interviewers love asking about: list comprehensions vs loops (performance), generators vs lists
(memory), async vs threading, and dataclasses vs Pydantic. Be ready to explain trade-offs, not just
syntax.
✅ Done with Phase 1?
Move to Phase 2: LLM Basics — OpenAI/Anthropic APIs, prompt engineering, function calling, and
your first RAG pipeline. Your FastAPI knowledge will be essential there.
Python Foundations Guide · Prepared for Naman · Masters' Union AI Lab