This is what you will build, shown on a real burning-season morning. On a clean day the bar is almost empty. นี่คือสิ่งที่คุณจะสร้าง ภาพนี้คือเช้าวันหนึ่งในฤดูเผาจริง ๆ ส่วนวันที่อากาศดี แถบจะเกือบว่างเปล่า 这就是你要做的东西,画面是烧芭季节某个真实的早上。空气好的日子,那条杠几乎是空的。
🇬🇧 English
Every year Chiang Mai fills with smoke. People ask each other: can the children play outside today?
There is a number that answers it. It is called PM2.5. It counts the tiny bits of dirt in the air that are small enough to go deep into your lungs.
You will write a program that asks for that number and says what it means. The data is free and open. You do not need an account.
🇹🇭 ไทย
ทุกปีเชียงใหม่เต็มไปด้วยควัน ผู้คนถามกันว่า วันนี้ให้เด็กออกไปเล่นข้างนอกได้ไหม
มีตัวเลขหนึ่งที่ตอบคำถามนี้ได้ ชื่อว่า PM2.5 มันนับฝุ่นจิ๋วในอากาศที่เล็กพอจะเข้าไปลึกถึงปอดของเรา
คุณจะเขียนโปรแกรมที่ไปถามตัวเลขนั้น แล้วบอกว่ามันแปลว่าอะไร ข้อมูลเปิดและใช้ฟรี ไม่ต้องมีบัญชีผู้ใช้
🇨🇳 中文
每年清迈都会被烟笼罩。大家互相问:今天孩子能出去玩吗?
有一个数字能回答这个问题,叫 PM2.5。它数的是空气里那些小到能钻进肺深处的脏东西。
你要写一个程序去问这个数字,再说清楚它意味着什么。数据是开放免费的,不用注册账号。
0 Get ready · เตรียมตัว · 做好准备
🇬🇧 English
One library. requests is how Python asks a website for something.
Everything else in this lesson is free and open. Many websites want you to sign up and give a card number. This one does not, which is why we use it.
🇹🇭 ไทย
ไลบรารีเดียว requests คือวิธีที่ Python ใช้ขอข้อมูลจากเว็บไซต์
ทุกอย่างที่เหลือในบทนี้เปิดและฟรี เว็บไซต์หลายแห่งบังคับให้สมัครสมาชิกและใส่เลขบัตร แต่ที่นี่ไม่ต้อง เราจึงเลือกใช้
🇨🇳 中文
只要一个库。requests 是 Python 向网站要东西的方式。
这一课里其他所有东西都是开放免费的。很多网站要你注册、填卡号,这个不用 —— 所以我们才选它。
pip3 install requests
mkdir ~/Documents/Code/air
cd ~/Documents/Code/air
vim air.py
1 Ask the internet one question · ถามอินเทอร์เน็ตหนึ่งคำถาม · 向互联网问一个问题
🇬🇧 English
An API is a web page made for programs instead of people. You send it numbers, it sends data back.
latitude and longitude say where on Earth you mean. Those two are Chiang Mai.
status_code of 200 means "here you are". 404 means "no such thing". Always look at it before you trust the answer.
🇹🇭 ไทย
API คือหน้าเว็บที่ทำไว้ให้โปรแกรมใช้ ไม่ใช่ให้คนอ่าน คุณส่งตัวเลขไป มันส่งข้อมูลกลับมา
latitude กับ longitude บอกว่าหมายถึงจุดไหนบนโลก สองค่านี้คือเชียงใหม่
status_code เท่ากับ 200 แปลว่า "นี่ครับ" ส่วน 404 แปลว่า "ไม่มีสิ่งนั้น" ให้ดูค่านี้ก่อนเสมอ ก่อนจะเชื่อคำตอบ
🇨🇳 中文
API 是做给程序看的网页,不是给人看的。你发数字过去,它把数据发回来。
latitude 和 longitude 说明地球上的哪个位置。这两个数是清迈。
status_code 等于 200 表示"给你",404 表示"没有这个东西"。相信答案之前,先看这个数。
import requests
API = "https://air-quality-api.open-meteo.com/v1/air-quality"
reply = requests.get(API, timeout=20, params={
"latitude": 18.7883, # Chiang Mai
"longitude": 98.9853,
"current": "pm2_5,pm10,us_aqi",
"timezone": "Asia/Bangkok",
})
print(reply.status_code)
print(reply.text[:200])
200, then a wall of text full of brackets. That is real air data from above your city.
คุณควรเห็น 200 ตามด้วยข้อความยาว ๆ เต็มไปด้วยวงเล็บ นั่นคือข้อมูลอากาศจริงเหนือเมืองของคุณ
你应该会看到 200,然后是一大堆带括号的文字。那就是你城市上空真实的空气数据。
2 That wall of text is a dictionary · ข้อความยาว ๆ นั้นคือ dictionary · 那堆文字其实是个字典
🇬🇧 English
The text is JSON. It looks like a Python dictionary because it works the same way: a name, then a value.
reply.json() turns it into a real dictionary. After that it is only Python you already know.
Print it first and look. Never guess what is inside — open it and see.
🇹🇭 ไทย
ข้อความนั้นคือ JSON หน้าตาเหมือน dictionary ของ Python เพราะทำงานแบบเดียวกัน คือมีชื่อ แล้วมีค่า
reply.json() จะแปลงมันเป็น dictionary จริง ๆ หลังจากนั้นก็เป็น Python ที่คุณรู้จักอยู่แล้ว
พิมพ์ออกมาดูก่อน อย่าเดาว่าข้างในมีอะไร ให้เปิดดูเลย
🇨🇳 中文
那段文字是 JSON。它长得像 Python 的字典,因为原理一样:一个名字,配一个值。
reply.json() 把它变成真正的字典。之后就全是你已经会的 Python 了。
先打印出来看看。永远不要猜里面有什么 —— 打开看。
data = reply.json()
print(data.keys()) # what is in here?
print(data["current"]) # the part we want
print(data["current"]["pm2_5"])
3 What does the number mean? · ตัวเลขนี้แปลว่าอะไร · 这个数字是什么意思
🇬🇧 English
A number on its own helps nobody. Is 55 fine? Is it frightening? Your program should say.
Health people group PM2.5 into bands. We put those bands in a list, and walk down it until the number fits.
Notice the shape: a list of rows, each row a limit and what to say. To change the advice you edit the list, not the code. That is a good habit.
🇹🇭 ไทย
ตัวเลขลอย ๆ ไม่ช่วยใคร ค่า 55 คือปกติ หรือน่ากลัว โปรแกรมของคุณควรบอกได้
ฝ่ายสาธารณสุขแบ่ง PM2.5 ออกเป็นช่วง ๆ เราเอาช่วงพวกนั้นใส่ลิสต์ แล้วไล่ลงไปจนกว่าตัวเลขจะเข้าช่วง
สังเกตรูปแบบนี้ไว้ ลิสต์ของแถว แต่ละแถวมีค่าขีดจำกัดกับข้อความ ถ้าจะแก้คำแนะนำ ก็แก้ที่ลิสต์ ไม่ใช่แก้ที่โค้ด นี่เป็นนิสัยที่ดี
🇨🇳 中文
光有一个数字帮不了任何人。55 算好还是吓人?你的程序应该说清楚。
公共卫生部门把 PM2.5 分成几个区间。我们把这些区间放进一个列表,一行行往下走,直到数字落进某一档。
注意这个形状:一个列表,每行是一个上限加一句话。要改建议就改列表,不用改代码。这是个好习惯。
BANDS = [
(12.0, "GOOD", "Go outside. The air is clean."),
(35.4, "OK", "Most people are fine. Play outside."),
(55.4, "NOT GOOD", "If you cough or have asthma, stay in."),
(150.4, "BAD", "Everyone should stay inside. Close the windows."),
(250.4, "VERY BAD", "Do not go outside. Wear an N95 mask if you must."),
(99999, "DANGEROUS", "Stay inside. Keep the windows shut. Use a filter."),
]
def band_for(pm25):
for limit, word, advice in BANDS:
if pm25 <= limit:
return word, advice
4 See it, do not just read it · ให้เห็นภาพ ไม่ใช่แค่อ่านตัวเลข · 要看得见,不只是读数字
🇬🇧 English
Draw a bar out of # marks. Now you can feel the difference between a clean day and a smoky one without reading anything.
Choosing the scale matters. Our first try used one block for every 8 units. Clean days and quite dirty days then looked the same, because both rounded down to one block.
One block for every 5 units fixed it. If a picture cannot tell two different things apart, the picture is wrong.
🇹🇭 ไทย
วาดแถบด้วยเครื่องหมาย # ทีนี้คุณจะรู้สึกถึงความต่างระหว่างวันอากาศดีกับวันควันเยอะ โดยไม่ต้องอ่านอะไรเลย
การเลือกสเกลสำคัญมาก ครั้งแรกเราใช้หนึ่งบล็อกต่อ 8 หน่วย ผลคือวันอากาศดีกับวันค่อนข้างแย่ดูเหมือนกัน เพราะทั้งคู่ปัดลงเหลือบล็อกเดียว
เปลี่ยนเป็นหนึ่งบล็อกต่อ 5 หน่วยก็แก้ได้ ถ้าภาพแยกของสองอย่างที่ต่างกันไม่ออก แปลว่าภาพนั้นผิด
🇨🇳 中文
用 # 画一条杠。这样不用读任何字,你也能感觉到干净的一天和有烟的一天差多少。
刻度怎么选很重要。我们第一次用一个方块代表 8 个单位,结果干净的日子和相当脏的日子看起来一模一样 —— 因为两个都向下取整成了一个方块。
改成一个方块代表 5 个单位就好了。如果一张图分不出两件不同的事,那这张图就是错的。
def bar(pm25, width=30):
filled = min(int(pm25 / 5), width) # one block = 5 ug/m3
return "#" * max(filled, 1) + "." * (width - max(filled, 1))
bar(5), bar(35) and bar(150). Then change the 5 to 8 and print them again.
ลองพิมพ์ bar(5) bar(35) และ bar(150) แล้วเปลี่ยนเลข 5 เป็น 8 แล้วพิมพ์ใหม่
打印 bar(5)、bar(35) 和 bar(150)。然后把 5 改成 8 再打印一次。
5 Your own village · หมู่บ้านของคุณเอง · 你自己的村子
🇬🇧 English
Put your places in a dictionary. Find the two numbers for anywhere on a map website: right-click, and it shows them.
An honest warning. Ask for Chiang Mai and Doi Suthep and you get exactly the same numbers. We checked. The data comes from a grid of squares about 11 km wide, and both places sit in one square.
So this tells you about your area, not your street. Real data always has a shape like this. Knowing it is part of using it honestly.
🇹🇭 ไทย
เอาสถานที่ของคุณใส่ dictionary หาค่าสองตัวของที่ไหนก็ได้จากเว็บแผนที่ คลิกขวาแล้วมันจะแสดงให้เห็น
คำเตือนตามตรง ถ้าถามค่าของเชียงใหม่กับดอยสุเทพ คุณจะได้ตัวเลขเดียวกันเป๊ะ เราลองแล้ว เพราะข้อมูลมาจากตารางช่องละประมาณ 11 กิโลเมตร และทั้งสองที่อยู่ในช่องเดียวกัน
ฉะนั้นมันบอกเรื่องของพื้นที่คุณ ไม่ใช่ถนนหน้าบ้านคุณ ข้อมูลจริงมักมีข้อจำกัดแบบนี้เสมอ การรู้ข้อจำกัดคือส่วนหนึ่งของการใช้มันอย่างซื่อสัตย์
🇨🇳 中文
把你的地点放进一个字典。任何地方的这两个数,都能在地图网站上右键点一下看到。
一句老实话。你问清迈和素贴山,会拿到完全一样的数字。我们试过。数据来自一张大约 11 公里一格的网格,这两个地方在同一格里。
所以它说的是你所在区域的情况,不是你家那条街。真实数据往往就是这个样子。知道它的边界,才算诚实地使用它。
PLACES = {
"chiangmai": ("Chiang Mai", 18.7883, 98.9853),
"doi": ("Doi Suthep", 18.8047, 98.9216),
"lamphun": ("Lamphun", 18.5744, 99.0087),
"bangkok": ("Bangkok", 13.7563, 100.5018),
}
parser.add_argument("--place", default="chiangmai", choices=sorted(PLACES))
6 What is coming later · อีกไม่กี่ชั่วโมงจะเป็นยังไง · 待会儿会怎样
🇬🇧 English
Ask for hourly as well and you get a long list of times and a long list of numbers, side by side.
zip walks down two lists together. It is exactly the right tool here.
Now the program answers a better question than "how is it now". It answers "should we go this afternoon".
🇹🇭 ไทย
ขอ hourly เพิ่มไปด้วย แล้วคุณจะได้ลิสต์ของเวลา กับลิสต์ของตัวเลข มาเรียงคู่กัน
zip จะไล่สองลิสต์ไปพร้อมกัน เป็นเครื่องมือที่เหมาะกับงานนี้พอดี
ตอนนี้โปรแกรมตอบคำถามที่ดีกว่าเดิม ไม่ใช่แค่ "ตอนนี้เป็นยังไง" แต่เป็น "บ่ายนี้ควรออกไปไหม"
🇨🇳 中文
顺便要一份 hourly,你会拿到一长串时间,和一长串数字,一一对应。
zip 能同时走两个列表。这里用它正合适。
现在程序回答的问题比"现在怎么样"更有用了:它回答"下午该不该出门"。
times = data["hourly"]["time"]
values = data["hourly"]["pm2_5"]
for when, value in zip(times[:6], values[:6]):
print(" %s %6.1f %s" % (when[11:16], value, bar(value, 24)))
7 When the internet is down · ตอนที่เน็ตล่ม · 断网的时候
🇬🇧 English
Your program now depends on something you do not control. Wifi drops. Websites go down. Plan for it.
raise_for_status() stops the program if the website said no, instead of quietly working with rubbish.
timeout=20 matters more than it looks. Without it, a sleeping website makes your program hang for ever, with no message.
🇹🇭 ไทย
ตอนนี้โปรแกรมของคุณพึ่งพาสิ่งที่คุณควบคุมไม่ได้ ไวไฟหลุดได้ เว็บล่มได้ ต้องเตรียมรับมือ
raise_for_status() จะหยุดโปรแกรมถ้าเว็บตอบว่าไม่ แทนที่จะทำงานต่อเงียบ ๆ ด้วยข้อมูลขยะ
timeout=20 สำคัญกว่าที่เห็น ถ้าไม่ใส่ เว็บที่ไม่ตอบจะทำให้โปรแกรมค้างไปตลอดกาล โดยไม่มีข้อความอะไรเลย
🇨🇳 中文
你的程序现在依赖一个你控制不了的东西。wifi 会断,网站会挂。要提前想好。
raise_for_status() 会在网站说"不行"的时候直接停下来,而不是拿着一堆垃圾继续跑。
timeout=20 比看起来重要得多。不写它的话,一个不回应的网站会让你的程序永远卡住,而且一句话都不说。
try:
data = get_air(lat, lon)
except requests.exceptions.ConnectionError:
print("No internet. This program needs it to ask about the air.")
return 1
except requests.exceptions.HTTPError as problem:
print("The website said no: %s" % problem)
return 1
8 The whole program · โปรแกรมทั้งหมด · 完整的程序
🇬🇧 English
Here is the finished file. Download it and compare with your own.
🇹🇭 ไทย
นี่คือไฟล์ที่เสร็จแล้ว ดาวน์โหลดไปเทียบกับของคุณเองได้
🇨🇳 中文
这是做完的文件。下载下来和你自己写的对照一下。
python3 air.py
python3 air.py --place lamphun --hours 12
9 Now change it · ลองแก้ดู · 现在动手改它
🇬🇧 English
This program has real data in it. That makes it a good place to start something bigger.
🇹🇭 ไทย
โปรแกรมนี้มีข้อมูลจริงอยู่ข้างใน จึงเป็นจุดเริ่มต้นที่ดีสำหรับอะไรที่ใหญ่กว่านี้
🇨🇳 中文
这个程序里装着真实数据,很适合作为更大项目的起点。
| Try this | How | Level |
|---|---|---|
| Add your own village | One line in PLACES. Get the numbers from a map site. | easy |
| Say it in Thai | Translate the words in BANDS. The code does not change. | easy |
| Compare two places | Call the API twice and print both bars together. | easy |
| Save it every day | Write each reading into SQLite. See the homework app. | harder |
| Put it on the map | Colour the markers in Map Chiang Mai by PM2.5. | harder |
| Tell the class | Show it on a web page with Flask. See the school wifi lesson. | harder |