📌 Ne Zaman Kullanılır?
- ✅ Veri analizi, otomasyon, web backend (Django/FastAPI), ML/AI, scripting
- ⚠️ Real-time/WebSocket için Node.js daha yaygın
- ❌ Frontend geliştirme, mobil uygulama (native)
Önerilen Kullanım: FastAPI/Django + PostgreSQL (web) veya veri bilimi/otomasyon Alternatifler: Node.js (web API), Go (performans), Ruby (Rails)
Python Rehberi -- Temelden İleri Seviyeye (EN + TR)
🐍 Complete English + Turkish guide for Python: syntax, OOP, data science (NumPy/Pandas), web (Flask), automation (Selenium), GUI (PyQt5/Kivy), visualization (Folium), game dev (Pygame), testing & deployment. (🐍 Python için kapsamlı rehber -- temel söz dizimi, OOP, veri bilimi, web, otomasyon, GUI, harita, oyun geliştirme, test ve dağıtım.)
1) Temeller (Basics)
# Değişkenler & Tipler (Variables & Types)
x = 42 # int
pi = 3.14 # float
name = "Fahri" # str
is_ok = True # bool
nums = [1, 2, 3] # list
coords = (10, 20) # tuple (değiştirilemez / immutable)
data = {"a": 1} # dict
unique = {1, 2, 3} # set
nothing = None # NoneType# String formatting (f-string)
age = 25
print(f"Merhaba, ben {name}. Yaşım {age}.")
# Çoklu satır (multiline)
text = """
Bu çok
satırlı bir string.
"""
# String methodları
"hello world".upper() # "HELLO WORLD"
"hello world".split() # ["hello", "world"]
"hello world".replace("o","0") # "hell0 w0rld"
" space ".strip() # "space"Type conversion (Tür dönüştürme):
int("42") # 42
float("3.14") # 3.14
str(42) # "42"
list("abc") # ["a", "b", "c"]
bool(0) # False
bool("") # False
bool([]) # False2) Control Flow & Functions
# if / elif / else
if x > 10:
print("Büyük")
elif x == 10:
print("Eşit")
else:
print("Küçük")
# Ternary
status = "ok" if x > 0 else "error"
# for döngüsü
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i, val in enumerate(["a", "b", "c"]):
print(f"{i}: {val}")
# while döngüsü
while x > 0:
x -= 1
# Fonksiyonlar
def greet(name: str = "world") -> str:
"""Selamlama fonksiyonu."""
return f"Hello, {name}!"
# *args & **kwargs
def log(*args, **kwargs):
print(args) # tuple
print(kwargs) # dict
# Lambda
square = lambda n: n * n
sorted_list = sorted([3, 1, 2], key=lambda x: -x) # [3, 2, 1]
# Decorators
def timer(func):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__}: {time.time() - start:.3f}s")
return result
return wrapper
@timer
def slow_func():
import time
time.sleep(1)3) Collections & Comprehensions
# List comprehension
squares = [n * n for n in range(10)]
evens = [n for n in range(20) if n % 2 == 0]
# Dict comprehension
mapping = {c: ord(c) for c in "abc"}
# {'a': 97, 'b': 98, 'c': 99}
# Set comprehension
unique = {x % 3 for x in range(6)} # {0, 1, 2}
# Generator expression (bellek dostu / memory efficient)
gen = (n * n for n in range(1_000_000))Sık kullanılan list/dict methodları:
| Method | Açıklama |
|---|---|
list.append(x) | Sona ekle |
list.extend([1,2]) | Listeyi genişlet |
list.insert(i, x) | Belirli indexe ekle |
list.pop(i) | Indexten çıkar |
list.sort() | Sırala (in-place) |
dict.get(k, default) | Güvenli erişim |
dict.keys() | Anahtarlar |
dict.values() | Değerler |
dict.items() | (key, value) çiftleri |
dict.update({...}) | Güncelle/birleştir |
4) Modules, Packages & Virtual Envs
# Virtual environment oluştur (Create venv)
python -m venv .venv
source .venv/bin/activate # Linux/Mac
# .venv\Scripts\activate # Windows
# Paket yönetimi (Package management)
pip install requests flask numpy
pip freeze > requirements.txt
pip install -r requirements.txt
# Poetry (modern alternatif)
pip install poetry
poetry init
poetry add requests
poetry add --group dev pytest
poetry install
poetry build# Kendi modülün (Your own module)
# mypkg/__init__.py
# mypkg/utils.py
from .utils import slugify
# Standart kütüphane örnekleri
import os, sys, json, re, datetime
from pathlib import Path
from collections import Counter, defaultdict
from itertools import chain, product5) OOP, Dataclasses & Exceptions
# Temel sınıf (Basic class)
class Animal:
def __init__(self, name: str, sound: str):
self.name = name
self.sound = sound
def speak(self) -> str:
return f"{self.name} says {self.sound}"
def __repr__(self):
return f"Animal({self.name!r})"
# Kalıtım (Inheritance)
class Dog(Animal):
def __init__(self, name: str):
super().__init__(name, "Woof")
def fetch(self, item: str) -> str:
return f"{self.name} fetches {item}"
# Dataclass (Python 3.7+)
from dataclasses import dataclass, field
@dataclass
class User:
id: int
name: str
email: str
tags: list[str] = field(default_factory=list)
user = User(1, "Fahri", "info@fahriaydin.dev")
# Exception handling
class AppError(Exception):
"""Custom application error."""
pass
try:
result = risky_operation()
except ValueError as e:
print(f"Değer hatası: {e}")
except (TypeError, KeyError) as e:
print(f"Tip/Anahtar hatası: {e}")
except Exception as e:
print(f"Beklenmeyen hata: {e}")
finally:
cleanup()6) Typing & Static Analysis
from typing import Optional, Union, Literal
from collections.abc import Iterable, Callable
# Temel typing
def total(xs: Iterable[int]) -> int:
return sum(xs)
age: Optional[int] = None # int | None
value: Union[str, int] = "hello" # str veya int
mode: Literal["read", "write"] = "read" # sadece belirli değerler
# Callable typing
Handler = Callable[[str, int], bool]
# Generic (Python 3.12+)
def first[T](items: list[T]) -> T | None:
return items[0] if items else None
# TypedDict
from typing import TypedDict
class Config(TypedDict):
host: str
port: int
debug: bool# mypy ile statik tip kontrolü
pip install mypy
mypy .7) Files, Paths & CLI
from pathlib import Path
# Path işlemleri
p = Path("data/output.txt")
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("hello", encoding="utf-8")
content = p.read_text(encoding="utf-8")
# Dosya listesi (glob)
for f in Path(".").rglob("*.py"):
print(f)
# argparse (CLI)
from argparse import ArgumentParser
parser = ArgumentParser(description="Veri işleme aracı")
parser.add_argument("input", help="Girdi dosyası")
parser.add_argument("--output", "-o", default="out.json")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()8) Data Handling: JSON/CSV/SQLite
import json, csv, sqlite3
# --- JSON ---
data = {"name": "Fahri", "skills": ["Python", "React"]}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
parsed = json.loads(json_str)
# Dosyaya yaz/oku
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
with open("data.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
# --- CSV ---
with open("out.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["id", "name", "score"])
writer.writerow([1, "Fahri", 95])
with open("out.csv", "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["score"])
# --- SQLite ---
con = sqlite3.connect("app.db")
cur = con.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE
)
""")
cur.execute("INSERT INTO users(name, email) VALUES (?, ?)", ("Fahri", "info@fahriaydin.dev"))
con.commit()
for row in cur.execute("SELECT * FROM users"):
print(row)
con.close()9) XML Okuma & Yazma
XML, yapılandırılmış veri alışverişinde hala yaygın olarak kullanılır (RSS, sitemap, config, SOAP). (XML is still widely used for structured data exchange -- RSS, sitemaps, configs, SOAP.)
import xml.etree.ElementTree as ET
# --- XML Okuma (Parsing) ---
xml_str = """<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<book id="1" lang="tr">
<title>Python Rehberi</title>
<author>Fahri</author>
<price currency="TRY">150.00</price>
</book>
<book id="2" lang="en">
<title>Flask Web Dev</title>
<author>Miguel</author>
<price currency="USD">39.99</price>
</book>
</catalog>"""
root = ET.fromstring(xml_str) # String'den parse et
# tree = ET.parse("catalog.xml") # Dosyadan parse et
# root = tree.getroot()
# Element bilgileri
print(root.tag) # "catalog"
print(root[0].tag) # "book"
print(root[0].attrib) # {'id': '1', 'lang': 'tr'}
# --- XPath ile Arama ---
for book in root.findall("book"):
title = book.find("title").text
author = book.find("author").text
price = book.find("price").text
lang = book.get("lang")
print(f"[{lang}] {title} - {author} ({price})")
# Attribute filtresi ile arama
tr_books = root.findall(".//book[@lang='tr']")
print(f"Türkçe kitap sayısı: {len(tr_books)}")
# --- XML Oluşturma ---
catalog = ET.Element("catalog")
book = ET.SubElement(catalog, "book", id="3", lang="tr")
ET.SubElement(book, "title").text = "Django Rehberi"
ET.SubElement(book, "author").text = "Fahri"
ET.SubElement(book, "price", currency="TRY").text = "200.00"
tree = ET.ElementTree(catalog)
ET.indent(tree, space=" ") # Python 3.9+ (pretty print)
tree.write("output.xml", encoding="utf-8", xml_declaration=True)
# --- lxml (daha gelismis XPath & XSLT destegi) ---
# pip install lxml
from lxml import etree
doc = etree.fromstring(xml_str.encode())
results = doc.xpath("//book[price > 100]/title/text()")
print(results) # ['Python Rehberi']Pratik: RSS Feed Parse Etme
import xml.etree.ElementTree as ET
import urllib.request
# RSS feed indir ve parse et
url = "https://blog.python.org/feeds/posts/default?alt=rss"
response = urllib.request.urlopen(url)
tree = ET.parse(response)
root = tree.getroot()
# RSS item'larini listele
for item in root.findall(".//item")[:5]:
title = item.find("title").text
link = item.find("link").text
print(f"- {title}\n {link}\n")Pratik: Sitemap Parse Etme
import xml.etree.ElementTree as ET
import urllib.request
url = "https://example.com/sitemap.xml"
response = urllib.request.urlopen(url)
tree = ET.parse(response)
root = tree.getroot()
# Sitemap namespace
ns = {"sm": "http://www.sitemaps.org/schemas/sitemap/0.9"}
for url_elem in root.findall("sm:url", ns):
loc = url_elem.find("sm:loc", ns).text
lastmod = url_elem.find("sm:lastmod", ns)
print(f"{loc} ({lastmod.text if lastmod is not None else 'N/A'})")10) NumPy -- Sayısal Hesaplama
NumPy, Python'da hızlı sayısal işlemler için temel kütüphanedir. (NumPy is the foundational library for fast numerical computing in Python.)
pip install numpyimport numpy as np
# Array oluşturma (Creating arrays)
a = np.array([1, 2, 3, 4, 5])
zeros = np.zeros((3, 4)) # 3x4 sıfır matrisi
ones = np.ones((2, 3)) # 2x3 birler matrisi
rng = np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
lin = np.linspace(0, 1, 5) # [0, 0.25, 0.5, 0.75, 1.0]
rand = np.random.rand(3, 3) # 3x3 rastgele (0-1)
eye = np.eye(3) # 3x3 birim matris
# Temel işlemler (Basic operations)
b = np.array([10, 20, 30, 40, 50])
print(a + b) # Eleman bazlı toplama
print(a * 2) # Skaler çarpma
print(a ** 2) # Kare alma
print(np.dot(a, b)) # İç çarpım (dot product)
# Şekil değiştirme (Reshaping)
matrix = np.arange(12).reshape(3, 4)
print(matrix.shape) # (3, 4)
flat = matrix.flatten()
# İndeksleme & dilimleme (Indexing & slicing)
print(matrix[0, :]) # İlk satır
print(matrix[:, 1]) # İkinci sütun
print(matrix[1:, :2]) # Alt-sol blok
# İstatistik (Statistics)
data = np.array([23, 45, 12, 67, 34])
print(np.mean(data)) # Ortalama
print(np.median(data)) # Medyan
print(np.std(data)) # Standart sapma
print(np.min(data)) # Minimum
print(np.max(data)) # Maksimum
print(np.argmax(data)) # Max index
# Boolean indexing (Koşullu filtreleme)
filtered = data[data > 30] # [45, 67, 34]
# Lineer cebir (Linear algebra)
A = np.array([[1, 2], [3, 4]])
inv = np.linalg.inv(A) # Ters matris
det = np.linalg.det(A) # Determinant
vals, vecs = np.linalg.eig(A) # Özdeğer/özvektör11) Pandas -- Veri Analizi
Pandas, tablo verisi (CSV, Excel, SQL) üzerinde analiz yapmak için kullanılır. (Pandas is used for tabular data analysis -- CSV, Excel, SQL and more.)
pip install pandas openpyxlimport pandas as pd
# --- DataFrame Oluşturma ---
df = pd.DataFrame({
"name": ["Fahri", "Ali", "Ayşe", "Mehmet"],
"age": [25, 30, 22, 35],
"city": ["İstanbul", "Ankara", "İzmir", "İstanbul"],
"salary": [8000, 12000, 7000, 15000]
})
# CSV okuma/yazma
df = pd.read_csv("data.csv")
df.to_csv("output.csv", index=False)
# Excel okuma/yazma
df = pd.read_excel("data.xlsx", sheet_name="Sheet1")
df.to_excel("output.xlsx", index=False)
# --- Temel İnceleme (Basic inspection) ---
print(df.head()) # İlk 5 satır
print(df.tail(3)) # Son 3 satır
print(df.info()) # Veri tipleri & null sayısı
print(df.describe()) # İstatistiksel özet
print(df.shape) # (satır, sütun)
print(df.columns.tolist()) # Sütun isimleri
# --- Filtreleme (Filtering) ---
young = df[df["age"] < 30]
istanbul = df[df["city"] == "İstanbul"]
multi = df[(df["age"] > 25) & (df["salary"] > 10000)]
# --- Sıralama (Sorting) ---
df_sorted = df.sort_values("salary", ascending=False)
# --- Gruplama (GroupBy) ---
city_avg = df.groupby("city")["salary"].mean()
city_stats = df.groupby("city").agg(
avg_salary=("salary", "mean"),
count=("name", "count"),
max_age=("age", "max")
)
# --- Yeni sütun ekleme ---
df["tax"] = df["salary"] * 0.15
df["category"] = df["age"].apply(lambda x: "genç" if x < 30 else "deneyimli")
# --- Eksik veri (Missing data) ---
df.isnull().sum() # Sütun bazında null sayısı
df.dropna() # Null olanları çıkar
df.fillna(0) # Null'ları 0 ile doldur
df["age"].fillna(df["age"].median(), inplace=True)
# --- Birleştirme (Merge/Join) ---
orders = pd.DataFrame({"user_id": [1, 2, 1], "product": ["A", "B", "C"]})
users = pd.DataFrame({"user_id": [1, 2], "name": ["Fahri", "Ali"]})
merged = pd.merge(orders, users, on="user_id", how="left")
# --- Pivot table ---
pivot = df.pivot_table(values="salary", index="city", aggfunc="mean")12) Flask -- Web Framework
Flask, hafif ve esnek bir Python web framework'üdür. (Flask is a lightweight and flexible Python web framework.)
pip install flaskfrom flask import Flask, request, jsonify, render_template, redirect, url_for
app = Flask(__name__)
# --- Basit route'lar ---
@app.route("/")
def index():
return render_template("index.html")
@app.get("/ping")
def ping():
return jsonify(ok=True)
# --- Parametreli route ---
@app.route("/user/<int:user_id>")
def get_user(user_id):
return jsonify(id=user_id, name="Fahri")
# --- POST & JSON ---
@app.route("/api/items", methods=["POST"])
def create_item():
data = request.get_json()
name = data.get("name")
if not name:
return jsonify(error="name is required"), 400
return jsonify(id=1, name=name), 201
# --- Query params ---
@app.route("/search")
def search():
q = request.args.get("q", "")
page = request.args.get("page", 1, type=int)
return jsonify(query=q, page=page)
# --- Form data ---
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
# authenticate...
return redirect(url_for("index"))
return render_template("login.html")
# --- Error handlers ---
@app.errorhandler(404)
def not_found(e):
return jsonify(error="Not found"), 404
@app.errorhandler(500)
def server_error(e):
return jsonify(error="Internal server error"), 500
# --- Blueprint (modüler yapı) ---
from flask import Blueprint
api_bp = Blueprint("api", __name__, url_prefix="/api/v1")
@api_bp.route("/users")
def list_users():
return jsonify(users=[])
app.register_blueprint(api_bp)
if __name__ == "__main__":
app.run(debug=True, port=5000)Proje yapısı (Project structure):
myapp/
├── app.py # veya __init__.py
├── config.py
├── models/
├── routes/
│ ├── __init__.py
│ ├── auth.py
│ └── api.py
├── templates/
│ ├── base.html
│ └── index.html
├── static/
│ ├── css/
│ └── js/
└── requirements.txt13) Django -- Web Framework
Django, "batteries included" felsefesiyle Python'un en kapsamli web framework'udur. (Django is Python's most comprehensive web framework with a "batteries included" philosophy.)
Kurulum & Proje Oluşturma
# Kurulum
pip install django
# Proje olustur
django-admin startproject myproject .
# "." ile bulundugun dizinde olusturur (ekstra klasor yok)
# Uygulama (app) olustur
python manage.py startapp blog
# Gelistirme sunucusu
python manage.py runserver # http://127.0.0.1:8000
python manage.py runserver 0.0.0.0:8080 # Farkli portProje Yapisi
myproject/
├── manage.py # Yonetim komutu (CLI entry point)
├── myproject/
│ ├── __init__.py
│ ├── settings.py # Proje ayarlari (DB, apps, middleware...)
│ ├── urls.py # Ana URL yapilandirmasi
│ ├── asgi.py # ASGI entry point
│ └── wsgi.py # WSGI entry point
└── blog/ # Uygulama (app)
├── __init__.py
├── admin.py # Admin panel kayitlari
├── apps.py # App yapilandirmasi
├── models.py # Veritabani modelleri
├── views.py # Gorunumler (views)
├── urls.py # App URL'leri (kendin olustur)
├── serializers.py # DRF serializer (kendin olustur)
├── templates/ # HTML sablonlari
│ └── blog/
│ └── post_list.html
├── static/ # Statik dosyalar (CSS, JS, img)
├── migrations/ # Veritabani migration dosyalari
└── tests.py # Testler# settings.py - App ekleme
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
# 3rd party
"rest_framework",
# Local apps
"blog",
]Models & Migrations (ORM)
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.SlugField(unique=True)
class Meta:
verbose_name_plural = "categories"
ordering = ["name"]
def __str__(self):
return self.name
class Post(models.Model):
STATUS_CHOICES = [
("draft", "Taslak"),
("published", "Yayinda"),
]
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
body = models.TextField()
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="draft")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published_at = models.DateTimeField(null=True, blank=True)
class Meta:
ordering = ["-created_at"]
def __str__(self):
return self.title
def publish(self):
self.status = "published"
self.published_at = timezone.now()
self.save()# Migration olustur ve uygula
python manage.py makemigrations
python manage.py migrate
# ORM Shell
python manage.py shell# ORM Sorgulari (Shell icerisinde)
from blog.models import Post, Category
# Olusturma
cat = Category.objects.create(name="Python", slug="python")
post = Post.objects.create(
title="Django Rehberi",
slug="django-rehberi",
author=user,
category=cat,
body="Icerik burada...",
status="published"
)
# Sorgulama
Post.objects.all() # Tum kayitlar
Post.objects.filter(status="published") # Filtreleme
Post.objects.filter(title__icontains="django") # Buyuk/kucuk harf duyarsiz
Post.objects.exclude(status="draft") # Haric tutma
Post.objects.get(slug="django-rehberi") # Tek kayit
Post.objects.filter(author__username="fahri") # Iliski uzerinden filtre
Post.objects.order_by("-created_at")[:5] # Son 5 yazi
Post.objects.count() # Sayi
# Guncelleme & Silme
post.title = "Yeni Baslik"
post.save()
Post.objects.filter(status="draft").update(status="published")
post.delete()Views (Function-Based & Class-Based)
# blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse
from django.views.generic import ListView, DetailView, CreateView
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Post
# --- Function-Based Views (FBV) ---
def post_list(request):
posts = Post.objects.filter(status="published")
return render(request, "blog/post_list.html", {"posts": posts})
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug, status="published")
return render(request, "blog/post_detail.html", {"post": post})
@login_required
def post_create(request):
if request.method == "POST":
title = request.POST["title"]
body = request.POST["body"]
post = Post.objects.create(
title=title, body=body, author=request.user, slug=title.lower().replace(" ", "-")
)
return redirect("post_detail", slug=post.slug)
return render(request, "blog/post_form.html")
def api_posts(request):
"""Basit JSON API"""
posts = Post.objects.filter(status="published").values("id", "title", "slug", "created_at")
return JsonResponse(list(posts), safe=False)
# --- Class-Based Views (CBV) ---
class PostListView(ListView):
model = Post
template_name = "blog/post_list.html"
context_object_name = "posts"
queryset = Post.objects.filter(status="published")
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ["title", "body", "category", "status"]
template_name = "blog/post_form.html"
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)URL Routing
# blog/urls.py
from django.urls import path
from . import views
app_name = "blog" # namespace
urlpatterns = [
path("", views.PostListView.as_view(), name="post_list"),
path("post/<slug:slug>/", views.post_detail, name="post_detail"),
path("post/new/", views.post_create, name="post_create"),
path("api/posts/", views.api_posts, name="api_posts"),
]
# myproject/urls.py (ana URL dosyasi)
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("blog.urls", namespace="blog")),
]Templates (DTL Basics)
<!-- templates/blog/base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Blog{% endblock %}</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<nav>
<a href="{% url 'blog:post_list' %}">Ana Sayfa</a>
{% if user.is_authenticated %}
<span>{{ user.username }}</span>
<a href="{% url 'logout' %}">Cikis</a>
{% else %}
<a href="{% url 'login' %}">Giris</a>
{% endif %}
</nav>
<main>{% block content %}{% endblock %}</main>
</body>
</html>
<!-- templates/blog/post_list.html -->
{% extends "blog/base.html" %}
{% block title %}Yazilar{% endblock %}
{% block content %}
<h1>Blog Yazilari</h1>
{% for post in posts %}
<article>
<h2><a href="{% url 'blog:post_detail' slug=post.slug %}">{{ post.title }}</a></h2>
<p>{{ post.body|truncatewords:30 }}</p>
<small>{{ post.author.username }} - {{ post.created_at|date:"d M Y" }}</small>
</article>
{% empty %}
<p>Henuz yazi yok.</p>
{% endfor %}
<!-- Sayfalama (Pagination) -->
{% if is_paginated %}
<nav>
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">Onceki</a>
{% endif %}
<span>Sayfa {{ page_obj.number }} / {{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Sonraki</a>
{% endif %}
</nav>
{% endif %}
{% endblock %}Django REST Framework (DRF)
pip install djangorestframework
# settings.py'de INSTALLED_APPS'e "rest_framework" ekle# blog/serializers.py
from rest_framework import serializers
from .models import Post, Category
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ["id", "name", "slug"]
class PostSerializer(serializers.ModelSerializer):
author = serializers.StringRelatedField(read_only=True)
category = CategorySerializer(read_only=True)
class Meta:
model = Post
fields = ["id", "title", "slug", "author", "category", "body", "status", "created_at"]
read_only_fields = ["author", "created_at"]
# blog/views.py (DRF ViewSets)
from rest_framework import viewsets, permissions, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from .serializers import PostSerializer, CategorySerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.filter(status="published")
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ["title", "body"]
ordering_fields = ["created_at", "title"]
lookup_field = "slug"
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=False, methods=["get"])
def recent(self, request):
recent_posts = self.queryset.order_by("-created_at")[:5]
serializer = self.get_serializer(recent_posts, many=True)
return Response(serializer.data)
class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
lookup_field = "slug"
# blog/urls.py (DRF Router)
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r"posts", PostViewSet)
router.register(r"categories", CategoryViewSet)
# urlpatterns'e ekle:
# path("api/", include(router.urls)),Admin Panel
# blog/admin.py
from django.contrib import admin
from .models import Post, Category
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ["name", "slug"]
prepopulated_fields = {"slug": ("name",)}
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ["title", "author", "category", "status", "created_at"]
list_filter = ["status", "category", "created_at"]
search_fields = ["title", "body"]
prepopulated_fields = {"slug": ("title",)}
raw_id_fields = ["author"]
date_hierarchy = "created_at"
ordering = ["-created_at"]
list_editable = ["status"]# Superuser olustur (Admin panele giris icin)
python manage.py createsuperuser
# Admin panel: http://127.0.0.1:8000/admin/Authentication
# settings.py
LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/"
LOGIN_URL = "/accounts/login/"
# myproject/urls.py
from django.contrib.auth import views as auth_views
urlpatterns = [
# ...
path("accounts/login/", auth_views.LoginView.as_view(), name="login"),
path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"),
path("accounts/password_change/", auth_views.PasswordChangeView.as_view(), name="password_change"),
]# DRF Authentication (settings.py)
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticatedOrReadOnly",
],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
}Temel Komutlar
| Komut | Açıklama |
|---|---|
django-admin startproject myproject . | Yeni proje oluştur |
python manage.py startapp blog | Yeni app oluştur |
python manage.py runserver | Geliştirme sunucusu |
python manage.py makemigrations | Migration dosyasi oluştur |
python manage.py migrate | Migration'lari uygula |
python manage.py createsuperuser | Admin kullanicisi oluştur |
python manage.py shell | Django shell (ORM erisimi) |
python manage.py collectstatic | Statik dosyaları topla |
python manage.py test | Testleri çalıştır |
python manage.py showmigrations | Migration durumunu göster |
python manage.py dbshell | Veritabani shell'ine baglan |
python manage.py dumpdata blog > data.json | Veriyi JSON'a aktar |
python manage.py loaddata data.json | JSON'dan veri yükle |
python manage.py check --deploy | Deployment kontrolu |
14) FastAPI -- Modern Async Web Framework
FastAPI, yüksek performanslı, otomatik dokümantasyonlu modern Python web framework'üdür. (FastAPI is a modern, high-performance Python web framework with automatic documentation.)
pip install fastapi uvicorn[standard]Temel Uygulama
from fastapi import FastAPI, HTTPException, Query, Path, Depends, status
from fastapi.responses import JSONResponse
app = FastAPI(
title="Benim API'm",
description="FastAPI ile oluşturulmuş REST API",
version="1.0.0",
)
@app.get("/")
async def root():
return {"message": "Merhaba, FastAPI!"}
@app.get("/items/{item_id}")
async def get_item(
item_id: int = Path(..., title="Item ID", ge=1),
q: str | None = Query(None, max_length=50),
):
return {"item_id": item_id, "q": q}# Geliştirme sunucusu
uvicorn main:app --reload --port 8000
# Otomatik dokümantasyon: http://localhost:8000/docs (Swagger UI)
# Alternatif dokümantasyon: http://localhost:8000/redoc (ReDoc)Pydantic Models
from pydantic import BaseModel, Field, EmailStr, field_validator
from datetime import datetime
class UserCreate(BaseModel):
name: str = Field(..., min_length=2, max_length=100)
email: EmailStr
age: int = Field(..., ge=0, le=150)
tags: list[str] = Field(default_factory=list)
@field_validator("name")
@classmethod
def name_must_not_be_empty(cls, v: str) -> str:
if not v.strip():
raise ValueError("İsim boş olamaz")
return v.strip()
class UserResponse(BaseModel):
id: int
name: str
email: str
age: int
tags: list[str]
created_at: datetime
model_config = {"from_attributes": True} # ORM mode
class UserUpdate(BaseModel):
name: str | None = None
email: EmailStr | None = None
age: int | None = Field(None, ge=0, le=150)
# Request/Response ile kullanım
@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate):
# user.name, user.email vs. otomatik validate edilir
db_user = save_to_db(user)
return db_user
@app.patch("/users/{user_id}", response_model=UserResponse)
async def update_user(user_id: int, user: UserUpdate):
update_data = user.model_dump(exclude_unset=True)
db_user = partial_update(user_id, update_data)
return db_userDependency Injection
from fastapi import Depends, Header, HTTPException
from typing import Annotated
# Basit dependency
async def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Authentication dependency
async def get_current_user(
authorization: str = Header(...),
):
token = authorization.replace("Bearer ", "")
user = verify_token(token)
if not user:
raise HTTPException(status_code=401, detail="Geçersiz token")
return user
# Kullanım
@app.get("/profile")
async def profile(
current_user: Annotated[User, Depends(get_current_user)],
db: Annotated[Session, Depends(get_db)],
):
return {"user": current_user.name}
# Sınıf bazlı dependency
class Pagination:
def __init__(
self,
page: int = Query(1, ge=1),
per_page: int = Query(20, ge=1, le=100),
):
self.skip = (page - 1) * per_page
self.limit = per_page
@app.get("/items")
async def list_items(pagination: Pagination = Depends()):
items = db.query(Item).offset(pagination.skip).limit(pagination.limit).all()
return itemsMiddleware
from fastapi import Request
from fastapi.middleware.cors import CORSMiddleware
import time
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Custom middleware -- istek süresi loglama
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
start_time = time.perf_counter()
response = await call_next(request)
process_time = time.perf_counter() - start_time
response.headers["X-Process-Time"] = str(process_time)
return responseBackground Tasks
from fastapi import BackgroundTasks
def send_email(email: str, message: str):
"""Arka planda çalışacak fonksiyon"""
import smtplib
# E-posta gönderme işlemi...
print(f"E-posta gönderildi: {email}")
def write_log(message: str):
with open("log.txt", "a") as f:
f.write(f"{message}\n")
@app.post("/register")
async def register(
user: UserCreate,
background_tasks: BackgroundTasks,
):
db_user = save_to_db(user)
# Arka plan görevleri -- response'u bekletmez
background_tasks.add_task(send_email, user.email, "Hoşgeldiniz!")
background_tasks.add_task(write_log, f"Yeni kullanıcı: {user.name}")
return {"id": db_user.id, "message": "Kayıt başarılı"}WebSocket
from fastapi import WebSocket, WebSocketDisconnect
class ConnectionManager:
def __init__(self):
self.active_connections: list[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
await manager.connect(websocket)
try:
while True:
data = await websocket.receive_text()
await manager.broadcast(f"{client_id}: {data}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"{client_id} ayrıldı")FastAPI + SQLAlchemy
# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase
DATABASE_URL = "postgresql://user:pass@localhost/mydb"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class Base(DeclarativeBase):
pass
# models.py
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func
from sqlalchemy.orm import relationship
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), nullable=False)
email = Column(String(255), unique=True, index=True)
created_at = Column(DateTime, server_default=func.now())
posts = relationship("Post", back_populates="author")
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(200), nullable=False)
body = Column(String, nullable=False)
author_id = Column(Integer, ForeignKey("users.id"))
author = relationship("User", back_populates="posts")
# schemas.py (Pydantic)
from pydantic import BaseModel
class PostOut(BaseModel):
id: int
title: str
body: str
author_id: int
model_config = {"from_attributes": True}
# main.py
from fastapi import Depends
from sqlalchemy.orm import Session
from database import SessionLocal, engine, Base
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/posts", response_model=list[PostOut])
def list_posts(skip: int = 0, limit: int = 20, db: Session = Depends(get_db)):
return db.query(Post).offset(skip).limit(limit).all()
@app.post("/posts", response_model=PostOut, status_code=201)
def create_post(title: str, body: str, author_id: int, db: Session = Depends(get_db)):
post = Post(title=title, body=body, author_id=author_id)
db.add(post)
db.commit()
db.refresh(post)
return postProje yapısı (Project structure):
fastapi-project/
├── main.py # FastAPI app ve router'lar
├── database.py # SQLAlchemy engine & session
├── models.py # SQLAlchemy ORM modelleri
├── schemas.py # Pydantic şemaları
├── routers/
│ ├── __init__.py
│ ├── users.py
│ └── posts.py
├── dependencies.py # Paylaşılan dependency'ler
├── middleware.py # Custom middleware
├── alembic/ # Veritabanı migration'ları
├── tests/
│ ├── test_users.py
│ └── test_posts.py
├── alembic.ini
└── requirements.txt15) Selenium -- Web Otomasyon
Selenium, web tarayıcısını otomatik kontrol etmek için kullanılır -- test, scraping, otomasyon. (Selenium automates web browsers -- testing, scraping, automation.)
pip install selenium webdriver-managerfrom selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
# --- Tarayıcı başlatma (Launch browser) ---
options = webdriver.ChromeOptions()
options.add_argument("--headless") # Görünmez mod (headless)
options.add_argument("--no-sandbox")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=options
)
# --- Sayfa açma ---
driver.get("https://www.google.com")
print(driver.title)
# --- Element bulma (Finding elements) ---
search_box = driver.find_element(By.NAME, "q")
search_box.send_keys("Python Selenium")
search_box.send_keys(Keys.RETURN)
# --- Bekleme (Explicit wait) ---
results = WebDriverWait(driver, 10).until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, "h3"))
)
for r in results[:5]:
print(r.text)
# --- Tıklama & form doldurma ---
button = driver.find_element(By.ID, "submit-btn")
button.click()
input_field = driver.find_element(By.CSS_SELECTOR, "input[name='email']")
input_field.clear()
input_field.send_keys("info@fahriaydin.dev")
# --- Screenshot ---
driver.save_screenshot("screenshot.png")
# --- Sayfa kaynağı ---
html = driver.page_source
# --- Kapatma ---
driver.quit()Sık kullanılan By seçicileri:
| Seçici | Kullanım |
|---|---|
By.ID | find_element(By.ID, "myId") |
By.NAME | find_element(By.NAME, "email") |
By.CLASS_NAME | find_element(By.CLASS_NAME, "btn") |
By.CSS_SELECTOR | find_element(By.CSS_SELECTOR, "div.card > h2") |
By.XPATH | find_element(By.XPATH, "//div[@class='item']") |
By.TAG_NAME | find_element(By.TAG_NAME, "a") |
By.LINK_TEXT | find_element(By.LINK_TEXT, "Click here") |
16) PyQt5 -- Masaüstü GUI
PyQt5, Qt framework ile masaüstü uygulamaları geliştirmek için kullanılır. (PyQt5 is used to build cross-platform desktop applications with Qt.)
pip install PyQt5import sys
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QLineEdit, QTextEdit, QMessageBox,
QFileDialog, QComboBox, QCheckBox, QTableWidget, QTableWidgetItem
)
from PyQt5.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Benim Uygulamam")
self.setGeometry(100, 100, 600, 400)
# Ana widget & layout
central = QWidget()
self.setCentralWidget(central)
layout = QVBoxLayout(central)
# Label
self.label = QLabel("Merhaba, PyQt5!")
self.label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.label)
# Input
self.input = QLineEdit()
self.input.setPlaceholderText("Adınızı girin...")
layout.addWidget(self.input)
# Buton satırı
btn_row = QHBoxLayout()
self.btn_greet = QPushButton("Selamla")
self.btn_greet.clicked.connect(self.on_greet)
btn_row.addWidget(self.btn_greet)
self.btn_clear = QPushButton("Temizle")
self.btn_clear.clicked.connect(lambda: self.input.clear())
btn_row.addWidget(self.btn_clear)
layout.addLayout(btn_row)
# TextEdit (çok satırlı çıktı)
self.output = QTextEdit()
self.output.setReadOnly(True)
layout.addWidget(self.output)
def on_greet(self):
name = self.input.text().strip()
if name:
self.output.append(f"Merhaba, {name}!")
else:
QMessageBox.warning(self, "Uyarı", "Lütfen adınızı girin.")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())Sık kullanılan widget'lar:
| Widget | Açıklama |
|---|---|
QLabel | Metin/resim gösterme |
QPushButton | Buton |
QLineEdit | Tek satır input |
QTextEdit | Çok satır metin alanı |
QComboBox | Dropdown/seçim kutusu |
QCheckBox | Onay kutusu |
QRadioButton | Radio button |
QSlider | Kaydırıcı |
QProgressBar | İlerleme çubuğu |
QTableWidget | Tablo |
QFileDialog | Dosya seçme dialogu |
QMessageBox | Uyarı/bilgi dialogu |
Layout'lar: QVBoxLayout (dikey), QHBoxLayout (yatay), QGridLayout (ızgara), QFormLayout (form).
17) Kivy -- Cross-Platform GUI
Kivy, mobil ve masaüstü uygulamalar için dokunmatik destekli Python GUI framework'üdür. (Kivy is a cross-platform Python GUI framework with touch support -- desktop & mobile.)
pip install kivyfrom kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
class MyApp(App):
def build(self):
self.title = "Kivy Uygulamam"
layout = BoxLayout(orientation="vertical", padding=20, spacing=10)
self.label = Label(text="Merhaba, Kivy!", font_size=24)
layout.add_widget(self.label)
self.input = TextInput(hint_text="Adınızı yazın...", multiline=False, size_hint_y=None, height=40)
layout.add_widget(self.input)
btn = Button(text="Selamla", size_hint_y=None, height=50)
btn.bind(on_press=self.on_greet)
layout.add_widget(btn)
return layout
def on_greet(self, instance):
name = self.input.text.strip()
self.label.text = f"Merhaba, {name}!" if name else "Lütfen ad girin."
if __name__ == "__main__":
MyApp().run()KV Language (deklaratif UI):
# myapp.kv
BoxLayout:
orientation: "vertical"
padding: 20
spacing: 10
Label:
text: "Merhaba Kivy!"
font_size: 24
TextInput:
id: name_input
hint_text: "Adınız..."
multiline: False
size_hint_y: None
height: 40
Button:
text: "Selamla"
size_hint_y: None
height: 50
on_press: root.label.text = f"Merhaba, {name_input.text}!"Kivy vs PyQt5:
| Özellik | PyQt5 | Kivy |
|---|---|---|
| Mobil destek | - | Var (Buildozer ile APK) |
| Dokunmatik | Sınırlı | Tam destek |
| Olgunluk | Çok olgun | Aktif geliştirme |
| Görünüm | Native OS | Kendi UI sistemi |
| Lisans | GPL/Ticari | MIT |
18) Folium -- Harita Görselleştirme
Folium, Leaflet.js tabanlı interaktif haritalar oluşturur. (Folium creates interactive maps powered by Leaflet.js.)
pip install foliumimport folium
# --- Temel harita ---
m = folium.Map(location=[41.0082, 28.9784], zoom_start=12) # İstanbul
# --- Marker ekleme ---
folium.Marker(
location=[41.0082, 28.9784],
popup="<b>İstanbul</b><br>Türkiye'nin en büyük şehri",
tooltip="Tıkla!",
icon=folium.Icon(color="red", icon="info-sign")
).add_to(m)
# --- Birden fazla marker ---
locations = [
{"name": "Galata Kulesi", "lat": 41.0256, "lon": 28.9744},
{"name": "Ayasofya", "lat": 41.0086, "lon": 28.9802},
{"name": "Topkapı Sarayı", "lat": 41.0115, "lon": 28.9833},
]
for loc in locations:
folium.Marker(
[loc["lat"], loc["lon"]],
popup=loc["name"],
tooltip=loc["name"]
).add_to(m)
# --- Daire (Circle) ---
folium.Circle(
location=[41.0082, 28.9784],
radius=1000, # metre
color="blue",
fill=True,
fill_opacity=0.3,
popup="1km çevresi"
).add_to(m)
# --- GeoJSON katmanı ---
folium.GeoJson("districts.geojson", name="İlçeler").add_to(m)
# --- Isı haritası (Heatmap) ---
from folium.plugins import HeatMap
heat_data = [[41.01, 28.97, 0.8], [41.02, 28.98, 0.5], [41.00, 28.96, 0.9]]
HeatMap(heat_data).add_to(m)
# --- Katman kontrolü ---
folium.LayerControl().add_to(m)
# --- Kaydet ---
m.save("istanbul_map.html")19) Pygame -- Oyun Geliştirme
Pygame, 2D oyunlar ve multimedya uygulamaları için kullanılır. (Pygame is used for 2D games and multimedia applications.)
pip install pygameimport pygame
import sys
# --- Başlatma (Initialize) ---
pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("İlk Oyunum")
clock = pygame.time.Clock()
FPS = 60
# --- Renkler ---
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# --- Oyuncu (Player) ---
player = pygame.Rect(WIDTH // 2 - 25, HEIGHT // 2 - 25, 50, 50)
player_speed = 5
player_color = GREEN
# --- Font ---
font = pygame.font.SysFont("Arial", 24)
score = 0
# --- Oyun döngüsü (Game loop) ---
running = True
while running:
# Olaylar (Events)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
# Girdi (Input)
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
player.x -= player_speed
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
player.x += player_speed
if keys[pygame.K_UP] or keys[pygame.K_w]:
player.y -= player_speed
if keys[pygame.K_DOWN] or keys[pygame.K_s]:
player.y += player_speed
# Sınır kontrolü (Boundary check)
player.clamp_ip(screen.get_rect())
# Çizim (Drawing)
screen.fill(BLACK)
pygame.draw.rect(screen, player_color, player)
# Skor göster
score_text = font.render(f"Skor: {score}", True, WHITE)
screen.blit(score_text, (10, 10))
# Ekranı güncelle
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
sys.exit()Sık kullanılan Pygame fonksiyonları:
| Fonksiyon | Açıklama |
|---|---|
pygame.display.set_mode() | Pencere oluştur |
pygame.draw.rect() | Dikdörtgen çiz |
pygame.draw.circle() | Daire çiz |
pygame.draw.line() | Çizgi çiz |
pygame.image.load() | Resim yükle |
pygame.mixer.Sound() | Ses efekti yükle |
pygame.mixer.music.load() | Müzik yükle |
pygame.font.SysFont() | Sistem fontu |
pygame.key.get_pressed() | Basılı tuşlar |
pygame.mouse.get_pos() | Fare konumu |
pygame.sprite.Sprite | Sprite sınıfı |
pygame.sprite.Group | Sprite grubu |
Sprite tabanlı yapı:
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface((50, 50))
self.image.fill(GREEN)
self.rect = self.image.get_rect(center=(x, y))
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
all_sprites = pygame.sprite.Group()
player = Player(400, 300)
all_sprites.add(player)
# Game loop içinde:
all_sprites.update()
all_sprites.draw(screen)20) Concurrency: threads, asyncio
# --- Threading (I/O-bound işler) ---
from concurrent.futures import ThreadPoolExecutor
import requests
urls = ["https://httpbin.org/delay/1"] * 5
def fetch(url):
return requests.get(url).status_code
with ThreadPoolExecutor(max_workers=5) as pool:
results = list(pool.map(fetch, urls))
print(results)
# --- Multiprocessing (CPU-bound işler) ---
from concurrent.futures import ProcessPoolExecutor
def heavy(n):
return sum(i * i for i in range(n))
with ProcessPoolExecutor() as pool:
results = list(pool.map(heavy, [10**6] * 4))
# --- AsyncIO ---
import asyncio
import aiohttp
async def fetch_async(session, url):
async with session.get(url) as resp:
return await resp.text()
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch_async(session, f"https://httpbin.org/get?i={i}") for i in range(5)]
results = await asyncio.gather(*tasks)
print(f"{len(results)} sonuç alındı")
asyncio.run(main())21) asyncio Derinlemesine
asyncio, Python'un yerleşik asenkron I/O kütüphanesidir. I/O-bound işlemlerde yüksek performans sağlar.
async/await Temelleri
import asyncio
# Temel coroutine
async def say_hello(name: str, delay: float):
await asyncio.sleep(delay)
print(f"Merhaba, {name}!")
return f"done-{name}"
# Birden fazla coroutine'i eşzamanlı çalıştırma
async def main():
# gather -- tümünü paralel çalıştır, sonuçları topla
results = await asyncio.gather(
say_hello("Fahri", 1),
say_hello("Ali", 0.5),
say_hello("Ayşe", 0.7),
)
print(results) # ['done-Fahri', 'done-Ali', 'done-Ayşe']
asyncio.run(main())# Task oluşturma ve iptal etme
async def long_running():
try:
while True:
print("Çalışıyor...")
await asyncio.sleep(1)
except asyncio.CancelledError:
print("Görev iptal edildi!")
raise # CancelledError'u tekrar fırlat
async def main():
task = asyncio.create_task(long_running())
await asyncio.sleep(3)
task.cancel()
try:
await task
except asyncio.CancelledError:
print("Görev başarıyla iptal edildi")
asyncio.run(main())# Timeout ile çalışma
async def slow_operation():
await asyncio.sleep(10)
return "bitti"
async def main():
try:
result = await asyncio.wait_for(slow_operation(), timeout=3.0)
except asyncio.TimeoutError:
print("Zaman aşımı! 3 saniyede tamamlanamadı.")
# TaskGroup (Python 3.11+) -- yapısal eşzamanlılık
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(say_hello("A", 1))
task2 = tg.create_task(say_hello("B", 2))
# Tüm görevler tamamlandığında buraya gelir
print(task1.result(), task2.result())
asyncio.run(main())asyncio Semaphore & Queue
import asyncio
# Eşzamanlı bağlantı sayısını sınırla
semaphore = asyncio.Semaphore(5)
async def rate_limited_fetch(url: str):
async with semaphore:
# Aynı anda en fazla 5 istek
await asyncio.sleep(0.5)
return f"Fetched: {url}"
# asyncio Queue -- üretici/tüketici deseni
async def producer(queue: asyncio.Queue):
for i in range(10):
await queue.put(f"item-{i}")
await asyncio.sleep(0.1)
await queue.put(None) # Durdurma sinyali
async def consumer(queue: asyncio.Queue, name: str):
while True:
item = await queue.get()
if item is None:
queue.task_done()
break
print(f"[{name}] İşleniyor: {item}")
await asyncio.sleep(0.3)
queue.task_done()
async def main():
queue = asyncio.Queue(maxsize=5)
await asyncio.gather(
producer(queue),
consumer(queue, "Worker-1"),
consumer(queue, "Worker-2"),
)
asyncio.run(main())aiohttp -- Asenkron HTTP İstemci/Sunucu
import aiohttp
import asyncio
# --- Asenkron HTTP istemci ---
async def fetch_many():
async with aiohttp.ClientSession() as session:
urls = [f"https://httpbin.org/get?id={i}" for i in range(10)]
async def fetch_one(url):
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
return await resp.json()
results = await asyncio.gather(*[fetch_one(u) for u in urls])
print(f"{len(results)} istek tamamlandı")
asyncio.run(fetch_many())
# --- POST isteği ---
async def post_data():
async with aiohttp.ClientSession() as session:
payload = {"name": "Fahri", "role": "developer"}
async with session.post("https://httpbin.org/post", json=payload) as resp:
data = await resp.json()
print(data["json"]) # Gönderilen veri
asyncio.run(post_data())asyncpg -- Asenkron PostgreSQL
import asyncpg
import asyncio
async def main():
# Bağlantı havuzu (connection pool)
pool = await asyncpg.create_pool(
"postgresql://user:pass@localhost/mydb",
min_size=5,
max_size=20,
)
# Tablo oluştur
async with pool.acquire() as conn:
await conn.execute("""
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE
)
""")
# Kayıt ekle
async with pool.acquire() as conn:
await conn.execute(
"INSERT INTO users(name, email) VALUES($1, $2)",
"Fahri", "info@fahriaydin.dev"
)
# Sorgu çalıştır
async with pool.acquire() as conn:
rows = await conn.fetch("SELECT * FROM users WHERE name = $1", "Fahri")
for row in rows:
print(dict(row)) # {'id': 1, 'name': 'Fahri', 'email': '...'}
# Transaction
async with pool.acquire() as conn:
async with conn.transaction():
await conn.execute("UPDATE users SET name=$1 WHERE id=$2", "Fahri A.", 1)
# Hata olursa otomatik rollback
await pool.close()
asyncio.run(main())22) Type Hints Advanced
İleri seviye tip ipuçları, büyük projelerde kodun okunabilirliğini ve güvenliğini artırır.
Protocol -- Yapısal Alt Tipleme (Structural Subtyping)
from typing import Protocol, runtime_checkable
# Protocol -- interface gibi davranır, kalıtım gerektirmez
class Drawable(Protocol):
def draw(self) -> str: ...
class Circle:
def draw(self) -> str:
return "Daire çizildi"
class Square:
def draw(self) -> str:
return "Kare çizildi"
def render(shape: Drawable) -> None:
# Circle ve Square, Drawable'ı implement etmiş sayılır
# çünkü draw() metodu var (duck typing + tip güvenliği)
print(shape.draw())
render(Circle()) # Çalışır
render(Square()) # Çalışır
# runtime_checkable -- isinstance kontrolü yapılabilir
@runtime_checkable
class Sendable(Protocol):
def send(self, data: bytes) -> None: ...
class EmailClient:
def send(self, data: bytes) -> None:
print("E-posta gönderildi")
client = EmailClient()
print(isinstance(client, Sendable)) # TrueTypedDict -- Tip Güvenli Dict
from typing import TypedDict, Required, NotRequired
class UserDict(TypedDict):
name: str
age: int
email: str
class PartialUser(TypedDict, total=False):
name: str
age: int
email: str
# Python 3.11+ -- Required / NotRequired
class Config(TypedDict):
host: Required[str]
port: Required[int]
debug: NotRequired[bool]
log_level: NotRequired[str]
def connect(config: Config) -> None:
host = config["host"]
port = config["port"]
debug = config.get("debug", False)
print(f"Bağlanılıyor: {host}:{port} (debug={debug})")
connect({"host": "localhost", "port": 8080})Generic -- Tip Parametreleri
from typing import Generic, TypeVar
T = TypeVar("T")
K = TypeVar("K")
V = TypeVar("V")
# Generic sınıf
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
if not self._items:
raise IndexError("Boş stack")
return self._items.pop()
def peek(self) -> T:
return self._items[-1]
def __len__(self) -> int:
return len(self._items)
stack: Stack[int] = Stack()
stack.push(42)
stack.push(99)
value: int = stack.pop() # Tip güvenli
# Generic fonksiyon
def merge_dicts(d1: dict[K, V], d2: dict[K, V]) -> dict[K, V]:
return {**d1, **d2}
# Python 3.12+ söz dizimi (daha kısa)
def first[T](items: list[T]) -> T | None:
return items[0] if items else None
class Pair[A, B]:
def __init__(self, first: A, second: B):
self.first = A
self.second = B
# Bound TypeVar -- tip kısıtlaması
from typing import TypeVar
from numbers import Number
N = TypeVar("N", bound=Number)
def double(value: N) -> N:
return value * 2 # type: ignoreDiğer Faydalı Tipler
from typing import (
Annotated, Final, ClassVar, Self, TypeAlias,
overload, get_type_hints,
)
# TypeAlias -- karmaşık tipler için takma ad
JSON: TypeAlias = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None
# Final -- değiştirilemez değer
MAX_RETRIES: Final = 3
# ClassVar -- sınıf değişkeni (instance değil)
class AppConfig:
VERSION: ClassVar[str] = "1.0.0"
name: str
# Self (Python 3.11+) -- zincirleme metotlar için
class Builder:
def __init__(self) -> None:
self._name = ""
self._port = 8080
def name(self, name: str) -> "Self":
self._name = name
return self
def port(self, port: int) -> "Self":
self._port = port
return self
# overload -- farklı parametre kombinasyonları
@overload
def parse(data: str) -> dict: ...
@overload
def parse(data: bytes) -> dict: ...
@overload
def parse(data: str, raw: bool) -> str: ...
def parse(data, raw=False):
if isinstance(data, bytes):
data = data.decode()
if raw:
return data
return json.loads(data)
# Annotated -- metadata ile tip
UserId = Annotated[int, "Kullanıcı kimlik numarası, pozitif tam sayı"]23) Decorators Advanced
Decorator'lar, fonksiyonları veya sınıfları değiştirmeden davranışlarını genişletmenin güçlü yoludur.
functools.wraps -- Metadata Koruma
import functools
import time
# wraps olmadan decorator orijinal fonksiyonun adını ve docstring'ini kaybeder
def timer(func):
@functools.wraps(func) # Orijinal fonksiyonun metadata'sını korur
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} {elapsed:.4f}s sürdü")
return result
return wrapper
@timer
def compute(n: int) -> int:
"""N'e kadar olan sayıların toplamı."""
return sum(range(n))
print(compute.__name__) # "compute" (wraps sayesinde, yoksa "wrapper" olurdu)
print(compute.__doc__) # "N'e kadar olan sayıların toplamı."Parametreli Decorator
import functools
# Parametre alan decorator -- üç katman gerekir
def retry(max_attempts: int = 3, delay: float = 1.0):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"[{attempt}/{max_attempts}] {func.__name__} başarısız: {e}")
if attempt < max_attempts:
import time
time.sleep(delay)
raise last_exception
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def unstable_api_call():
import random
if random.random() < 0.7:
raise ConnectionError("Bağlantı hatası")
return {"status": "ok"}
# Erişim kontrolü decorator
def require_role(*roles: str):
def decorator(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if user.get("role") not in roles:
raise PermissionError(f"{user.get('role')} rolü yetkisiz")
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_role("admin", "moderator")
def delete_post(user, post_id):
print(f"Post {post_id} silindi")
# Cache decorator (lru_cache alternatifi)
def memoize(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoize
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# Standart kütüphanedeki cache
from functools import lru_cache, cache
@lru_cache(maxsize=128)
def expensive_calc(x: int, y: int) -> int:
return x ** y
@cache # sınırsız cache (Python 3.9+)
def get_config(key: str) -> str:
return load_from_file(key)Class Decorator
import functools
import time
# Sınıfa uygulanabilen decorator
def singleton(cls):
"""Bir sınıftan yalnızca tek instance oluşturulmasını sağlar."""
instances = {}
@functools.wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self, url: str):
self.url = url
print(f"Bağlantı kuruldu: {url}")
db1 = Database("postgresql://localhost/mydb")
db2 = Database("postgresql://localhost/other")
print(db1 is db2) # True -- aynı instance
# Otomatik __repr__ ekleyen decorator
def auto_repr(cls):
def __repr__(self):
attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
return f"{cls.__name__}({attrs})"
cls.__repr__ = __repr__
return cls
@auto_repr
class Point:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
print(Point(3.0, 4.0)) # Point(x=3.0, y=4.0)
# Metotlara uygulanabilen sınıf bazlı decorator
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
print(f"{self.func.__name__} {self.call_count}. kez çağrıldı")
return self.func(*args, **kwargs)
@CountCalls
def process(data):
return len(data)
process([1, 2, 3]) # process 1. kez çağrıldı
process([4, 5]) # process 2. kez çağrıldı
print(process.call_count) # 224) Context Managers
Context manager'lar, kaynakların güvenli şekilde açılıp kapatılmasını sağlar (dosya, bağlantı, kilit vb.).
with İfadesi & enter / exit
# Sınıf bazlı context manager
class FileManager:
def __init__(self, filename: str, mode: str = "r"):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode, encoding="utf-8")
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
# True dönerse exception yutulur, False/None dönerse exception yayılır
return False
with FileManager("data.txt", "w") as f:
f.write("Merhaba!")
# Zamanlayıcı context manager
import time
class Timer:
def __enter__(self):
self.start = time.perf_counter()
return self
def __exit__(self, *args):
self.elapsed = time.perf_counter() - self.start
print(f"Geçen süre: {self.elapsed:.4f}s")
with Timer() as t:
# Ölçülecek kod
total = sum(range(1_000_000))
print(f"Toplam: {total}, Süre: {t.elapsed:.4f}s")
# Transaction context manager
class Transaction:
def __init__(self, connection):
self.conn = connection
def __enter__(self):
self.conn.begin()
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
self.conn.rollback()
print(f"Rollback yapıldı: {exc_val}")
else:
self.conn.commit()
return Falsecontextlib -- Decorator Tabanlı Context Manager
from contextlib import contextmanager, suppress, redirect_stdout
# @contextmanager ile basit context manager
@contextmanager
def timer(label: str = ""):
start = time.perf_counter()
try:
yield # with bloğu burada çalışır
finally:
elapsed = time.perf_counter() - start
print(f"[{label}] {elapsed:.4f}s")
with timer("hesaplama"):
result = sum(i**2 for i in range(100_000))
# Geçici dizin context manager
@contextmanager
def temp_directory():
import tempfile, shutil
dirpath = tempfile.mkdtemp()
try:
yield dirpath
finally:
shutil.rmtree(dirpath)
with temp_directory() as tmpdir:
# tmpdir ile çalış, with bloğu bitince silinir
from pathlib import Path
Path(tmpdir, "test.txt").write_text("geçici veri")
# Veritabanı bağlantı havuzu
@contextmanager
def get_connection(pool):
conn = pool.acquire()
try:
yield conn
except Exception:
conn.rollback()
raise
else:
conn.commit()
finally:
pool.release(conn)
# suppress -- belirli exception'ları yok say
with suppress(FileNotFoundError):
os.remove("olmayan_dosya.txt")
# FileNotFoundError olursa sessizce devam eder
# redirect_stdout -- stdout'u yönlendir
import io
f = io.StringIO()
with redirect_stdout(f):
print("Bu stdout'a gitmez")
output = f.getvalue() # "Bu stdout'a gitmez\n"Async Context Manager
import asyncio
from contextlib import asynccontextmanager
# Sınıf bazlı async context manager
class AsyncDBPool:
def __init__(self, dsn: str):
self.dsn = dsn
self.pool = None
async def __aenter__(self):
import asyncpg
self.pool = await asyncpg.create_pool(self.dsn)
return self.pool
async def __aexit__(self, exc_type, exc_val, exc_tb):
if self.pool:
await self.pool.close()
async def main():
async with AsyncDBPool("postgresql://localhost/mydb") as pool:
async with pool.acquire() as conn:
rows = await conn.fetch("SELECT 1")
# @asynccontextmanager ile
@asynccontextmanager
async def http_session():
import aiohttp
session = aiohttp.ClientSession()
try:
yield session
finally:
await session.close()
async def main():
async with http_session() as session:
async with session.get("https://httpbin.org/get") as resp:
data = await resp.json()
# FastAPI lifespan (uygulama yaşam döngüsü)
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# Uygulama başlarken
print("Veritabanı havuzu oluşturuluyor...")
app.state.db_pool = await create_pool()
yield
# Uygulama kapanırken
print("Bağlantılar kapatılıyor...")
await app.state.db_pool.close()
app = FastAPI(lifespan=lifespan)25) Python Kütüphane Ekosistemi
Python'un güçlü yanlarından biri zengin kütüphane ekosistemidir. Bu bölümde en sık kullanılan kütüphaneler anlatılmaktadır.
requests & httpx -- HTTP İstemci
pip install requests httpximport requests
# --- requests (senkron) ---
# GET isteği
resp = requests.get("https://api.github.com/users/fahri", timeout=10)
print(resp.status_code) # 200
print(resp.json()) # JSON olarak parse et
# POST isteği
resp = requests.post(
"https://httpbin.org/post",
json={"name": "Fahri", "role": "dev"},
headers={"Authorization": "Bearer TOKEN"},
timeout=10,
)
# Session -- bağlantı yeniden kullanımı
session = requests.Session()
session.headers.update({"Authorization": "Bearer TOKEN"})
resp = session.get("https://api.example.com/data")
# Dosya indirme
resp = requests.get("https://example.com/file.zip", stream=True)
with open("file.zip", "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)# --- httpx (senkron + asenkron) ---
import httpx
# Senkron kullanım (requests benzeri)
resp = httpx.get("https://httpbin.org/get", timeout=10)
print(resp.json())
# Asenkron kullanım
import asyncio
async def fetch():
async with httpx.AsyncClient() as client:
resp = await client.get("https://httpbin.org/get")
return resp.json()
asyncio.run(fetch())
# HTTP/2 desteği
client = httpx.Client(http2=True)
resp = client.get("https://example.com")
print(resp.http_version) # "HTTP/2"Celery -- Dağıtık Görev Kuyruğu
pip install celery[redis]# celery_app.py
from celery import Celery
app = Celery(
"tasks",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1",
)
# Yapılandırma
app.conf.update(
task_serializer="json",
result_serializer="json",
accept_content=["json"],
timezone="Europe/Istanbul",
task_track_started=True,
result_expires=3600,
)
# Görev tanımla
@app.task(bind=True, max_retries=3)
def send_email(self, to: str, subject: str, body: str):
try:
# E-posta gönderme işlemi
print(f"E-posta gönderiliyor: {to}")
except Exception as exc:
raise self.retry(exc=exc, countdown=60)
@app.task
def process_image(image_path: str) -> str:
# Uzun süren görüntü işleme
return f"İşlendi: {image_path}"
# Periyodik görevler (Celery Beat)
app.conf.beat_schedule = {
"temizlik-her-saat": {
"task": "tasks.cleanup",
"schedule": 3600.0, # saniye
},
}# Görev çağırma
result = send_email.delay("info@fahriaydin.dev", "Test", "Merhaba!")
print(result.id) # Görev ID
print(result.status) # PENDING, STARTED, SUCCESS, FAILURE
print(result.get(timeout=30)) # Sonucu bekle
# Zincirleme görevler
from celery import chain, group, chord
# Sıralı çalıştırma
chain(task1.s(arg1), task2.s(), task3.s())()
# Paralel çalıştırma
group(task1.s(i) for i in range(10))()# Celery worker başlat
celery -A celery_app worker --loglevel=info
# Beat scheduler başlat (periyodik görevler için)
celery -A celery_app beat --loglevel=infoAlembic -- Veritabanı Migration
pip install alembic# Alembic başlat
alembic init alembic
# Migration oluştur
alembic revision --autogenerate -m "create users table"
# Migration uygula
alembic upgrade head
# Bir adım geri al
alembic downgrade -1
# Migration geçmişini gör
alembic history# alembic/env.py -- SQLAlchemy modellerini bağla
from myapp.models import Base
target_metadata = Base.metadata
# Migration dosyası örneği (otomatik oluşturulur)
# alembic/versions/001_create_users.py
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
"users",
sa.Column("id", sa.Integer(), primary_key=True),
sa.Column("name", sa.String(100), nullable=False),
sa.Column("email", sa.String(255), unique=True),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now()),
)
op.create_index("ix_users_email", "users", ["email"])
def downgrade():
op.drop_index("ix_users_email")
op.drop_table("users")SQLAlchemy -- ORM Detaylı
pip install sqlalchemy psycopg2-binaryfrom sqlalchemy import create_engine, Column, Integer, String, ForeignKey, DateTime, func, select
from sqlalchemy.orm import DeclarativeBase, relationship, sessionmaker, Session
# Engine ve Base
engine = create_engine("postgresql://user:pass@localhost/mydb", echo=True)
class Base(DeclarativeBase):
pass
# Modeller
class Author(Base):
__tablename__ = "authors"
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
email = Column(String(255), unique=True)
books = relationship("Book", back_populates="author", cascade="all, delete-orphan")
def __repr__(self):
return f"Author(id={self.id}, name={self.name!r})"
class Book(Base):
__tablename__ = "books"
id = Column(Integer, primary_key=True)
title = Column(String(200), nullable=False)
author_id = Column(Integer, ForeignKey("authors.id"), nullable=False)
published_at = Column(DateTime, server_default=func.now())
author = relationship("Author", back_populates="books")
# Tablo oluştur
Base.metadata.create_all(engine)
SessionLocal = sessionmaker(bind=engine)
# CRUD işlemleri
with SessionLocal() as session:
# Create
author = Author(name="Fahri", email="info@fahriaydin.dev")
author.books.append(Book(title="Python Rehberi"))
session.add(author)
session.commit()
# Read
authors = session.query(Author).filter(Author.name.ilike("%fahri%")).all()
# SQLAlchemy 2.0 tarzı (select)
stmt = select(Book).where(Book.title.contains("Python")).order_by(Book.published_at.desc())
books = session.scalars(stmt).all()
# Update
author.name = "Fahri Aydın"
session.commit()
# Delete
session.delete(author)
session.commit()
# Join sorgusu
stmt = (
select(Book, Author)
.join(Author)
.where(Author.name == "Fahri")
)
results = session.execute(stmt).all()
# Aggregation
from sqlalchemy import func as sa_func
count = session.query(sa_func.count(Book.id)).scalar()Pillow -- Görüntü İşleme
pip install Pillowfrom PIL import Image, ImageDraw, ImageFont, ImageFilter
# Resim aç ve bilgilerini gör
img = Image.open("foto.jpg")
print(img.size) # (1920, 1080)
print(img.format) # JPEG
print(img.mode) # RGB
# Boyutlandırma
thumbnail = img.resize((300, 200))
img.thumbnail((800, 600)) # En-boy oranını korur (in-place)
# Kırpma (crop)
cropped = img.crop((100, 100, 500, 400)) # (left, top, right, bottom)
# Döndürme
rotated = img.rotate(45, expand=True)
# Filtre uygula
blurred = img.filter(ImageFilter.GaussianBlur(radius=5))
sharpened = img.filter(ImageFilter.SHARPEN)
# Metin ekleme
draw = ImageDraw.Draw(img)
draw.text((50, 50), "Merhaba!", fill="white")
# Format dönüştürme ve kaydetme
img.save("output.png")
img.save("output.webp", quality=85)
# Toplu işlem -- dizindeki tüm resimleri küçült
from pathlib import Path
for f in Path("photos").glob("*.jpg"):
with Image.open(f) as im:
im.thumbnail((800, 600))
im.save(f"thumbnails/{f.name}")BeautifulSoup -- HTML/XML Parsing
pip install beautifulsoup4 lxmlfrom bs4 import BeautifulSoup
import requests
# HTML parse et
html = requests.get("https://example.com").text
soup = BeautifulSoup(html, "lxml")
# Element seçme
title = soup.find("title").text
links = soup.find_all("a", href=True)
for link in links:
print(link["href"], link.text)
# CSS seçicileri
items = soup.select("div.item > h2.title")
first_para = soup.select_one("p.intro")
# Attribute ile arama
images = soup.find_all("img", attrs={"class": "thumbnail"})
# Metin çıkarma
text = soup.get_text(separator="\n", strip=True)
# Tablo parse etme
table = soup.find("table")
rows = []
for tr in table.find_all("tr"):
cells = [td.text.strip() for td in tr.find_all(["td", "th"])]
rows.append(cells)Scrapy -- Web Scraping Framework
pip install scrapy# myspider.py
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = ["https://quotes.toscrape.com/"]
def parse(self, response):
for quote in response.css("div.quote"):
yield {
"text": quote.css("span.text::text").get(),
"author": quote.css("small.author::text").get(),
"tags": quote.css("a.tag::text").getall(),
}
# Sonraki sayfa
next_page = response.css("li.next a::attr(href)").get()
if next_page:
yield response.follow(next_page, self.parse)# Spider çalıştır
scrapy runspider myspider.py -o quotes.jsonpython-dotenv -- Ortam Değişkenleri
pip install python-dotenv# .env dosyası
# DATABASE_URL=postgresql://user:pass@localhost/mydb
# SECRET_KEY=super-secret-key-123
# DEBUG=true
from dotenv import load_dotenv
import os
load_dotenv() # .env dosyasını yükle
db_url = os.getenv("DATABASE_URL")
secret = os.getenv("SECRET_KEY")
debug = os.getenv("DEBUG", "false").lower() == "true"click & typer -- Modern CLI Araçları
pip install click typer[all]# --- click ---
import click
@click.command()
@click.argument("name")
@click.option("--count", "-c", default=1, help="Tekrar sayısı")
@click.option("--shout/--no-shout", default=False)
def greet(name, count, shout):
"""NAME kişisini selamla."""
msg = f"Merhaba, {name}!"
if shout:
msg = msg.upper()
for _ in range(count):
click.echo(msg)
if __name__ == "__main__":
greet()# --- typer (tip ipuçları ile CLI) ---
import typer
from typing import Annotated
app = typer.Typer(help="Dosya yönetim aracı")
@app.command()
def process(
input_file: Annotated[str, typer.Argument(help="Girdi dosyası")],
output: Annotated[str, typer.Option("--output", "-o")] = "out.json",
verbose: Annotated[bool, typer.Option("--verbose", "-v")] = False,
):
"""Dosyayı işle ve çıktı oluştur."""
if verbose:
typer.echo(f"İşleniyor: {input_file}")
typer.echo(f"Çıktı: {output}")
if __name__ == "__main__":
app()rich -- Zengin Terminal Çıktısı
pip install richfrom rich.console import Console
from rich.table import Table
from rich.progress import track
from rich import print as rprint
import time
console = Console()
# Renkli çıktı
console.print("[bold red]Hata![/] Dosya bulunamadı.", style="italic")
rprint("[green]Başarılı[/green]")
# Tablo
table = Table(title="Kullanıcılar")
table.add_column("ID", style="cyan")
table.add_column("Ad", style="magenta")
table.add_column("E-posta")
table.add_row("1", "Fahri", "info@fahriaydin.dev")
table.add_row("2", "Ali", "ali@example.com")
console.print(table)
# Progress bar
for item in track(range(100), description="İşleniyor..."):
time.sleep(0.02)
# Loglama
from rich.logging import RichHandler
import logging
logging.basicConfig(
level=logging.INFO,
format="%(message)s",
handlers=[RichHandler()],
)
log = logging.getLogger("myapp")
log.info("Uygulama başlatıldı")schedule & APScheduler -- Zamanlanmış Görevler
pip install schedule APScheduler# --- schedule (basit) ---
import schedule
import time
def job():
print("Görev çalıştı!")
schedule.every(10).seconds.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("09:00").do(job)
schedule.every().monday.at("08:30").do(job)
while True:
schedule.run_pending()
time.sleep(1)# --- APScheduler (gelişmiş) ---
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
scheduler = BackgroundScheduler()
def daily_report():
print("Günlük rapor oluşturuluyor...")
def cleanup():
print("Temizlik yapılıyor...")
# Cron tarzı zamanlama
scheduler.add_job(daily_report, CronTrigger(hour=9, minute=0))
scheduler.add_job(cleanup, "interval", hours=6)
scheduler.start()boto3 -- AWS SDK
pip install boto3import boto3
# S3 işlemleri
s3 = boto3.client("s3")
# Dosya yükle
s3.upload_file("local.txt", "my-bucket", "remote/path/file.txt")
# Dosya indir
s3.download_file("my-bucket", "remote/path/file.txt", "downloaded.txt")
# Dosyaları listele
response = s3.list_objects_v2(Bucket="my-bucket", Prefix="uploads/")
for obj in response.get("Contents", []):
print(obj["Key"], obj["Size"])
# Presigned URL oluştur (geçici erişim)
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "my-bucket", "Key": "private/file.pdf"},
ExpiresIn=3600, # 1 saat
)marshmallow & Pydantic -- Veri Doğrulama
pip install marshmallow pydantic[email]# --- marshmallow ---
from marshmallow import Schema, fields, validate, post_load
class UserSchema(Schema):
name = fields.Str(required=True, validate=validate.Length(min=2, max=100))
email = fields.Email(required=True)
age = fields.Int(validate=validate.Range(min=0, max=150))
tags = fields.List(fields.Str())
@post_load
def make_user(self, data, **kwargs):
return User(**data)
schema = UserSchema()
result = schema.load({"name": "Fahri", "email": "info@fahriaydin.dev", "age": 25})
errors = schema.validate({"name": "", "email": "invalid"})
# errors: {'name': ['Shorter than minimum length 2.'], 'email': ['Not a valid email address.']}# --- Pydantic (daha modern) ---
from pydantic import BaseModel, EmailStr, field_validator
class User(BaseModel):
name: str
email: EmailStr
age: int
@field_validator("age")
@classmethod
def check_age(cls, v):
if v < 0 or v > 150:
raise ValueError("Geçersiz yaş")
return v
user = User(name="Fahri", email="info@fahriaydin.dev", age=25)
print(user.model_dump()) # dict
print(user.model_dump_json()) # JSON string26) Veri Bilimi Ek Araçlar
NumPy ve Pandas dışında veri bilimi ekosisteminin önemli kütüphaneleri.
Matplotlib & Seaborn -- Veri Görselleştirme
pip install matplotlib seabornimport matplotlib.pyplot as plt
import numpy as np
# --- Temel çizgi grafiği ---
x = np.linspace(0, 2 * np.pi, 100)
y1 = np.sin(x)
y2 = np.cos(x)
plt.figure(figsize=(10, 6))
plt.plot(x, y1, label="sin(x)", color="blue", linewidth=2)
plt.plot(x, y2, label="cos(x)", color="red", linestyle="--")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Trigonometrik Fonksiyonlar")
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig("trig.png", dpi=150, bbox_inches="tight")
plt.show()
# --- Bar grafiği ---
categories = ["Python", "JavaScript", "Go", "Rust"]
values = [85, 72, 45, 38]
plt.figure(figsize=(8, 5))
bars = plt.bar(categories, values, color=["#3776ab", "#f7df1e", "#00add8", "#ce422b"])
plt.ylabel("Popülerlik Skoru")
plt.title("Programlama Dilleri")
for bar, val in zip(bars, values):
plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1, str(val), ha="center")
plt.tight_layout()
plt.savefig("languages.png")
# --- Alt grafikler (subplots) ---
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes[0, 0].plot(x, y1)
axes[0, 0].set_title("sin(x)")
axes[0, 1].scatter(np.random.rand(50), np.random.rand(50), alpha=0.6)
axes[0, 1].set_title("Scatter")
axes[1, 0].hist(np.random.randn(1000), bins=30, edgecolor="black")
axes[1, 0].set_title("Histogram")
axes[1, 1].pie([30, 25, 20, 15, 10], labels=["A", "B", "C", "D", "E"], autopct="%1.1f%%")
axes[1, 1].set_title("Pasta")
plt.tight_layout()
plt.savefig("subplots.png")# --- Seaborn (istatistiksel görselleştirme) ---
import seaborn as sns
import pandas as pd
# Seaborn teması
sns.set_theme(style="whitegrid")
# Örnek veri
df = pd.DataFrame({
"ay": ["Oca", "Şub", "Mar", "Nis", "May", "Haz"] * 2,
"satış": [120, 135, 148, 162, 178, 195, 95, 108, 122, 138, 145, 168],
"bölge": ["İstanbul"] * 6 + ["Ankara"] * 6,
})
# Çizgi grafiği
plt.figure(figsize=(10, 6))
sns.lineplot(data=df, x="ay", y="satış", hue="bölge", marker="o")
plt.title("Aylık Satışlar")
plt.savefig("sales.png")
# Heatmap (korelasyon matrisi)
numeric_df = pd.DataFrame(np.random.rand(5, 5), columns=list("ABCDE"))
plt.figure(figsize=(8, 6))
sns.heatmap(numeric_df.corr(), annot=True, cmap="coolwarm", center=0)
plt.title("Korelasyon Matrisi")
plt.savefig("correlation.png")
# Box plot
plt.figure(figsize=(8, 5))
sns.boxplot(data=df, x="bölge", y="satış")
plt.title("Bölgeye Göre Satış Dağılımı")
plt.savefig("boxplot.png")scikit-learn -- Makine Öğrenimi Temelleri
pip install scikit-learnfrom sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report
import numpy as np
# Örnek veri (Iris dataset)
from sklearn.datasets import load_iris
iris = load_iris()
X, y = iris.data, iris.target
# Veriyi böl
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Ölçeklendirme
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# Model eğitimi
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train_scaled, y_train)
# Tahmin ve değerlendirme
y_pred = model.predict(X_test_scaled)
print(f"Doğruluk: {accuracy_score(y_test, y_pred):.4f}")
print(classification_report(y_test, y_pred, target_names=iris.target_names))
# Pipeline -- ön işleme + model
from sklearn.pipeline import Pipeline
pipeline = Pipeline([
("scaler", StandardScaler()),
("classifier", LogisticRegression(max_iter=200)),
])
pipeline.fit(X_train, y_train)
score = pipeline.score(X_test, y_test)
print(f"Pipeline doğruluk: {score:.4f}")
# Cross-validation
from sklearn.model_selection import cross_val_score
scores = cross_val_score(pipeline, X, y, cv=5, scoring="accuracy")
print(f"CV Ortalama: {scores.mean():.4f} (+/- {scores.std():.4f})")OpenCV -- Görüntü İşleme Temelleri
pip install opencv-pythonimport cv2
import numpy as np
# Resim okuma ve gösterme
img = cv2.imread("foto.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(img.shape) # (yükseklik, genişlik, kanal)
# Boyutlandırma
resized = cv2.resize(img, (640, 480))
resized2 = cv2.resize(img, None, fx=0.5, fy=0.5)
# Bulanıklaştırma
blurred = cv2.GaussianBlur(img, (5, 5), 0)
# Kenar algılama (Canny)
edges = cv2.Canny(gray, 50, 150)
# Yüz algılama (Haar Cascade)
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5)
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
# Kaydetme
cv2.imwrite("output.jpg", img)
# Video yakalama (webcam)
cap = cv2.VideoCapture(0)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
cv2.imshow("Kamera", frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()Jupyter Notebook
pip install jupyterlab notebook# Jupyter Lab başlat
jupyter lab
# Klasik notebook başlat
jupyter notebook
# Komut satırından notebook çalıştır
jupyter nbconvert --to script notebook.ipynb # .py'ye dönüştür
jupyter nbconvert --execute notebook.ipynb # Çalıştır ve çıktıları kaydet
jupyter nbconvert --to html notebook.ipynb # HTML'e dönüştür# Notebook içinde sık kullanılan sihirli komutlar (magic commands)
# %timeit -- performans ölçümü
# %timeit sum(range(10000))
# %%time -- hücre süresi ölçümü
# %%time
# result = heavy_computation()
# %matplotlib inline -- grafikleri notebook içinde göster
# !pip install paket -- hücre içinden paket kur
# %who -- tanımlı değişkenleri listele
# %load_ext autoreload
# %autoreload 2 -- modül değişikliklerini otomatik yükle27) Güvenlik (Security)
Web uygulamalarında güvenlik kritik öneme sahiptir. Bu bölümde yaygın güvenlik açıkları ve önlemleri anlatılmaktadır.
Input Validation
import re
from pydantic import BaseModel, field_validator, EmailStr
# Kullanıcı girdisini ASLA doğrudan kullanma
# Her zaman validate et ve sanitize et
class RegisterInput(BaseModel):
username: str
email: EmailStr
password: str
@field_validator("username")
@classmethod
def validate_username(cls, v):
if not re.match(r"^[a-zA-Z0-9_]{3,20}$", v):
raise ValueError("Kullanıcı adı 3-20 karakter, alfanümerik olmalı")
return v
@field_validator("password")
@classmethod
def validate_password(cls, v):
if len(v) < 8:
raise ValueError("Şifre en az 8 karakter olmalı")
if not re.search(r"[A-Z]", v):
raise ValueError("En az bir büyük harf gerekli")
if not re.search(r"[0-9]", v):
raise ValueError("En az bir rakam gerekli")
return vSQL Injection Koruması
# YANLIS -- SQL injection'a açık
def get_user_unsafe(username):
query = f"SELECT * FROM users WHERE name = '{username}'"
cursor.execute(query) # TEHLİKELİ!
# DOGRU -- Parametreli sorgu kullan
def get_user_safe(username):
cursor.execute("SELECT * FROM users WHERE name = %s", (username,))
# SQLAlchemy ile -- ORM zaten parametreli sorgu kullanır
user = session.query(User).filter(User.name == username).first()
# Ham sorgu gerekirse
from sqlalchemy import text
result = session.execute(
text("SELECT * FROM users WHERE name = :name"),
{"name": username}
)XSS (Cross-Site Scripting) Koruması
import html
# Kullanıcı girdisini HTML'de gösterirken escape et
user_input = '<script>alert("hack")</script>'
safe_output = html.escape(user_input)
# <script>alert("hack")</script>
# Jinja2 template'lerde otomatik escape aktiftir
# {{ user_input }} -- otomatik escape edilir
# {{ user_input | safe }} -- TEHLIKELI! Sadece güvenli veri için kullan
# Markdown render ederken
import bleach
allowed_tags = ["b", "i", "u", "a", "p", "br", "ul", "ol", "li", "code", "pre"]
allowed_attrs = {"a": ["href", "title"]}
clean_html = bleach.clean(
user_html,
tags=allowed_tags,
attributes=allowed_attrs,
strip=True,
)CSRF (Cross-Site Request Forgery) Koruması
# Flask-WTF ile CSRF koruması
# pip install flask-wtf
from flask_wtf import FlaskForm, CSRFProtect
from wtforms import StringField, SubmitField
app.config["SECRET_KEY"] = "gizli-anahtar"
csrf = CSRFProtect(app)
class PostForm(FlaskForm):
title = StringField("Başlık")
submit = SubmitField("Gönder")
# Template'de form kullanımı:
# <form method="POST">
# {{ form.hidden_tag() }} <!-- CSRF token -->
# {{ form.title() }}
# {{ form.submit() }}
# </form>
# Django'da CSRF varsayılan olarak aktiftir
# Template'de: {% csrf_token %}
# FastAPI'de CSRF genellikle gereksiz (token-based auth kullanılır)
# Ama cookie-based auth ile kullanılıyorsa:
# pip install fastapi-csrf-protectpickle & eval Tehlikeleri
# TEHLIKE: pickle güvenilmeyen veriyle kullanılmamalı
# pickle, rastgele Python kodu çalıştırabilir!
import pickle
# YANLIS -- güvenilmeyen kaynaktan gelen pickle verisi
# data = pickle.loads(untrusted_data) # TEHLIKELI!
# DOGRU -- JSON veya güvenli format kullan
import json
data = json.loads(trusted_json_string)
# eval() ASLA kullanıcı girdisiyle kullanılmamalı
# YANLIS
# result = eval(user_input) # TEHLIKELI!
# DOGRU -- ast.literal_eval sadece literal ifadeler için güvenli
import ast
safe_result = ast.literal_eval("[1, 2, 3]") # Sadece literal değerlerDosya Yükleme Güvenliği
import os
from pathlib import Path
from werkzeug.utils import secure_filename
UPLOAD_DIR = Path("uploads")
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".pdf"}
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB
def is_safe_upload(filename: str, file_size: int) -> bool:
# Dosya uzantısını kontrol et
ext = Path(filename).suffix.lower()
if ext not in ALLOWED_EXTENSIONS:
return False
# Dosya boyutunu kontrol et
if file_size > MAX_FILE_SIZE:
return False
# Güvenli dosya adı oluştur
safe_name = secure_filename(filename)
if not safe_name:
return False
return True
def save_upload(file, filename: str) -> Path:
safe_name = secure_filename(filename)
# Path traversal saldırısını engelle
dest = UPLOAD_DIR / safe_name
dest = dest.resolve()
if not str(dest).startswith(str(UPLOAD_DIR.resolve())):
raise ValueError("Geçersiz dosya yolu")
file.save(str(dest))
return destCORS Yapılandırması
# Flask
from flask_cors import CORS
# Spesifik origin'lere izin ver (geniş açma!)
CORS(app, origins=["https://mysite.com", "https://app.mysite.com"])
# FastAPI
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://mysite.com"], # ["*"] kullanma!
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)secrets Modülü -- Güvenli Rastgele Değerler
import secrets
# Güvenli token oluştur (random modülü KULLANMA!)
token = secrets.token_hex(32) # 64 karakter hex string
url_token = secrets.token_urlsafe(32) # URL-safe base64 string
# Güvenli şifre oluştur
import string
alphabet = string.ascii_letters + string.digits + string.punctuation
password = "".join(secrets.choice(alphabet) for _ in range(16))
# Güvenli karşılaştırma (timing attack'e karşı)
secrets.compare_digest(user_token, stored_token)
# Şifre hashleme (bcrypt veya argon2 kullan)
# pip install bcrypt
import bcrypt
password = "gizli_sifre".encode()
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
is_valid = bcrypt.checkpw(password, hashed)Güvenlik Kontrol Listesi
| Konu | Önlem |
|---|---|
| SQL Injection | Parametreli sorgu veya ORM kullan |
| XSS | HTML escape, Content-Security-Policy header |
| CSRF | CSRF token, SameSite cookie |
| Şifre saklama | bcrypt/argon2 ile hashle, düz metin ASLA |
| Token/Key | secrets modülü, .env dosyası, git'e ekleme |
| Dosya yükleme | Uzantı kontrol, boyut sınırı, secure_filename |
| CORS | Spesifik origin, wildcard kullanma |
| HTTPS | Production'da her zaman HTTPS |
| Dependency | Güncel tut, pip audit ile tarama yap |
| Hata mesajı | Production'da detaylı hata gösterme |
28) Testing: pytest Advanced
Kapsamlı test yazımı, güvenilir yazılımın temelidir.
pip install pytest pytest-cov pytest-mock hypothesispytest Temelleri ve Parametrize
# tests/test_utils.py
import pytest
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError("Sıfıra bölme hatası")
return a / b
def test_add():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, 1) == 0
# Parametrize -- birden fazla test vakası
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(0, 0, 0),
(-1, -1, -2),
(100, 200, 300),
(1.5, 2.5, 4.0),
])
def test_add_params(a, b, expected):
assert add(a, b) == expected
# Exception test etme
def test_divide_by_zero():
with pytest.raises(ValueError, match="Sıfıra bölme"):
divide(10, 0)
# Yaklaşık eşitlik (float karşılaştırma)
def test_float_division():
assert divide(1, 3) == pytest.approx(0.333, abs=0.001)
# Koşullu atla
@pytest.mark.skipif(sys.platform == "win32", reason="Sadece Linux'ta çalışır")
def test_unix_feature():
pass
# Beklenen başarısızlık
@pytest.mark.xfail(reason="Bilinen bug, düzeltilecek")
def test_known_bug():
assert add(1, 1) == 3Fixtures & conftest.py
# tests/conftest.py -- paylaşılan fixture'lar
import pytest
import tempfile
from pathlib import Path
@pytest.fixture
def sample_data():
"""Test verisi sağlar."""
return {
"users": [
{"id": 1, "name": "Fahri", "email": "info@fahriaydin.dev"},
{"id": 2, "name": "Ali", "email": "ali@example.com"},
]
}
@pytest.fixture
def temp_dir():
"""Geçici dizin oluşturur, test sonunda temizler."""
with tempfile.TemporaryDirectory() as tmpdir:
yield Path(tmpdir)
@pytest.fixture(scope="session")
def db_connection():
"""Tüm test oturumu boyunca tek veritabanı bağlantısı."""
conn = create_test_database()
yield conn
conn.close()
drop_test_database()
@pytest.fixture(autouse=True)
def reset_db(db_connection):
"""Her testten önce veritabanını sıfırla."""
db_connection.execute("DELETE FROM users")
db_connection.commit()# tests/test_users.py
def test_user_count(sample_data):
assert len(sample_data["users"]) == 2
def test_temp_file(temp_dir):
file_path = temp_dir / "test.txt"
file_path.write_text("test verisi")
assert file_path.read_text() == "test verisi"
# Fixture parametrize
@pytest.fixture(params=["sqlite", "postgresql"])
def db_engine(request):
if request.param == "sqlite":
return create_sqlite_engine()
return create_postgres_engine()
def test_query(db_engine):
# Her iki veritabanı ile de çalışır
result = db_engine.execute("SELECT 1")
assert result is not NoneMock & Patch
from unittest.mock import patch, MagicMock, AsyncMock
import pytest
# Fonksiyon mocklama
def get_weather(city):
import requests
resp = requests.get(f"https://api.weather.com/{city}")
return resp.json()
def test_get_weather():
mock_response = MagicMock()
mock_response.json.return_value = {"temp": 25, "city": "Istanbul"}
with patch("requests.get", return_value=mock_response) as mock_get:
result = get_weather("istanbul")
assert result["temp"] == 25
mock_get.assert_called_once_with("https://api.weather.com/istanbul")
# pytest-mock ile (daha temiz söz dizimi)
def test_get_weather_mocker(mocker):
mock_resp = MagicMock()
mock_resp.json.return_value = {"temp": 25}
mocker.patch("requests.get", return_value=mock_resp)
result = get_weather("istanbul")
assert result["temp"] == 25
# Async fonksiyon mocklama
async def fetch_data(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.json()
@pytest.mark.asyncio
async def test_fetch_data():
with patch("aiohttp.ClientSession") as mock_session:
mock_resp = AsyncMock()
mock_resp.json.return_value = {"data": "test"}
mock_session.return_value.__aenter__.return_value.get.return_value.__aenter__.return_value = mock_resp
result = await fetch_data("https://example.com/api")
assert result["data"] == "test"Flask/Django/FastAPI Test Client
# --- Flask test ---
import pytest
from myapp import create_app
@pytest.fixture
def client():
app = create_app(testing=True)
app.config["TESTING"] = True
with app.test_client() as client:
yield client
def test_index(client):
resp = client.get("/")
assert resp.status_code == 200
def test_create_item(client):
resp = client.post("/api/items", json={"name": "Test"})
assert resp.status_code == 201
assert resp.json["name"] == "Test"# --- Django test ---
from django.test import TestCase, Client
class PostViewTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user("testuser", password="pass123")
def test_post_list(self):
response = self.client.get("/api/posts/")
self.assertEqual(response.status_code, 200)
def test_create_post_unauthorized(self):
response = self.client.post("/api/posts/", {"title": "Test"})
self.assertEqual(response.status_code, 403)
def test_create_post_authorized(self):
self.client.login(username="testuser", password="pass123")
response = self.client.post("/api/posts/", {
"title": "Test Post",
"body": "Test body",
})
self.assertEqual(response.status_code, 201)# --- FastAPI test ---
from fastapi.testclient import TestClient
from myapp.main import app
client = TestClient(app)
def test_root():
resp = client.get("/")
assert resp.status_code == 200
assert resp.json() == {"message": "Merhaba, FastAPI!"}
def test_create_user():
resp = client.post("/users", json={
"name": "Fahri",
"email": "info@fahriaydin.dev",
"age": 25,
})
assert resp.status_code == 201
data = resp.json()
assert data["name"] == "Fahri"
assert "id" in data
# Async test
import pytest
from httpx import AsyncClient, ASGITransport
@pytest.mark.asyncio
async def test_async_root():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
resp = await ac.get("/")
assert resp.status_code == 200
# Dependency override
from myapp.dependencies import get_db
def override_get_db():
db = TestSessionLocal()
try:
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_dbtox -- Çoklu Ortam Test
pip install tox# tox.ini
[tox]
envlist = py310, py311, py312
[testenv]
deps =
pytest
pytest-cov
commands =
pytest --cov=myapp --cov-report=term-missing {posargs}
[testenv:lint]
deps = ruff
commands = ruff check .# Tüm ortamlarda test çalıştır
tox
# Sadece belirli ortamda
tox -e py312
# Lint ortamını çalıştır
tox -e linthypothesis -- Property-Based Testing
from hypothesis import given, strategies as st, settings
# Property-based test -- rastgele veri ile test et
@given(st.integers(), st.integers())
def test_add_commutative(a, b):
"""Toplama işlemi değişmeli olmalı (a + b == b + a)"""
assert add(a, b) == add(b, a)
@given(st.integers(), st.integers(), st.integers())
def test_add_associative(a, b, c):
"""Toplama işlemi birleşmeli olmalı"""
assert add(add(a, b), c) == add(a, add(b, c))
@given(st.text(min_size=1, max_size=100))
def test_string_reverse(s):
"""String'i iki kez ters çevirmek orijinali verir"""
assert s[::-1][::-1] == s
@given(st.lists(st.integers()))
def test_sort_idempotent(lst):
"""Sıralanmış listeyi tekrar sıralamak sonucu değiştirmez"""
sorted_lst = sorted(lst)
assert sorted(sorted_lst) == sorted_lst
# Daha karmaşık stratejiler
@given(st.dictionaries(
keys=st.text(min_size=1, max_size=10),
values=st.integers(min_value=0, max_value=100),
min_size=1,
max_size=5,
))
@settings(max_examples=200)
def test_dict_operations(d):
assert len(d) <= 5
for key in d:
assert isinstance(d[key], int)Coverage -- Kod Kapsama
# Test çalıştır ve kapsama raporu oluştur
pytest --cov=myapp --cov-report=html --cov-report=term-missing
# Minimum kapsama oranı zorunlu kıl
pytest --cov=myapp --cov-fail-under=80# pyproject.toml veya .coveragerc
[tool.coverage.run]
source = ["myapp"]
omit = ["*/tests/*", "*/migrations/*"]
[tool.coverage.report]
fail_under = 80
show_missing = true
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
"if __name__ == .__main__.",
]29) Lint/Format & Tooling
# Ruff (modern, hızlı -- black + isort + flake8 yerine)
pip install ruff
ruff check . # Lint
ruff format . # Format
# Klasik araçlar
pip install black isort flake8 mypy
black . # Kod formatlama
isort . # Import sıralama
flake8 # Stil kontrolü
mypy . # Tip kontrolü
# Pre-commit
pip install pre-commit
# .pre-commit-config.yaml oluştur, sonra:
pre-commit install30) Deployment & Docker
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]# docker-compose.yml
services:
web:
build: .
ports:
- "5000:5000"
environment:
- FLASK_ENV=production
- DATABASE_URL=postgresql://user:pass@db/mydb
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:31) Tips & Best Practices
Python'da daha iyi, daha okunabilir ve daha performanslı kod yazma ipuçları.
Sanal Ortam (venv/poetry) Her Zaman Kullan
# Her proje için ayrı sanal ortam
python -m venv .venv
source .venv/bin/activate
# Veya poetry ile (pyproject.toml yönetimi)
poetry init
poetry add fastapi uvicorn
poetry install
# Bağımlılıkları kilitle
pip freeze > requirements.txt # pip
poetry lock # poetry
# .gitignore'a ekle
# .venv/
# __pycache__/
# *.pycType Hints Her Yerde Kullan
# YANLIS -- tip belirsiz
def process(data, flag):
if flag:
return data.upper()
return len(data)
# DOGRU -- tip açık
def process(data: str, flag: bool) -> str | int:
if flag:
return data.upper()
return len(data)
# Fonksiyon imzası hem dokümantasyon hem de IDE desteği sağlar
def fetch_users(
page: int = 1,
per_page: int = 20,
active_only: bool = True,
) -> list[dict[str, str]]:
...f-string Formatlama
name = "Fahri"
score = 95.678
# Temel
f"Merhaba, {name}!"
# Sayı formatlama
f"Skor: {score:.2f}" # "Skor: 95.68"
f"Büyük sayı: {1_000_000:,}" # "Büyük sayı: 1,000,000"
f"Yüzde: {0.956:.1%}" # "Yüzde: 95.6%"
# Hizalama
f"{'sol':<10}|{'orta':^10}|{'sag':>10}"
# Debug (Python 3.8+)
x = 42
f"{x = }" # "x = 42"
f"{x + 1 = }" # "x + 1 = 43"
# Tarih formatlama
from datetime import datetime
now = datetime.now()
f"{now:%Y-%m-%d %H:%M:%S}" # "2026-04-09 14:30:00"pathlib > os.path
from pathlib import Path
# YANLIS (eski yol)
import os
path = os.path.join("data", "output", "file.txt")
os.makedirs(os.path.dirname(path), exist_ok=True)
# DOGRU (modern yol)
path = Path("data") / "output" / "file.txt"
path.parent.mkdir(parents=True, exist_ok=True)
# Dosya işlemleri
path.write_text("içerik", encoding="utf-8")
content = path.read_text(encoding="utf-8")
# Glob
for py_file in Path("src").rglob("*.py"):
print(py_file)
# Dosya bilgileri
path.exists()
path.is_file()
path.is_dir()
path.suffix # ".txt"
path.stem # "file"
path.name # "file.txt"dataclass Kullan
# YANLIS -- boilerplate fazla
class User:
def __init__(self, id, name, email):
self.id = id
self.name = name
self.email = email
def __repr__(self):
return f"User(id={self.id}, name={self.name!r})"
def __eq__(self, other):
return self.id == other.id
# DOGRU -- dataclass
from dataclasses import dataclass, field
@dataclass
class User:
id: int
name: str
email: str
tags: list[str] = field(default_factory=list)
# __init__, __repr__, __eq__ otomatik oluşturulur
# frozen=True ile immutable yapılabilir
@dataclass(frozen=True)
class Point:
x: float
y: floatComprehension vs Loop
# Basit dönüşümler için comprehension tercih et
# YANLIS (loop)
result = []
for x in range(10):
if x % 2 == 0:
result.append(x ** 2)
# DOGRU (comprehension)
result = [x ** 2 for x in range(10) if x % 2 == 0]
# Karmaşık mantık için loop kullan (okunabilirlik)
# Comprehension tek satırda anlaşılmıyorsa loop daha iyi
# Generator -- büyük veri için
total = sum(x ** 2 for x in range(1_000_000)) # Bellek dostulogging > print
import logging
# logging yapılandırması
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
handlers=[
logging.StreamHandler(),
logging.FileHandler("app.log"),
],
)
logger = logging.getLogger(__name__)
# YANLIS
print("Kullanıcı giriş yaptı")
print(f"HATA: {e}")
# DOGRU
logger.info("Kullanıcı giriş yaptı: %s", username)
logger.error("Veritabanı hatası: %s", e, exc_info=True)
logger.warning("Rate limit aşıldı: IP=%s", ip_address)
logger.debug("Sorgu sonucu: %d kayıt", len(results))
# Avantajlar:
# - Seviye kontrolü (DEBUG, INFO, WARNING, ERROR, CRITICAL)
# - Dosyaya yazma
# - Format kontrolü
# - Production'da debug kapatma32) Hızlı Referans (Cheat Sheet)
| Konu | Komut / Bilgi |
|---|---|
| Venv oluştur | python -m venv .venv |
| Venv aktif et | source .venv/bin/activate |
| Paket kur | pip install paket |
| Freeze | pip freeze > requirements.txt |
| Script çalıştır | python script.py |
| REPL | python veya ipython |
| Test | pytest -v |
| Format | ruff format . veya black . |
| Lint | ruff check . veya flake8 |
| Tip kontrol | mypy . |
| Profiling | python -m cProfile script.py |
| Flask çalıştır | flask run --debug |
| Django çalıştır | python manage.py runserver |
| Django migration | python manage.py makemigrations && python manage.py migrate |
| FastAPI çalıştır | uvicorn main:app --reload |
| Gunicorn | gunicorn -b 0.0.0.0:5000 app:app |
| Celery worker | celery -A app worker --loglevel=info |
| Jupyter | jupyter lab |
| Coverage | pytest --cov=myapp --cov-report=html |
| Güvenlik tarama | pip audit |
Ilgili Rehberler
Backend
- Backend Genel Bakış
- Yazilim Mimarisi
- API Development
- Laravel Rehberi
- ASP.NET Core Guide
- Node.js Rehberi
- AI & LLM