Python Fundamentals

From Basic to Advanced - All in One Place

Comprehensive guide covering Python basics, NumPy, Pandas, Django, Pygame, Pymunk and more

Python Basics

Variables and Data Types

Python is dynamically typed, meaning you don't need to declare variable types.

# Basic data types
integer_var = 42
float_var = 3.14159
string_var = "Hello, Python!"
boolean_var = True
none_var = None

# Collections
list_var = [1, 2, 3, "four", 5.0]
tuple_var = (1, 2, 3)  # Immutable
dict_var = {"name": "Alice", "age": 25}
set_var = {1, 2, 3, 3, 2}  # Duplicates removed: {1, 2, 3}

Control Flow

# If-elif-else
age = 18
if age < 13:
    print("Child")
elif age < 20:
    print("Teenager")
else:
    print("Adult")

# Loops
for i in range(5):  # 0 to 4
    print(i)

# While loop
count = 0
while count < 5:
    print(count)
    count += 1

Functions

# Basic function
def greet(name):
    return f"Hello, {name}!"

# Function with default arguments
def power(base, exponent=2):
    return base ** exponent

# Lambda function
square = lambda x: x * x

Object-Oriented Programming

class Dog:
    # Class attribute
    species = "Canis familiaris"
    
    # Initializer
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    # Instance method
    def description(self):
        return f"{self.name} is {self.age} years old"
    
    # Another instance method
    def speak(self, sound):
        return f"{self.name} says {sound}"

# Inheritance
class Bulldog(Dog):
    def speak(self, sound="Woof"):
        return super().speak(sound)

NumPy - Numerical Python

Creating Arrays

import numpy as np

# Create arrays
arr1 = np.array([1, 2, 3, 4, 5])  # 1D array
arr2 = np.array([[1, 2, 3], [4, 5, 6]])  # 2D array
zeros = np.zeros((3, 4))  # 3x4 array of zeros
ones = np.ones((2, 2))  # 2x2 array of ones
range_arr = np.arange(0, 10, 2)  # array([0, 2, 4, 6, 8])
random_arr = np.random.rand(3, 3)  # 3x3 random array

Array Operations

# Basic operations
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

print(a + b)  # [5 7 9]
print(a * b)  # [4 10 18]
print(np.dot(a, b))  # Dot product: 32

# Matrix operations
matrix = np.array([[1, 2], [3, 4]])
print(matrix.T)  # Transpose
print(np.linalg.inv(matrix))  # Inverse matrix

Array Indexing and Slicing

arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

print(arr[0, 1])  # 2 (first row, second column)
print(arr[:, 1])  # [2 5 8] (all rows, second column)
print(arr[1:3, 0:2])  # [[4 5] [7 8]] (subarray)

Broadcasting

# Broadcasting allows operations between arrays of different shapes
a = np.array([1, 2, 3])
b = 2
print(a * b)  # [2 4 6]

matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
row = np.array([10, 20, 30])
print(matrix + row)  # Adds row to each row of matrix

Pandas - Data Analysis

DataFrames and Series

import pandas as pd

# Create DataFrame from dictionary
data = {
    'Name': ['Alice', 'Bob', 'Charlie'],
    'Age': [25, 30, 35],
    'City': ['New York', 'Paris', 'London']
}
df = pd.DataFrame(data)

# Create Series
ages = pd.Series([25, 30, 35], name="Age")

# Basic operations
print(df.head())  # First 5 rows
print(df.describe())  # Statistics
print(df['Age'].mean())  # Average age

Data Selection

# Selecting columns
print(df['Name'])  # Single column
print(df[['Name', 'Age']])  # Multiple columns

# Selecting rows
print(df.iloc[0])  # First row by index
print(df.loc[df['Age'] > 30])  # Rows where Age > 30

Data Cleaning

# Handling missing data
df['Salary'] = [50000, None, 70000]  # Add column with missing value
print(df.isnull())  # Check for missing values
df_clean = df.dropna()  # Drop rows with missing values
df_filled = df.fillna(60000)  # Fill missing values

