跳至主要内容

5 篇文章 含有標籤「python」

檢視所有標籤

Django「一對一」、「一對多」、「多對多」關聯入門教學筆記 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

當你在學 Django 或資料庫設計時,常會遇到「一對一」、「一對多」、「多對多」這三種關聯(Relationship)。這些是資料庫中表與表之間的關係,下面用簡單的例子幫你搞懂它們的差異:


一對一(One-to-One)

概念:

一個資料只對應另一個資料,彼此之間是唯一配對的關係。

例子:

每個人都有一張身份證 → 一個人對應一張身份證,一張身份證只對應一個人。

Django 實作:

class Person(models.Model):
name = models.CharField(max_length=100)

class IDCard(models.Model):
number = models.CharField(max_length=20)
owner = models.OneToOneField(Person, on_delete=models.CASCADE)

一對多(One-to-Many)

概念:

一筆資料可以對應到多筆資料,但每一筆資料只能對應回唯一一筆上層資料。

例子:

一個作者可以寫很多本書 → 一個作者對應多本書,但一本書只會有一個作者。

Django 實作:

class Author(models.Model):
name = models.CharField(max_length=100)

class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE)

多對多(Many-to-Many)

概念:

一筆資料可以對應到多筆資料,而對方也可以對應回來多筆資料。

例子:

學生選修多門課程,一門課也有很多學生 → 學生對多門課,課程對多位學生。

Django 實作:

class Student(models.Model):
name = models.CharField(max_length=100)
courses = models.ManyToManyField('Course')

class Course(models.Model):
title = models.CharField(max_length=100)

(也可以在 Course 裡加 students = models.ManyToManyField(Student),結果會一樣)


小整理比較表:

類型關係形式範例Django 欄位
一對一A ➝ B 且 B ➝ A人 → 身份證OneToOneField
一對多A ➝ 多個 B作者 → 書ForeignKey
多對多A ⇄ 多個 B,互相多對多學生 ⇄ 課程ManyToManyField

Django CRUD(不使用 ModelForm)入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

在 Django 中,ModelForm 提供了一個快速建立表單與驗證的工具,但在某些情境下,我們可能希望自己掌控表單結構與驗證流程。這篇筆記將示範如何不依賴 ModelForm,手動實作一套 CRUD 系統,幫助你更深入理解 Django 表單處理的基本原理。

我們將製作一個簡單的「書籍管理系統」,支援新增(Create)、讀取(Read)、更新(Update)與刪除(Delete)書籍資訊。

1. 建立 Django 專案與應用

首先,安裝 Django 並建立新的專案與應用:

pip install django
django-admin startproject myproject
cd myproject
python manage.py startapp books

註冊 books 應用於 myproject/settings.pyINSTALLED_APPS

INSTALLED_APPS = [
...
'books',
]

2. 定義模型(Model)

books/models.py 中定義一個簡單的書籍模型:

from django.db import models

class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
published_date = models.DateField()

def __str__(self):
return self.title

遷移資料庫:

python manage.py makemigrations
python manage.py migrate

3. 撰寫 Views(不使用 ModelForm)

books/views.py 中撰寫手動處理的 CRUD 功能。

新增書籍(Create)

from django.shortcuts import render, redirect
from .models import Book
from django.utils.dateparse import parse_date

def create_book(request):
if request.method == 'POST':
title = request.POST.get('title')
author = request.POST.get('author')
published_date_str = request.POST.get('published_date')
published_date = parse_date(published_date_str)

if title and author and published_date:
Book.objects.create(title=title, author=author, published_date=published_date)
return redirect('book_list')
else:
error = "所有欄位皆為必填"
return render(request, 'books/book_form.html', {'error': error})
return render(request, 'books/book_form.html')

讀取書籍(Read)

def book_list(request):
books = Book.objects.all()
return render(request, 'books/book_list.html', {'books': books})

更新書籍(Update)

from django.shortcuts import get_object_or_404

def update_book(request, pk):
book = get_object_or_404(Book, pk=pk)
if request.method == 'POST':
title = request.POST.get('title')
author = request.POST.get('author')
published_date_str = request.POST.get('published_date')
published_date = parse_date(published_date_str)

if title and author and published_date:
book.title = title
book.author = author
book.published_date = published_date
book.save()
return redirect('book_list')
else:
error = "所有欄位皆為必填"
return render(request, 'books/book_form.html', {'book': book, 'error': error})
return render(request, 'books/book_form.html', {'book': book})

刪除書籍(Delete)

def delete_book(request, pk):
book = get_object_or_404(Book, pk=pk)
if request.method == 'POST':
book.delete()
return redirect('book_list')
return render(request, 'books/book_confirm_delete.html', {'book': book})

