This is what you will build. Close it, go away, come back tomorrow — everything is still there. นี่คือสิ่งที่คุณจะสร้าง ปิดไป เดินออกไป กลับมาพรุ่งนี้ ทุกอย่างยังอยู่ครบ 这就是你要做的东西。关掉它,走开,明天再回来 —— 东西全都还在。
🇬🇧 English
You have written programs that work. But when you close them, everything is gone.
That is the line between an exercise and an app. An app remembers.
We will use sqlite3 to keep the data, and argparse to take orders. Both come free inside Python. There is nothing to install and nothing to break.
The app is for a teacher: who has which homework, and who has finished.
🇹🇭 ไทย
คุณเคยเขียนโปรแกรมที่ทำงานได้แล้ว แต่พอปิดโปรแกรม ทุกอย่างก็หายไปหมด
นั่นคือเส้นแบ่งระหว่างแบบฝึกหัดกับแอปจริง แอปจริงต้องจำได้
เราจะใช้ sqlite3 เก็บข้อมูล และใช้ argparse รับคำสั่ง ทั้งสองตัวมีอยู่ใน Python อยู่แล้ว ไม่ต้องติดตั้ง และไม่มีอะไรพัง
แอปนี้ทำเพื่อครู ว่านักเรียนคนไหนมีการบ้านอะไร และใครทำเสร็จแล้วบ้าง
🇨🇳 中文
你已经写过能跑的程序了。但一关掉,东西全没了。
这就是练习和真正的程序之间的分界线。真正的程序记得住。
我们用 sqlite3 存数据,用 argparse 接收命令。这两个 Python 里都自带,不用装,也不会坏。
这个程序是给老师用的:谁有哪些作业,谁做完了。
0 Get ready · เตรียมตัว · 做好准备
🇬🇧 English
Nothing to install. Really. sqlite3 and argparse are already inside Python.
This matters more than it sounds. A program with no extra libraries will still run in ten years. Nothing can go out of date.
🇹🇭 ไทย
ไม่ต้องติดตั้งอะไรเลย จริง ๆ sqlite3 กับ argparse อยู่ใน Python อยู่แล้ว
เรื่องนี้สำคัญกว่าที่คิด โปรแกรมที่ไม่ต้องพึ่งไลบรารีเสริม จะยังรันได้ในอีกสิบปี ไม่มีอะไรตกรุ่น
🇨🇳 中文
什么都不用装。真的。sqlite3 和 argparse 本来就在 Python 里。
这件事比听起来重要。一个不依赖额外库的程序,十年后照样能跑,没有东西会过时。
mkdir ~/Documents/Code/homework
cd ~/Documents/Code/homework
vim homework.py
1 A file that is a database · ไฟล์ที่เป็นฐานข้อมูล · 一个就是数据库的文件
🇬🇧 English
A database sounds big and scary. SQLite is not. It is one file on your disk.
Run these three lines. Then look in your folder. A new file appeared: homework.db. That file is your whole database.
🇹🇭 ไทย
คำว่าฐานข้อมูลฟังดูใหญ่และน่ากลัว แต่ SQLite ไม่ใช่แบบนั้น มันคือไฟล์เดียวบนเครื่องคุณ
ลองรันสามบรรทัดนี้ แล้วดูในโฟลเดอร์ จะมีไฟล์ใหม่ชื่อ homework.db โผล่มา ไฟล์นั้นคือฐานข้อมูลทั้งหมดของคุณ
🇨🇳 中文
"数据库"听起来又大又吓人。SQLite 不是。它就是你硬盘上的一个文件。
运行这三行,然后看看文件夹。多了一个新文件 homework.db。那个文件就是你的整个数据库。
import sqlite3
conn = sqlite3.connect("homework.db")
print("The database is open.")
python3 homework.py, then ls. You made a database with one line.
python3 homework.py แล้วพิมพ์ ls คุณสร้างฐานข้อมูลด้วยโค้ดบรรทัดเดียว
python3 homework.py,然后 ls。你用一行代码就建了个数据库。
2 A table · ตาราง · 一张表
🇬🇧 English
A database holds tables. A table is like a page in a notebook, with columns at the top and one line for each thing.
Our table is called jobs. Each job has an id, a student, a title, a due date, and whether it is done.
IF NOT EXISTS means: make this table the first time, and do nothing every other time. Now you can run your program twice safely.
🇹🇭 ไทย
ฐานข้อมูลเก็บตาราง ตารางก็เหมือนหน้าหนึ่งในสมุด มีหัวคอลัมน์อยู่ด้านบน และมีหนึ่งบรรทัดต่อหนึ่งเรื่อง
ตารางของเราชื่อ jobs การบ้านแต่ละชิ้นมี id ชื่อนักเรียน ชื่องาน วันกำหนดส่ง และสถานะว่าเสร็จหรือยัง
IF NOT EXISTS แปลว่า สร้างตารางนี้ครั้งแรกครั้งเดียว ครั้งต่อ ๆ ไปไม่ต้องทำอะไร ทีนี้รันโปรแกรมซ้ำได้อย่างปลอดภัย
🇨🇳 中文
数据库里装的是表。一张表就像笔记本上的一页,上面是列名,每一样东西占一行。
我们的表叫 jobs。每份作业有 id、学生、题目、截止日期,还有做完没有。
IF NOT EXISTS 的意思是:第一次就建,以后就什么都不做。这样你的程序跑两次也不会出事。
conn.execute("""CREATE TABLE IF NOT EXISTS jobs (
id INTEGER PRIMARY KEY,
student TEXT NOT NULL,
title TEXT NOT NULL,
due TEXT,
done INTEGER NOT NULL DEFAULT 0
)""")
conn.commit()
3 The student called O'Brien · นักเรียนที่ชื่อ O'Brien · 那个叫 O'Brien 的学生
🇬🇧 English
Now put a row in. There is one right way and one wrong way, and the wrong way looks easier.
The wrong way glues the name into the text yourself. It works — until a student is called O'Brien. That little ' ends the text early and your program breaks.
The right way uses ?. You give the values separately, and Python puts them in safely. It never breaks, whatever the name is.
Always use ?. Every time. This same habit also stops the most common attack on real websites.
🇹🇭 ไทย
ทีนี้มาใส่ข้อมูลหนึ่งแถว มีวิธีที่ถูกหนึ่งวิธี และวิธีที่ผิดหนึ่งวิธี ซึ่งวิธีผิดดูเหมือนจะง่ายกว่า
วิธีที่ผิดคือเอาชื่อไปต่อเป็นข้อความเอง มันใช้ได้ จนกระทั่งมีนักเรียนชื่อ O'Brien เจ้าเครื่องหมาย ' ตัวเล็ก ๆ นั้นจะปิดข้อความก่อนกำหนด แล้วโปรแกรมก็พัง
วิธีที่ถูกคือใช้ ? คุณส่งค่าไปแยกต่างหาก แล้ว Python จะใส่ให้อย่างปลอดภัย ไม่พังไม่ว่าชื่อจะเป็นอะไร
ใช้ ? เสมอ ทุกครั้ง นิสัยเดียวกันนี้ยังช่วยกันการโจมตีที่พบบ่อยที่สุดบนเว็บไซต์จริงด้วย
🇨🇳 中文
现在插入一行。有一种正确的写法和一种错误的写法,而错的那种看起来更简单。
错的写法是自己把名字拼进字符串里。它能用 —— 直到有个学生叫 O'Brien。那个小小的 ' 会把字符串提前结束,你的程序就崩了。
正确的写法用 ?。你把值单独传进去,Python 会安全地放进去。不管名字是什么都不会坏。
永远用 ?。每一次都用。这个习惯还能挡住真实网站上最常见的一种攻击。
# WRONG. Looks fine. Breaks on O'Brien.
conn.execute("INSERT INTO jobs (student, title) VALUES ('" + name + "', '" + title + "')")
# RIGHT. Works with any name, always.
conn.execute("INSERT INTO jobs (student, title) VALUES (?, ?)", (name, title))
conn.commit()
O'Brien with the wrong way. Read the error. Then fix it with ?.
ลองเพิ่มนักเรียนชื่อ O'Brien ด้วยวิธีที่ผิด อ่าน error แล้วแก้ด้วย ?
用错误的写法加一个叫 O'Brien 的学生。读一下报错,然后用 ? 改好它。
4 Where did my data go? · ข้อมูลหายไปไหน · 我的数据去哪了
🇬🇧 English
Read the rows back with SELECT. Every row comes back as a small group of values.
Now the trap. Take out the line conn.commit() and run it again. Your new rows are gone.
SQLite holds your changes to one side until you say "yes, save it". commit() is you saying yes. Forgetting it is the number one reason people think a database is broken.
🇹🇭 ไทย
อ่านข้อมูลกลับมาด้วย SELECT แต่ละแถวจะกลับมาเป็นกลุ่มค่าเล็ก ๆ
ทีนี้ระวังกับดัก ลองลบบรรทัด conn.commit() ออกแล้วรันใหม่ แถวใหม่ของคุณจะหายไป
SQLite จะพักการเปลี่ยนแปลงไว้ก่อน จนกว่าคุณจะบอกว่า "ใช่ บันทึกเลย" ซึ่ง commit() ก็คือการบอกว่าใช่ การลืมบรรทัดนี้คือสาเหตุอันดับหนึ่งที่คนคิดว่าฐานข้อมูลพัง
🇨🇳 中文
用 SELECT 把数据读回来。每一行回来时是一小组值。
现在说说那个坑。把 conn.commit() 这行删掉再跑一次,你新加的行不见了。
SQLite 会先把你的改动放在一边,等你说"好,存吧"。commit() 就是你说好。忘了它,是大家以为数据库坏掉的第一大原因。
for row in conn.execute("SELECT id, student, title FROM jobs"):
print(row)
5 Your program takes orders · โปรแกรมรับคำสั่งได้ · 让程序接收命令
🇬🇧 English
Editing the file every time you add homework is silly. A real app takes orders from the terminal.
argparse does this. add_subparsers lets one program have several commands, like git does.
You get --help for free. Try it.
🇹🇭 ไทย
การแก้ไฟล์ทุกครั้งที่จะเพิ่มการบ้านนั้นไม่เข้าท่า แอปจริง ๆ ต้องรับคำสั่งจาก terminal
argparse ทำหน้าที่นี้ ส่วน add_subparsers ทำให้โปรแกรมเดียวมีหลายคำสั่งได้ เหมือนที่ git ทำ
แล้วคุณจะได้ --help มาฟรี ๆ ลองดู
🇨🇳 中文
每次加作业都要改文件,太傻了。真正的程序从终端接收命令。
argparse 就是干这个的。add_subparsers 让一个程序有好几个命令,就像 git 那样。
而且你会白得一个 --help。试试看。
import argparse
parser = argparse.ArgumentParser(description="Keep track of homework.")
jobs = parser.add_subparsers(dest="command", required=True)
p_add = jobs.add_parser("add", help="add a new job")
p_add.add_argument("student")
p_add.add_argument("title")
p_add.add_argument("--due", default=None)
p_add.set_defaults(run=add)
args = parser.parse_args()
python3 homework.py --help, then python3 homework.py add "Ploy" "Read page 40".
python3 homework.py --help จากนั้น python3 homework.py add "Ploy" "Read page 40"
python3 homework.py --help,然后 python3 homework.py add "Ploy" "Read page 40"。
6 Mark it finished · ทำเครื่องหมายว่าเสร็จแล้ว · 标记为做完了
🇬🇧 English
UPDATE changes a row that is already there. WHERE says which one.
Never write UPDATE without WHERE. Without it, you change every row in the table at once.
rowcount tells you how many rows changed. If it is zero, that id does not exist — so say so, kindly.
🇹🇭 ไทย
UPDATE ใช้แก้แถวที่มีอยู่แล้ว ส่วน WHERE บอกว่าแถวไหน
อย่าเขียน UPDATE โดยไม่มี WHERE เด็ดขาด ถ้าไม่มี คุณจะแก้ทุกแถวในตารางพร้อมกันหมด
rowcount บอกว่ามีกี่แถวที่เปลี่ยนไป ถ้าเป็นศูนย์ แปลว่าไม่มี id นั้นอยู่ ก็บอกผู้ใช้ไปดี ๆ
🇨🇳 中文
UPDATE 改的是已经存在的行,WHERE 说明改哪一行。
千万不要写没有 WHERE 的 UPDATE。没有它,你会一次改掉表里所有的行。
rowcount 告诉你改了几行。如果是零,说明那个 id 不存在 —— 那就好好告诉用户一声。
result = conn.execute("UPDATE jobs SET done = 1 WHERE id = ?", (args.job_id,))
conn.commit()
if result.rowcount == 0:
print("There is no job with id %d." % args.job_id)
return 1
print("Job %d is finished. Well done!" % args.job_id)
7 Count things · นับข้อมูล · 数一数
🇬🇧 English
Here is where a database beats a list. You can ask it to count for you.
GROUP BY student makes one line for each student. COUNT(*) counts their jobs. SUM(done) adds up the finished ones, because done is 1 or 0.
The database does the counting. Your Python just prints it.
🇹🇭 ไทย
ตรงนี้แหละที่ฐานข้อมูลชนะการใช้ลิสต์ธรรมดา เพราะคุณสั่งให้มันนับให้ได้เลย
GROUP BY student ทำให้ได้หนึ่งบรรทัดต่อนักเรียนหนึ่งคน COUNT(*) นับจำนวนงาน SUM(done) บวกงานที่เสร็จแล้ว เพราะค่า done เป็น 1 หรือ 0
ฐานข้อมูลเป็นคนนับ ส่วน Python ของคุณแค่พิมพ์ออกมา
🇨🇳 中文
这里就是数据库比普通列表强的地方:你可以让它替你数。
GROUP BY student 让每个学生占一行。COUNT(*) 数他们有几份作业。SUM(done) 把做完的加起来,因为 done 不是 1 就是 0。
数数是数据库干的,你的 Python 只负责打印。
rows = conn.execute("""SELECT student,
COUNT(*) AS all_jobs,
SUM(done) AS finished
FROM jobs
GROUP BY student
ORDER BY student""").fetchall()
python3 homework.py report. One line for each student, counted by the database.
python3 homework.py report จะได้หนึ่งบรรทัดต่อนักเรียนหนึ่งคน นับโดยฐานข้อมูล
python3 homework.py report。每个学生一行,数字是数据库数出来的。
8 Do not lose the file · อย่าทำไฟล์หาย · 别把文件弄丢了
🇬🇧 English
One trap. sqlite3.connect("homework.db") makes the file where you are standing, not where the program is. Run it from another folder and you get an empty new database, and think you lost everything.
Fix it by tying the file to the program.
Your whole app is now one .py and one .db. To back it up, copy the .db. To give it to another teacher, send both. That is all.
🇹🇭 ไทย
มีกับดักหนึ่งอย่าง sqlite3.connect("homework.db") จะสร้างไฟล์ตรงที่คุณยืนอยู่ ไม่ใช่ตรงที่โปรแกรมอยู่ ถ้ารันจากโฟลเดอร์อื่น คุณจะได้ฐานข้อมูลเปล่าใบใหม่ แล้วนึกว่าข้อมูลหายหมด
แก้ได้ด้วยการผูกไฟล์ไว้กับตัวโปรแกรม
ตอนนี้แอปทั้งหมดของคุณคือไฟล์ .py หนึ่งไฟล์ กับ .db หนึ่งไฟล์ ถ้าจะสำรองข้อมูล ก็คัดลอกไฟล์ .db ถ้าจะส่งให้ครูคนอื่น ก็ส่งทั้งสองไฟล์ แค่นั้นเอง
🇨🇳 中文
有一个坑。sqlite3.connect("homework.db") 会在你当前所在的位置建文件,不是在程序所在的位置。从别的文件夹运行,你会得到一个空的新数据库,然后以为东西全丢了。
把文件绑定到程序上就能解决。
现在你的整个程序就是一个 .py 加一个 .db。要备份,就复制那个 .db。要给别的老师,两个一起发过去。就这么简单。
from pathlib import Path
# Next to this file, not next to wherever you are standing.
DB_PATH = Path(__file__).with_name("homework.db")
conn = sqlite3.connect(DB_PATH)
9 The whole app · ทั้งหมดรวมกัน · 完整的程序
🇬🇧 English
Here is the finished file, with everything joined up and tidied. Download it and compare with your own.
🇹🇭 ไทย
นี่คือไฟล์ที่เสร็จแล้ว รวมทุกอย่างเข้าด้วยกันและจัดให้เรียบร้อย ดาวน์โหลดไปเทียบกับของคุณเองได้
🇨🇳 中文
这是做完的文件,所有东西都拼好、整理过了。下载下来和你自己写的对照一下。
python3 homework.py add "Ploy" "Unit 3 worksheet" --due 2026-09-01
python3 homework.py list --student Ploy
python3 homework.py done 1
python3 homework.py report
10 Now change it · ลองแก้ดู · 现在动手改它
🇬🇧 English
The app is yours now. Most of these are one new command or one new column.
🇹🇭 ไทย
แอปนี้เป็นของคุณแล้ว ส่วนใหญ่แค่เพิ่มคำสั่งใหม่หนึ่งคำสั่ง หรือคอลัมน์ใหม่หนึ่งคอลัมน์
🇨🇳 中文
这个程序现在是你的了。下面大部分只是加一个新命令,或者加一列。
| Try this | How | Level |
|---|---|---|
| Delete a job | A new delete command. DELETE FROM jobs WHERE id = ? | easy |
| Show only late jobs | WHERE due < ? AND done = 0, with today's date. | easy |
| Add a class name | A new column, and a --class flag on add. | easy |
| Count by month | GROUP BY the first 7 letters of due. | harder |
| Undo a "done" | SET done = 0. One word different from step 6. | easy |
| Put it on the web | Keep this file. Add Flask. Same database, a second face on it. | harder |