# Removing duplicates
df = pd.DataFrame({'A': [1, 1, 2, 2], 'B': ['a', 'b', 'a', 'b']})
df_no_dup = df.drop_duplicates()

Grouping and Aggregation

# Group by and aggregate
df = pd.DataFrame({
    'Department': ['Sales', 'Sales', 'IT', 'IT', 'HR'],
    'Employee': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
    'Salary': [60000, 50000, 80000, 75000, 55000]
})

grouped = df.groupby('Department')
print(grouped.mean())  # Average salary by department
print(grouped.agg({'Salary': ['mean', 'min', 'max', 'count']}))  # Multiple stats

Django - Web Framework

Setting Up a Project

# Install Django
# pip install django

# Create a new project
# django-admin startproject myproject

# Create a new app
# python manage.py startapp myapp

# Project structure:
# myproject/
# ├── manage.py
# ├── myproject/
# │   ├── __init__.py
# │   ├── settings.py
# │   ├── urls.py
# │   └── wsgi.py
# └── myapp/
#     ├── migrations/
#     ├── __init__.py
#     ├── admin.py
#     ├── apps.py
#     ├── models.py
#     ├── tests.py
#     └── views.py

Models

# myapp/models.py
from django.db import models

class BlogPost(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    published_date = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    
    def __str__(self):
        return self.title

# After creating models:
# python manage.py makemigrations
# python manage.py migrate

Views and URLs

# myapp/views.py
from django.shortcuts import render
from .models import BlogPost

def post_list(request):
    posts = BlogPost.objects.all().order_by('-published_date')
    return render(request, 'blog/post_list.html', {'posts': posts})

# myproject/urls.py
from django.contrib import admin
from django.urls import path
from myapp import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('posts/', views.post_list, name='post_list'),
]

Templates

<!-- myapp/templates/blog/post_list.html -->
{% extends 'base.html' %}

{% block content %}
  <h1>Blog Posts</h1>
  {% for post in posts %}
    <div class="post">
      <h2><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h2>
      <p>Published: {{ post.published_date }} by {{ post.author }}</p>
      <p>{{ post.content|linebreaksbr }}</p>
    </div>
  {% endfor %}
{% endblock %}

Pygame - Game Development

Basic Setup

import pygame
import sys

# Initialize pygame
pygame.init()

# Set up the display
screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("My Pygame")

# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)

# Game loop
clock = pygame.time.Clock()
running = True

while running:
    # Handle events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    
    # Update game state
    
    # Draw everything
    screen.fill(BLACK)
    pygame.draw.rect(screen, RED, (100, 100, 50, 50))
    
    # Update the display
    pygame.display.flip()
    
    # Cap the frame rate
    clock.tick(60)

pygame.quit()
sys.exit()