4. 設定 URL 路由

books/urls.py 中設定對應的路由:

from django.urls import path
from . import views

urlpatterns = [
path('', views.book_list, name='book_list'),
path('create/', views.create_book, name='create_book'),
path('update/<int:pk>/', views.update_book, name='update_book'),
path('delete/<int:pk>/', views.delete_book, name='delete_book'),
]

並在 myproject/urls.py 引入 books 路由:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('books/', include('books.urls')),
]

5. 建立模板(Templates)

手動撰寫簡單的 HTML 表單與顯示畫面。

book_list.html

<h1>書籍列表</h1>
<a href="{% url 'create_book' %}">新增書籍</a>
<ul>
{% for book in books %}
<li>
{{ book.title }} - {{ book.author }} - {{ book.published_date }}
<a href="{% url 'update_book' book.id %}">編輯</a>
<a href="{% url 'delete_book' book.id %}">刪除</a>
</li>
{% endfor %}
</ul>

book_form.html

<h1>{% if book %}編輯書籍{% else %}新增書籍{% endif %}</h1>

{% if error %}
<p style="color:red;">{{ error }}</p>
{% endif %}

<form method="post">
{% csrf_token %}
<p>
標題:<input type="text" name="title" value="{{ book.title|default_if_none:'' }}">
</p>
<p>
作者:<input type="text" name="author" value="{{ book.author|default_if_none:'' }}">
</p>
<p>
出版日期(格式 yyyy-mm-dd):<input type="text" name="published_date" value="{{ book.published_date|default_if_none:'' }}">
</p>
<button type="submit">儲存</button>
</form>

<a href="{% url 'book_list' %}">返回列表</a>

book_confirm_delete.html

<h1>刪除書籍</h1>
<p>確定要刪除 "{{ book.title }}" 嗎?</p>
<form method="post">
{% csrf_token %}
<button type="submit">確定刪除</button>
</form>
<a href="{% url 'book_list' %}">取消</a>

6. 啟動伺服器測試

啟動 Django 開發伺服器:

python manage.py runserver

在瀏覽器開啟 http://127.0.0.1:8000/books/,你將可以新增、查詢、編輯和刪除書籍。


總結

這篇教學示範了在 不使用 ModelForm 的情況下,手動撰寫表單處理與資料驗證,完整實作了 Django 的 CRUD 功能。

這種方式的優點在於靈活度高,你可以完全控制表單的結構、驗證邏輯與錯誤處理,非常適合需要客製化表單行為或前後端分離的專案。不過,相較於使用 ModelForm,開發成本略高,也容易產生重複程式碼,因此適時選擇工具是重要的工程判斷。

進一步的優化方向包括:

  • 加入更完整的資料驗證
  • 增加欄位格式錯誤提示
  • 使用 JavaScript 增強表單互動
  • 將表單資料與邏輯封裝成 Class-Based Views(CBV)

透過本篇範例,希望你對 Django 低階處理表單與 CRUD 流程有更深入的理解。

FastAPI 入門教學筆記:打造現代 Python Web API 的入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

什麼是 FastAPI?

FastAPI 是一個現代、快速(高效能)、基於 Python 3.7+ 類型提示的 Web 框架,用於建構 API。其核心優勢包含:

  • 自動生成文件:內建 OpenAPI 與 Swagger UI 支援
  • 高效能:基於 Starlette 和 Pydantic,效能可媲美 Node.js 與 Go
  • 開發快速:強大的 IDE 支援與自動補全功能
  • 自動驗證與序列化:透過 Pydantic 型別自動完成

FastAPI 適合快速構建 RESTful API,尤其在開發微服務、機器學習模型部署、或任何 API 後端都非常合適。


快速開始:環境準備與安裝

建議使用虛擬環境管理專案依賴。

python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install fastapi uvicorn
  • fastapi:核心框架
  • uvicorn:ASGI Server,用來啟動應用程式

第一個 FastAPI 程式:Hello API

# main.py

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"message": "Hello, FastAPI!"}

啟動應用程式:

uvicorn main:app --reload
  • --reload:啟用熱重載,開發時會自動重新載入程式

訪問 http://127.0.0.1:8000/,你會看到:

{
"message": "Hello, FastAPI!"
}

自動生成的互動式 API 文件

FastAPI 自動提供兩個 API 文件頁面:

  • Swagger UI:http://127.0.0.1:8000/docs
  • Redoc:http://127.0.0.1:8000/redoc

這些文件會根據程式中的路由與型別提示自動生成,方便前後端協作與測試。


使用路由參數與查詢參數

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}

範例請求:

GET /items/42?q=fastapi

回應結果:

