Python is a high-level, interpreted, object-oriented programming language known for its simplicity and readability.
PEP 8 is the official style guide for Python code. It provides conventions for writing readable and consistent Python code.
# Good PEP 8 style
def calculate_average(numbers):
"""Calculate the average of a list of numbers."""
if not numbers:
return 0
return sum(numbers) / len(numbers)
class StudentGradeCalculator:
def __init__(self, student_name):
self.student_name = student_name
self.grades = []
| Type | Description | Example | Methods |
|---|---|---|---|
| int | Integer numbers | 42, -17, 0 | bit_length(), to_bytes() |
| float | Floating-point numbers | 3.14, -0.5, 2e10 | is_integer(), hex() |
| complex | Complex numbers | 3+4j, 2-1j | real, imag, conjugate() |
# Type examples
integer_num = 42
float_num = 3.14159
complex_num = 3 + 4j
string_text = "Hello, Python!"
list_data = [1, 2, 3, "mixed", True]
tuple_data = (1, 2, 3)
dict_data = {"name": "John", "age": 30}
set_data = {1, 2, 3, 3} # {1, 2, 3}
# Type checking
print(type(integer_num)) #
print(isinstance(float_num, (int, float))) # True
Cannot be changed after creation. Examples: int, float, str, tuple, frozenset
Can be modified after creation. Examples: list, dict, set, user-defined objects
# Immutable example
s1 = "Hello"
s2 = s1
s1 += " World" # Creates new string
print(s1) # "Hello World"
print(s2) # "Hello" (unchanged)
# Mutable example
list1 = [1, 2, 3]
list2 = list1
list1.append(4) # Modifies existing list
print(list1) # [1, 2, 3, 4]
print(list2) # [1, 2, 3, 4] (same object)
Ordered, mutable collection that allows duplicates.
# List operations
fruits = ["apple", "banana", "cherry"]
fruits.append("orange") # Add to end
fruits.insert(1, "grape") # Insert at position
fruits.remove("banana") # Remove by value
popped = fruits.pop() # Remove and return last
fruits[0] = "kiwi" # Modify by index
# List comprehension
squares = [x**2 for x in range(10)]
even_squares = [x**2 for x in range(10) if x % 2 == 0]
Unordered collection of key-value pairs.
# Dictionary operations
person = {"name": "John", "age": 30, "city": "New York"}
person["job"] = "Developer" # Add new key-value
age = person.get("age", 0) # Safe access with default
person.update({"salary": 75000, "age": 31}) # Update multiple
# Dictionary comprehension
word_counts = {word: len(word) for word in ["hello", "world", "python"]}
# Useful methods
keys = list(person.keys()) # Get all keys
values = list(person.values()) # Get all values
items = list(person.items()) # Get key-value pairs
Unordered collection of unique elements.
# Set operations
set1 = {1, 2, 3, 4, 5}
set2 = {4, 5, 6, 7, 8}
union = set1 | set2 # {1, 2, 3, 4, 5, 6, 7, 8}
intersection = set1 & set2 # {4, 5}
difference = set1 - set2 # {1, 2, 3}
symmetric_diff = set1 ^ set2 # {1, 2, 3, 6, 7, 8}
# Set methods
set1.add(6) # Add single element
set1.update([7, 8, 9]) # Add multiple elements
set1.discard(10) # Remove if exists (no error)
set1.remove(1) # Remove (raises KeyError if not found)
Ordered, immutable collection.
# Tuple operations
coordinates = (10, 20)
person_info = ("John", 30, "Developer")
# Tuple unpacking
name, age, job = person_info
x, y = coordinates
# Named tuples (from collections)
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
print(p.x, p.y) # 10 20
# Basic function
def greet(name, greeting="Hello"):
"""Function with default parameter."""
return f"{greeting}, {name}!"
# Variable arguments
def sum_all(*args):
"""Function with variable positional arguments."""
return sum(args)
def print_info(**kwargs):
"""Function with variable keyword arguments."""
for key, value in kwargs.items():
print(f"{key}: {value}")
# Mixed parameters
def complex_function(required, default="value", *args, **kwargs):
print(f"Required: {required}")
print(f"Default: {default}")
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
# Usage examples
print(greet("Alice")) # Hello, Alice!
print(greet("Bob", "Hi")) # Hi, Bob!
print(sum_all(1, 2, 3, 4, 5)) # 15
print_info(name="John", age=30) # name: John, age: 30
# LEGB Rule: Local -> Enclosing -> Global -> Built-in
global_var = "I'm global"
def outer_function():
enclosing_var = "I'm in enclosing scope"
def inner_function():
local_var = "I'm local"
print(local_var) # Local
print(enclosing_var) # Enclosing
print(global_var) # Global
print(len([1, 2, 3])) # Built-in
return inner_function
# Global and nonlocal keywords
counter = 0
def increment_global():
global counter
counter += 1
def create_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
# Lambda syntax
square = lambda x: x**2
add = lambda x, y: x + y
# Common use with higher-order functions
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
total = functools.reduce(lambda x, y: x + y, numbers)
# Sorting with lambda
students = [("Alice", 85), ("Bob", 90), ("Charlie", 78)]
students.sort(key=lambda x: x[1]) # Sort by grade
Decorators are a way to modify or enhance functions/classes without permanently modifying their code. They use the @ symbol syntax.
# Basic decorator
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before function call")
result = func(*args, **kwargs)
print("After function call")
return result
return wrapper
@my_decorator
def say_hello(name):
print(f"Hello, {name}!")
return f"Greeting for {name}"
# Equivalent to: say_hello = my_decorator(say_hello)
# Decorator with parameters
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def print_message(message):
print(message)
# Built-in decorators
class Calculator:
def __init__(self, value):
self._value = value
@property
def value(self):
return self._value
@value.setter
def value(self, new_value):
if new_value < 0:
raise ValueError("Value must be non-negative")
self._value = new_value
@staticmethod
def add(a, b):
return a + b
@classmethod
def from_string(cls, value_str):
return cls(int(value_str))
class Person:
# Class variable (shared by all instances)
species = "Homo sapiens"
def __init__(self, name, age):
# Instance variables
self.name = name
self.age = age
self._private_var = "This is private by convention"
# Instance method
def introduce(self):
return f"Hi, I'm {self.name} and I'm {self.age} years old"
# Property with getter/setter
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("Age cannot be negative")
self._age = value
# Creating instances
person1 = Person("Alice", 30)
person2 = Person("Bob", 25)
print(person1.introduce()) # Hi, I'm Alice and I'm 30 years old
print(Person.species) # Homo sapiens
class BankAccount:
def __init__(self, account_number, balance=0):
self.account_number = account_number
self.balance = balance
def __str__(self):
return f"Account {self.account_number}: ${self.balance:.2f}"
def __repr__(self):
return f"BankAccount('{self.account_number}', {self.balance})"
def __eq__(self, other):
if isinstance(other, BankAccount):
return self.account_number == other.account_number
return False
def __len__(self):
return len(self.account_number)
def __bool__(self):
return self.balance > 0
# Usage
account = BankAccount("123456", 1000)
print(str(account)) # Account 123456: $1000.00
print(len(account)) # 6
print(bool(account)) # True
Python uses automatic memory management with reference counting and garbage collection.
import sys
# Object creation increases reference count
my_list = [1, 2, 3]
print(sys.getrefcount(my_list)) # Reference count
# Multiple references
another_ref = my_list
print(sys.getrefcount(my_list)) # Increased count
# Deleting reference
del another_ref
print(sys.getrefcount(my_list)) # Decreased count
import gc
# Check garbage collection statistics
print(gc.get_stats())
# Manual garbage collection
collected = gc.collect()
print(f"Collected {collected} objects")
# Disable/enable automatic garbage collection
gc.disable()
gc.enable()
# Memory optimization tips
class OptimizedClass:
__slots__ = ['x', 'y'] # Reduces memory usage
def __init__(self, x, y):
self.x = x
self.y = y
# Using memory_profiler
from memory_profiler import profile
@profile
def memory_intensive_function():
# Create large data structures
big_list = [i for i in range(1000000)]
big_dict = {i: i**2 for i in range(100000)}
return big_list, big_dict
# Using tracemalloc (built-in)
import tracemalloc
tracemalloc.start()
# ... your code here ...
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current / 1024 / 1024:.1f} MB")
print(f"Peak memory usage: {peak / 1024 / 1024:.1f} MB")
tracemalloc.stop()
The GIL is a mutex that protects access to Python objects, preventing multiple native threads from executing Python bytecodes simultaneously.
import threading
import time
def io_bound_task(name, duration):
print(f"Starting {name}")
time.sleep(duration) # Simulates I/O operation (GIL released)
print(f"Finished {name}")
def cpu_bound_task(name, iterations):
print(f"Starting {name}")
total = 0
for i in range(iterations):
total += i * i
print(f"Finished {name}, result: {total}")
# Threading for I/O-bound tasks
threads = []
for i in range(3):
t = threading.Thread(target=io_bound_task, args=(f"Task-{i}", 2))
threads.append(t)
t.start()
for t in threads:
t.join()
# Multiprocessing for CPU-bound tasks
from multiprocessing import Process, Pool
import os
def cpu_intensive_work(n):
return sum(i * i for i in range(n))
if __name__ == '__main__':
# Using Process
processes = []
for i in range(os.cpu_count()):
p = Process(target=cpu_intensive_work, args=(1000000,))
processes.append(p)
p.start()
for p in processes:
p.join()
# Using Pool
with Pool() as pool:
results = pool.map(cpu_intensive_work, [1000000] * 4)
print(results)
Django is a high-level Python web framework that follows the Model-View-Template (MVT) pattern.
# models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.title
# views.py
from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse
from django.views.generic import ListView, CreateView
from .models import Post
# Function-based view
def post_list(request):
posts = Post.objects.all().select_related('author', 'category')
return render(request, 'blog/post_list.html', {'posts': posts})
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
return render(request, 'blog/post_detail.html', {'post': post})
# Class-based view
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
return Post.objects.select_related('author', 'category')
# API view
from django.views.decorators.csrf import csrf_exempt
import json
@csrf_exempt
def api_posts(request):
if request.method == 'GET':
posts = list(Post.objects.values('id', 'title', 'content'))
return JsonResponse({'posts': posts})
elif request.method == 'POST':
data = json.loads(request.body)
post = Post.objects.create(
title=data['title'],
content=data['content'],
author=request.user
)
return JsonResponse({'id': post.id, 'status': 'created'})
Flask is a lightweight, micro web framework for Python. It's more minimalist and flexible compared to Django.
from flask import Flask, request, jsonify, render_template
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
db = SQLAlchemy(app)
# Model
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'content': self.content
}
# Routes
@app.route('/')
def home():
posts = Post.query.all()
return render_template('index.html', posts=posts)
@app.route('/api/posts', methods=['GET', 'POST'])
def api_posts():
if request.method == 'GET':
posts = Post.query.all()
return jsonify([post.to_dict() for post in posts])
elif request.method == 'POST':
data = request.get_json()
post = Post(title=data['title'], content=data['content'])
db.session.add(post)
db.session.commit()
return jsonify(post.to_dict()), 201
@app.route('/api/posts/', methods=['GET', 'PUT', 'DELETE'])
def api_post_detail(post_id):
post = Post.query.get_or_404(post_id)
if request.method == 'GET':
return jsonify(post.to_dict())
elif request.method == 'PUT':
data = request.get_json()
post.title = data.get('title', post.title)
post.content = data.get('content', post.content)
db.session.commit()
return jsonify(post.to_dict())
elif request.method == 'DELETE':
db.session.delete(post)
db.session.commit()
return '', 204
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
Foundation for scientific computing in Python, providing support for large multi-dimensional arrays.
import numpy as np
# Creating arrays
arr1 = np.array([1, 2, 3, 4, 5])
arr2 = np.zeros((3, 4))
arr3 = np.ones((2, 3))
arr4 = np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
# Array operations
matrix = np.array([[1, 2], [3, 4]])
print(matrix.shape) # (2, 2)
print(matrix.dtype) # int64
# Mathematical operations
result = np.dot(matrix, matrix) # Matrix multiplication
mean_val = np.mean(arr1)
std_val = np.std(arr1)
High-level data manipulation and analysis library built on NumPy.
import pandas as pd
# Creating DataFrames
data = {
'Name': ['Alice', 'Bob', 'Charlie', 'Diana'],
'Age': [25, 30, 35, 28],
'City': ['New York', 'London', 'Tokyo', 'Paris']
}
df = pd.DataFrame(data)
# Data operations
print(df.head())
print(df.info())
print(df.describe())
# Filtering and selection
young_people = df[df['Age'] < 30]
names_ages = df[['Name', 'Age']]
# Grouping and aggregation
grouped = df.groupby('City')['Age'].mean()
# File I/O
df.to_csv('people.csv', index=False)
df_loaded = pd.read_csv('people.csv')
try:
# Code that might raise an exception
result = 10 / 0
except ZeroDivisionError as e:
print(f"Division by zero error: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
else:
print("No exceptions occurred")
finally:
print("This always executes")
# Multiple exception types
try:
value = int(input("Enter a number: "))
result = 10 / value
except (ValueError, ZeroDivisionError) as e:
print(f"Error: {type(e).__name__}: {e}")
# Custom exceptions
class CustomError(Exception):
def __init__(self, message, code=None):
self.message = message
self.code = code
super().__init__(self.message)
def validate_age(age):
if age < 0:
raise CustomError("Age cannot be negative", code="NEGATIVE_AGE")
if age > 150:
raise CustomError("Age seems unrealistic", code="UNREALISTIC_AGE")
return True
try:
validate_age(-5)
except CustomError as e:
print(f"Validation error: {e.message} (Code: {e.code})")
# Type hints example
from typing import List, Dict, Optional
def process_data(items: List[str],
config: Dict[str, any]) -> Optional[List[int]]:
"""Process a list of items according to configuration.
Args:
items: List of string items to process
config: Configuration dictionary
Returns:
List of processed integers or None if processing fails
"""
if not items:
return None
try:
return [len(item) for item in items if item.strip()]
except Exception as e:
print(f"Processing failed: {e}")
return None
# Using dataclasses for cleaner code
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
email: Optional[str] = None
def is_adult(self) -> bool:
return self.age >= 18