Sprite Example

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((50, 50))
        self.image.fill(RED)
        self.rect = self.image.get_rect()
        self.rect.center = (screen_width // 2, screen_height // 2)
        self.speed = 5
    
    def update(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.rect.x -= self.speed
        if keys[pygame.K_RIGHT]:
            self.rect.x += self.speed
        if keys[pygame.K_UP]:
            self.rect.y -= self.speed
        if keys[pygame.K_DOWN]:
            self.rect.y += self.speed
        
        # Keep player on screen
        self.rect.clamp_ip(screen.get_rect())

# Create sprite group
all_sprites = pygame.sprite.Group()
player = Player()
all_sprites.add(player)

# In game loop:
# all_sprites.update()
# all_sprites.draw(screen)

Collision Detection

class Enemy(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((30, 30))
        self.image.fill(WHITE)
        self.rect = self.image.get_rect()
        self.rect.x = random.randrange(screen_width - self.rect.width)
        self.rect.y = random.randrange(screen_height - self.rect.height)
        self.speedy = random.randrange(1, 4)
    
    def update(self):
        self.rect.y += self.speedy
        if self.rect.top > screen_height:
            self.rect.x = random.randrange(screen_width - self.rect.width)
            self.rect.y = random.randrange(-100, -40)
            self.speedy = random.randrange(1, 4)

# Create enemy group
enemies = pygame.sprite.Group()
for i in range(8):
    e = Enemy()
    all_sprites.add(e)
    enemies.add(e)

# Check for collisions
hits = pygame.sprite.spritecollide(player, enemies, False)
if hits:
    # Handle collision
    print("Player hit an enemy!")

Pymunk - Physics Engine

Basic Physics Setup

import pymunk
import pymunk.pygame_util

# Initialize space
space = pymunk.Space()
space.gravity = (0, 900)  # Gravity pointing down

# Create a static floor
floor = pymunk.Segment(space.static_body, (0, 500), (800, 500), 5)
floor.elasticity = 0.8
space.add(floor)

# Create a dynamic ball
ball_mass = 1
ball_radius = 30
ball_moment = pymunk.moment_for_circle(ball_mass, 0, ball_radius)
ball_body = pymunk.Body(ball_mass, ball_moment)
ball_body.position = (400, 100)
ball_shape = pymunk.Circle(ball_body, ball_radius)
ball_shape.elasticity = 0.9
ball_shape.friction = 0.5
space.add(ball_body, ball_shape)

# In game loop:
# space.step(1/60.0)  # Update physics
# draw_options = pymunk.pygame_util.DrawOptions(screen)
# space.debug_draw(draw_options)

Interactive Physics

# Mouse interaction
def create_ball(x, y):
    ball_mass = 1
    ball_radius = 15
    ball_moment = pymunk.moment_for_circle(ball_mass, 0, ball_radius)
    ball_body = pymunk.Body(ball_mass, ball_moment)
    ball_body.position = (x, y)
    ball_shape = pymunk.Circle(ball_body, ball_radius)
    ball_shape.elasticity = 0.8
    ball_shape.friction = 0.5
    space.add(ball_body, ball_shape)
    return ball_shape

# In event loop:
# if event.type == pygame.MOUSEBUTTONDOWN:
#     if event.button == 1:  # Left click
#         create_ball(event.pos[0], event.pos[1])

Joints and Constraints

# Create two bodies connected with a joint
body1 = pymunk.Body(1, pymunk.moment_for_box(1, (50, 50)))
body1.position = (300, 200)
box1 = pymunk.Poly.create_box(body1, (50, 50))
box1.friction = 0.7
space.add(body1, box1)

body2 = pymunk.Body(1, pymunk.moment_for_box(1, (50, 50)))
body2.position = (400, 200)
box2 = pymunk.Poly.create_box(body2, (50, 50))
box2.friction = 0.7
space.add(body2, box2)

# Create a pin joint between them
joint = pymunk.PinJoint(body1, body2, (25, 25), (-25, 25))
space.add(joint)

Advanced Python Concepts

Decorators

# Simple decorator
def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

# say_hello() will now include the decorator's behavior

# Decorator with arguments
def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}")

Generators

# Simple generator
def count_up_to(max):
    count = 1
    while count <= max:
        yield count
        count += 1

counter = count_up_to(5)
for num in counter:
    print(num)  # Prints 1, 2, 3, 4, 5

# Generator expression
squares = (x*x for x in range(10))
for square in squares:
    print(square)

Context Managers

# Using context manager (with statement)
with open('file.txt', 'r') as file:
    content = file.read()
# File automatically closed here

# Creating a custom context manager
from contextlib import contextmanager

@contextmanager
def managed_file(name):
    try:
        f = open(name, 'w')
        yield f
    finally:
        f.close()

with managed_file('hello.txt') as f:
    f.write('Hello, world!')
    f.write('Bye now')

Metaclasses

# Simple metaclass example
class Meta(type):
    def __new__(cls, name, bases, dct):
        # Add a class attribute
        dct['created_by'] = 'Meta'
        
        # Ensure all methods are uppercase
        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val
        
        return super().__new__(cls, name, bases, uppercase_attr)

class MyClass(metaclass=Meta):
    def my_method(self):
        print("This is my method")

obj = MyClass()
obj.MY_METHOD()  # Note the uppercase method name
print(MyClass.created_by)  # 'Meta'