{
"item_id": 42,
"q": "fastapi"
}
  • item_id 是路由參數,會自動轉換為 int
  • q 是查詢參數,預設為 None

接收與驗證請求資料:使用 Pydantic 模型

from pydantic import BaseModel

class Item(BaseModel):
name: str
description: str = None
price: float
in_stock: bool = True

@app.post("/items/")
def create_item(item: Item):
return {"message": "Item created", "item": item}

發送 POST 請求:

POST /items/
{
"name": "Laptop",
"description": "A powerful device",
"price": 1299.99,
"in_stock": true
}

回應:

{
"message": "Item created",
"item": {
"name": "Laptop",
"description": "A powerful device",
"price": 1299.99,
"in_stock": true
}
}
  • Pydantic 會自動進行資料驗證與轉換
  • FastAPI 可根據模型自動生成 API 文件

表單與檔案上傳支援

from fastapi import Form, UploadFile, File

@app.post("/submit/")
def submit_form(username: str = Form(...), file: UploadFile = File(...)):
return {
"username": username,
"filename": file.filename,
"content_type": file.content_type
}

這對於處理使用者上傳檔案與表單資料非常方便。


回傳自定義 HTTP 狀態碼與錯誤處理

from fastapi import HTTPException

@app.get("/users/{user_id}")
def read_user(user_id: int):
if user_id != 1:
raise HTTPException(status_code=404, detail="User not found")
return {"user_id": user_id, "name": "Alice"}

這會回傳:

{
"detail": "User not found"
}

並帶有 HTTP 404 錯誤。


小結與下一步學習方向

FastAPI 提供了一種現代化且優雅的方式來構建 API:

  • 強大的型別檢查與 IDE 支援
  • 直覺的程式結構與文件生成
  • 高效能適合用於生產環境

建議學習方向:

  1. 路由分割與模組化管理
  2. 使用依賴注入(Depends)
  3. 整合資料庫(如 SQLAlchemy)
  4. JWT 身份驗證與 OAuth2
  5. 測試與部署(例如 Docker、Gunicorn)

如果你是從 Flask 或 Django REST Framework 轉過來,會發現 FastAPI 提供了相當先進的開發體驗與高效能,是非常值得學習與投入的框架。

參考文件

  1. fastapi API

Python 指定使用 OpenSSL 介紹入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

如果 python -c "import ssl; print(ssl.OPENSSL_VERSION)" 顯示的是 LibreSSL 2.8.3,這表示 Python 並沒有使用 Homebrew 安裝的 OpenSSL,而是使用了系統預設的 LibreSSL。這通常發生在 Python 編譯時,沒有正確地連結到 Homebrew 的 OpenSSL 庫。

為了解決這個問題,請按照以下步驟操作:

步驟 1:重新安裝 Python 並指定使用 OpenSSL

首先,我們需要確保 Python 使用 Homebrew 安裝的 OpenSSL,而不是系統預設的 LibreSSL。

  1. 卸載現有的 Python 版本(如果是從系統預設安裝的)

    使用 pyenv 安裝 Python 會讓你更方便地管理不同版本的 Python,因此我們建議使用 pyenv

  2. 安裝 Homebrew OpenSSL 和 pyenv(如果尚未安裝)

    安裝 Homebrew(如果尚未安裝):

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

    安裝 pyenv:

    brew install pyenv

    安裝 Homebrew 的 OpenSSL:

    brew install openssl@3
  3. 配置 pyenv 使用 Homebrew OpenSSL

    .zshrc.bash_profile 中添加以下配置來設置編譯環境變數,確保 Python 使用 Homebrew 安裝的 OpenSSL:

    export LDFLAGS="-L/opt/homebrew/opt/openssl@3/lib -L/opt/homebrew/opt/readline/lib -L/opt/homebrew/opt/zlib/lib"
    export CPPFLAGS="-I/opt/homebrew/opt/openssl@3/include -I/opt/homebrew/opt/readline/include -I/opt/homebrew/opt/zlib/include"
    export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@3/lib/pkgconfig"

    然後運行:

    source ~/.zshrc  # 或者 source ~/.bash_profile

步驟 2:重新安裝 Python 版本

現在,你可以使用 pyenv 來安裝 Python,並確保它與 Homebrew 的 OpenSSL 兼容。

  1. 安裝 Python 版本(例如,Python 3.9.18):

    pyenv install 3.9.18
  2. 設置全域 Python 版本:

    pyenv global 3.9.18
  3. 確認 Python 是否正確安裝並使用 OpenSSL:

    python -c "import ssl; print(ssl.OPENSSL_VERSION)"

如果這時顯示的是 OpenSSL 1.1.1 或者更高版本,則說明你已經成功解決了這個問題,並且 Python 現在是使用 Homebrew 安裝的 OpenSSL。

步驟 3:創建虛擬環境

如果你使用 pyenv 安裝的 Python,並且已經確認它使用了 OpenSSL,那麼可以重新創建虛擬環境:

  1. 創建虛擬環境:

    python -m venv .venv
  2. 激活虛擬環境:

    source .venv/bin/activate
  3. 再次檢查 OpenSSL 版本:

    python -c "import ssl; print(ssl.OPENSSL_VERSION)"

步驟 4:安裝其他依賴

在虛擬環境中,我們可以安裝任何其他的套件,並且確保它們與正確的 OpenSSL 版本兼容。

pip install <package-name>

Python Decorator 入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在 Python 的日常開發中,decorator(裝飾器) 是一個非常強大的語法工具,常用於增強函式功能,例如:記錄日誌、驗證權限、計時、快取等。在許多框架(如 Flask、Django)或第三方函式庫中,也可以經常看到裝飾器的身影。

然而對初學者來說,decorator 的語法可能一開始比較難以理解,尤其涉及到函式是「第一類物件(first-class object)」、「閉包(closure)」的概念。本篇筆記將循序漸進帶你理解 decorator 的本質與應用方式。


重點摘要

  • Python 函式是第一類物件:可以作為參數傳遞、作為回傳值、賦值給變數。
  • 閉包(Closure):內部函式可以存取外部函式的變數,函式結束後變數仍可存活。
  • Decorator 是一種語法糖,本質是「接收一個函式,回傳一個新的函式」的高階函式。
  • 使用 @ 語法糖可以簡潔地套用裝飾器
  • 裝飾器可用於邏輯共用、權限驗證、效能監控、快取等實務情境
  • functools.wraps 可保持被裝飾函式的名稱與 docstring 資訊

一、基礎概念與語法

1. 函式是物件

def greet(name):
return f"Hello, {name}"

say_hello = greet
print(say_hello("Alice")) # Hello, Alice

函式可以被賦值給變數、作為參數傳遞,也能作為回傳值。


2. 函式作為參數的例子

def do_twice(func):
def wrapper():
func()
func()
return wrapper

def say_hi():
print("Hi!")

do_twice(say_hi)()

這就是簡單的裝飾器雛形,do_twice 接收一個函式,回傳一個新的函式。


二、實作一個簡單 decorator

1. 不帶參數的 decorator

def my_decorator(func):
def wrapper():
print("執行前")
func()
print("執行後")
return wrapper

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

say_hello()

輸出:

執行前
Hello!
執行後

@my_decorator 這行等同於 say_hello = my_decorator(say_hello)。裝飾器會接手原本的函式,包裝成新的邏輯後回傳。


2. 處理有參數的函式

def my_decorator(func):
def wrapper(*args, **kwargs):
print("執行前")
result = func(*args, **kwargs)
print("執行後")
return result
return wrapper

@my_decorator
def greet(name):
print(f"Hello, {name}")

greet("KD")

使用 *args, **kwargs 可以支援任何參數的函式。


3. 保留原函式資訊:使用 functools.wraps

import functools

def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("執行前")
return func(*args, **kwargs)
return wrapper

@my_decorator
def add(x, y):
"""加總兩數"""
return x + y

print(add.__name__) # add
print(add.__doc__) # 加總兩數

沒有 functools.wraps,函式的名稱會變成 wrapper,容易影響除錯與文件產生。


三、實用範例:計時 decorator

import time
import functools

def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 執行時間:{end - start:.4f} 秒")
return result
return wrapper

@timer
def slow_function():
time.sleep(1.5)
print("完成耗時操作")

slow_function()

四、進階:帶參數的 decorator

有時我們希望 decorator 接收參數,例如指定權限等,這時候會需要再多一層函式包裝:

def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator

@repeat(3)
def say_hello():
print("Hello!")

say_hello()

repeat(3) 會先執行,回傳 decorator(func),再包裝原函式。


五、常見應用場景

  • 日誌紀錄(logging)
  • 權限驗證(authorization)
  • 執行時間分析(performance monitoring)
  • 快取(caching)
  • API 路由(如 Flask 的 @app.route)

總結

Python 的 decorator 是一個非常實用的語法技巧,一旦理解其本質為「函式的包裝器」,就可以在實務開發中靈活應用。它讓我們可以以簡潔的方式注入共用邏輯,大大提升程式的可讀性與可維護性。

我們在練習 decorator 時,建議搭配日常開發情境,如記錄日誌、印出函式執行時間,從實作中加深理解。當你越熟悉它,便越能體會其在 Python 世界中的威力。