You can already read a Python file. You know functions, loops, and lists. This page covers the next seven tools. Each one solves a problem you will meet soon.
Every section has the same shape: the problem, the code, and when to use it. Read the code out loud. Then open the notebook and change it.
- 1. Classes — keep data and behaviour together.
- 2. Generators — handle data too big for memory.
- 3. Decorators — add behaviour to a function without editing it.
- 4. Type hints — say what goes in and what comes out.
- 5. pathlib — file paths that work on every computer.
- 6. json — save and load data in two lines.
- 7. requests — fetch web pages and APIs.
- 8. All seven in one program
การแปลภาษาไทยกำลังจะมา (Thai translation coming soon)
你已经能读懂一个 Python 文件了。你知道函数、循环和列表。本页讲接下来的七件工具。每一件都解决一个你很快会遇到的问题。
每一节的结构都一样:问题是什么、代码怎么写、什么时候用它。把代码读出声来。然后打开 notebook,动手改一改。
- 1. 类(class) —— 把数据和行为放在一起。
- 2. 生成器(generator) —— 处理装不进内存的数据。
- 3. 装饰器(decorator) —— 不改函数本体就给它加功能。
- 4. 类型提示 —— 说明传什么进去、返回什么出来。
- 5. pathlib —— 在任何电脑上都能用的文件路径。
- 6. json —— 两行代码存取数据。
- 7. requests —— 抓取网页和调用 API。
- 8. 七件工具合成一个程序
1. Classes — data and behaviour in one place · คลาส · 类 —— 把数据和行为放在一起
Say you are tracking dogs at a shelter. Each dog has a name and an age. Each dog can bark. With plain variables it gets messy fast:
dog1_name = "Somchai"
dog1_age = 3
dog2_name = "Nong Mee"
dog2_age = 5
Two dogs need four variables. Ten dogs need twenty. And nothing links a name to its age. A class fixes this. A class is a template. It says what data one dog holds, and what one dog can do.
class Dog:
"""One dog at the shelter."""
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
return f"{self.name} says woof!"
def human_years(self):
return self.age * 7
Now make some dogs. Each one is an object built from the template:
somchai = Dog("Somchai", 3)
nong_mee = Dog("Nong Mee", 5)
print(somchai.bark()) # Somchai says woof!
print(nong_mee.human_years()) # 35
Reading the class
class Dog:starts the template. Class names use CapitalCase by convention.__init__runs once when you make a new dog. It is called the constructor.selfmeans "this particular dog". Every method takes it as the first input.self.name = namestores the name on the object, so it stays there.barkandhuman_yearsare methods — functions that belong to the class.- You never pass
selfyourself.somchai.bark()passes it for you.
When to use a class
Use a class when some data and some actions always travel together. A dog and its bark. A user and their password check. A file and how you save it.
Do not use a class when a function is enough. Many beginners wrap one function in a class for no reason. If your class has one method and no stored data, write a function instead.
การแปลกำลังจะมา
假设你要记录收容所里的狗。每只狗有名字和年龄。每只狗都会叫。用普通变量很快就乱了:
dog1_name = "Somchai"
dog1_age = 3
dog2_name = "Nong Mee"
dog2_age = 5
两只狗要四个变量。十只狗就要二十个。而且名字和年龄之间没有任何联系。类解决了这个问题。类是一个模板。它规定一只狗保存哪些数据、能做哪些事。
class Dog:
"""One dog at the shelter."""
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
return f"{self.name} says woof!"
def human_years(self):
return self.age * 7
现在造几只狗。每一只都是用模板造出来的对象:
somchai = Dog("Somchai", 3)
nong_mee = Dog("Nong Mee", 5)
print(somchai.bark()) # Somchai says woof!
print(nong_mee.human_years()) # 35
逐行读这个类
class Dog:开始定义模板。类名按惯例用大驼峰命名。__init__在新建一只狗时运行一次,叫做构造函数。self表示"当前这一只狗"。每个方法的第一个参数都是它。self.name = name把名字存在对象上,之后一直都在。bark和human_years是方法 —— 属于这个类的函数。- 你从不用自己传
self。写somchai.bark()时 Python 会替你传。
什么时候该用类
当一组数据和一组动作总是同进同出时,就用类。狗和它的叫声。用户和验证密码的方法。文件和保存它的方式。
如果一个函数就够了,就不要用类。很多初学者毫无理由地把一个函数包进类里。如果你的类只有一个方法、也不保存数据,那就写成函数。
2. Generators — yield for data that will not fit
· เจนเนอเรเตอร์
· 生成器 —— 用 yield 处理装不下的数据
Here is a function that reads a log file:
def read_lines(path):
lines = []
with open(path) as f:
for line in f:
lines.append(line.strip())
return lines # the WHOLE file is now in memory
This works for a small file. For a 20 GB log file, your computer runs out of memory and the program dies. The problem is return. It hands back everything at once.
A generator hands back one item at a time. You swap return for yield:
def read_lines(path):
with open(path) as f:
for line in f:
yield line.strip() # give ONE line, then pause
The code looks almost the same. The behaviour is completely different.
What yield actually does
Calling the function does not run it. It returns a generator object — a promise to produce values later.
lines = read_lines("huge.log")
print(lines) # generator object, no file read yet
for line in lines: # NOW it runs, one line at a time
print(line)
Each time the loop asks for a value, the function runs until it hits yield. Then it pauses, keeping all its variables. The next request wakes it up at that exact spot.
An endless generator
Because values are made on demand, a generator can be infinite. A list cannot.
def count_from(start):
n = start
while True: # never ends
yield n
n += 1
for n in count_from(10):
print(n)
if n >= 13:
break # 10, 11, 12, 13
Rules to remember
- Any function with
yieldin it is a generator. There is no other keyword. - A generator is used once. Loop over it twice and the second loop gets nothing.
- You cannot ask for
len()or index it with[3]. It does not know its own size. - Need a list after all?
list(read_lines(path))— but that brings the memory cost back.
การแปลกำลังจะมา
下面这个函数读取一个日志文件:
def read_lines(path):
lines = []
with open(path) as f:
for line in f:
lines.append(line.strip())
return lines # 整个文件现在都在内存里
文件小的时候没问题。但如果是 20 GB 的日志,内存就会耗尽,程序直接崩溃。问题出在 return 上 —— 它一次性把所有东西都交出去。
生成器一次只交出一项。把 return 换成 yield:
def read_lines(path):
with open(path) as f:
for line in f:
yield line.strip() # 交出一行,然后暂停
代码几乎一模一样,行为却完全不同。
yield 到底做了什么
调用这个函数并不会执行它。它返回一个生成器对象 —— 一个"以后再产生值"的承诺。
lines = read_lines("huge.log")
print(lines) # 生成器对象,此时还没读文件
for line in lines: # 现在才开始运行,一次一行
print(line)
每当循环要一个值,函数就运行到 yield 为止,然后暂停,并保留所有变量。下一次请求会从那个位置继续。
无限生成器
因为值是按需产生的,生成器可以是无限的。列表做不到。
def count_from(start):
n = start
while True: # 永远不结束
yield n
n += 1
for n in count_from(10):
print(n)
if n >= 13:
break # 10, 11, 12, 13
要记住的规则
- 只要函数里有
yield,它就是生成器。没有别的关键字。 - 生成器只能用一次。循环第二遍时什么都取不到。
- 不能对它用
len(),也不能用[3]取下标。它不知道自己有多长。 - 确实需要列表?
list(read_lines(path))—— 但内存代价就又回来了。
3. Decorators — @something wraps a function
· เดคอเรเตอร์
· 装饰器 —— 用 @something 包住一个函数
You have five functions. You want to know how long each one takes. The obvious fix is to edit all five:
def fetch_data():
start = time.time() # added
...
print(time.time() - start) # added
That is the same two lines copied five times. If you change the format, you edit five places. A decorator writes those lines once and wraps them around any function.
import time
def timed(fn):
"""Print how long the wrapped function takes."""
def wrapper(*args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
print(f"{fn.__name__} took {time.time() - start:.2f}s")
return result
return wrapper
Now use it. The @ line is the decorator:
@timed
def slow_add(a, b):
time.sleep(1)
return a + b
slow_add(2, 3)
# slow_add took 1.00s
# 5
How to read it
A decorator is a function that takes a function and returns a new function. That sentence is the whole idea. The @ is only shorthand:
@timed
def slow_add(a, b): ...
# means exactly the same as:
def slow_add(a, b): ...
slow_add = timed(slow_add)
timed(fn)receives the original function asfn.wrapperis the replacement. It does the extra work, then callsfn.*args, **kwargsmeans "accept any inputs" and pass them straight through.return resultmatters. Forget it and your function returnsNone.return wrapper— note the missing brackets. You return the function, not a call to it.
Where you will meet them
You will use decorators from libraries long before you write your own:
| Decorator | From | What it does |
|---|---|---|
@app.route("/hi") | Flask | Runs this function when a browser visits /hi. |
@property | Python itself | Lets a method be read like plain data. |
@staticmethod | Python itself | A method in a class that ignores self. |
@lru_cache | functools | Remembers past results so repeat calls are instant. |
@pytest.fixture | pytest | Supplies test data to your tests. |
การแปลกำลังจะมา
你有五个函数,想知道每个跑多久。最直接的办法是把五个都改一遍:
def fetch_data():
start = time.time() # 新增
...
print(time.time() - start) # 新增
同样两行代码抄了五遍。以后想改格式,就得改五处。装饰器让你只写一次,然后包到任何函数外面。
import time
def timed(fn):
"""Print how long the wrapped function takes."""
def wrapper(*args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
print(f"{fn.__name__} took {time.time() - start:.2f}s")
return result
return wrapper
然后这样用。带 @ 的那一行就是装饰器:
@timed
def slow_add(a, b):
time.sleep(1)
return a + b
slow_add(2, 3)
# slow_add took 1.00s
# 5
怎么读它
装饰器就是一个"接收函数、返回新函数"的函数。这一句话就是全部道理。@ 只是简写:
@timed
def slow_add(a, b): ...
# 和下面完全等价:
def slow_add(a, b): ...
slow_add = timed(slow_add)
timed(fn)把原函数作为fn接收进来。wrapper是替代品。它做额外的事,然后调用fn。*args, **kwargs表示"接受任意参数",并原样传下去。return result很关键。忘了写,函数就会返回None。return wrapper—— 注意没有括号。返回的是函数本身,不是调用结果。
你会在哪里遇到它们
在自己写装饰器之前,你早就会用到别人库里的装饰器:
| 装饰器 | 来自 | 作用 |
|---|---|---|
@app.route("/hi") | Flask | 浏览器访问 /hi 时运行这个函数。 |
@property | Python 内置 | 让方法可以像普通数据一样读取。 |
@staticmethod | Python 内置 | 类里不需要 self 的方法。 |
@lru_cache | functools | 记住算过的结果,重复调用瞬间返回。 |
@pytest.fixture | pytest | 为测试提供数据。 |
4. Type hints — say what goes in and what comes out · Type hints · 类型提示 —— 说明传什么、返回什么
Look at this function. What is data? A list? A file? A number?
def process(data):
...
You cannot tell. You have to read the whole body to find out. Type hints answer the question in the first line:
def greet(name: str) -> str:
"""Return a friendly greeting."""
return f"Hello, {name}!"
name: str— the input should be a string.-> str— the function gives back a string.- Nothing else changed. The function body is the same.
Python does not enforce them. greet(42) still runs. Hints are notes for humans and tools, not rules for the interpreter.
The types you will actually use
| Hint | Means |
|---|---|
str, int, float, bool | Text, whole number, decimal, True/False. |
list[str] | A list of strings. |
dict[str, int] | A dictionary with string keys and number values. |
str | None | A string, or nothing. Very common for "might fail". |
Path | A pathlib path — see the next section. |
-> None | Returns nothing. Use it for functions that only print or save. |
from pathlib import Path
def word_count(text: str) -> dict[str, int]:
"""Count how many times each word appears."""
counts: dict[str, int] = {}
for word in text.lower().split():
counts[word] = counts.get(word, 0) + 1
return counts
def find_config(folder: Path) -> Path | None:
"""Return the config file if it exists, otherwise None."""
candidate = folder / "config.json"
return candidate if candidate.exists() else None
Why bother
- Your editor gets smarter. Type
name.and VS Code lists every string method. Without the hint it cannot guess. - Mistakes are caught early. Run
pip install mypy, thenmypy myfile.py. It finds wrong types before you run anything. - The code documents itself.
-> Path | Nonewarns the reader to handle theNonecase. - AI tools read them. An LLM agent decides how to call your function from its hints and docstring.
Lesson 1 introduces type hints too, from the beginner's side.
การแปลกำลังจะมา
看看这个函数。data 是什么?列表?文件?数字?
def process(data):
...
看不出来。你必须把整个函数体读完才知道。类型提示在第一行就回答了这个问题:
def greet(name: str) -> str:
"""Return a friendly greeting."""
return f"Hello, {name}!"
name: str—— 传进来的应该是字符串。-> str—— 函数返回一个字符串。- 其他什么都没变。函数体一模一样。
Python 并不强制执行它们。greet(42) 照样能跑。提示是写给人和工具看的说明,不是解释器的规则。
真正常用的几种类型
| 写法 | 含义 |
|---|---|
str、int、float、bool | 文本、整数、小数、真/假。 |
list[str] | 一个字符串列表。 |
dict[str, int] | 键是字符串、值是数字的字典。 |
str | None | 一个字符串,或者什么都没有。"可能失败"时非常常用。 |
Path | 一个 pathlib 路径 —— 见下一节。 |
-> None | 不返回任何东西。只打印或只保存的函数用它。 |
from pathlib import Path
def word_count(text: str) -> dict[str, int]:
"""Count how many times each word appears."""
counts: dict[str, int] = {}
for word in text.lower().split():
counts[word] = counts.get(word, 0) + 1
return counts
def find_config(folder: Path) -> Path | None:
"""Return the config file if it exists, otherwise None."""
candidate = folder / "config.json"
return candidate if candidate.exists() else None
为什么值得写
- 编辑器会变聪明。输入
name.,VS Code 就列出所有字符串方法。没有提示它猜不出来。 - 错误能提早发现。装上
pip install mypy,然后跑mypy myfile.py。代码还没运行,类型错误就被找出来了。 - 代码自带说明。
-> Path | None提醒读者要处理None的情况。 - AI 工具会读它们。大模型智能体就是靠类型提示和文档字符串来决定怎么调用你的函数。
第一课也从初学者的角度讲过类型提示。
5. pathlib — file paths that work everywhere · pathlib · pathlib —— 到处都能用的文件路径
Old Python code joins paths with strings:
import os
path = os.path.join(os.path.dirname(__file__), "data", "notes.txt")
name = os.path.splitext(os.path.basename(path))[0]
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
It works, but it is hard to read. Every line calls a different os.path function on a plain string. pathlib gives you a real Path object instead:
from pathlib import Path
path = Path(__file__).parent / "data" / "notes.txt"
name = path.stem
path.parent.mkdir(parents=True, exist_ok=True)
Three lines, and each one says what it does.
The slash is the trick
pathlib reuses the / operator to join paths. It reads like a real path:
base = Path("lessons")
today = base / "2026" / "august" / "notes.txt"
print(today)
# Windows: lessons\2026\august\notes.txt
# Mac/Linux: lessons/2026/august/notes.txt
You write / and Python picks the right separator for the computer. Never type "folder\\file.txt" again.
The methods worth memorising
| Code | What you get |
|---|---|
p.name | notes.txt — the file name with its extension. |
p.stem | notes — the name without the extension. |
p.suffix | .txt — the extension. |
p.parent | The folder holding the file. |
p.exists() | True or False. |
p.mkdir(parents=True, exist_ok=True) | Make the folder. Do not complain if it is already there. |
p.read_text(encoding="utf-8") | The whole file as a string. No open() needed. |
p.write_text(s, encoding="utf-8") | Save a string to the file. |
p.glob("*.txt") | Every .txt file in the folder. A generator! |
p.rglob("*.txt") | The same, but through every sub-folder too. |
from pathlib import Path
folder = Path("lessons")
for txt in folder.rglob("*.txt"):
print(txt.stem, "-", len(txt.read_text(encoding="utf-8")), "characters")
การแปลกำลังจะมา
老式的 Python 代码用字符串拼路径:
import os
path = os.path.join(os.path.dirname(__file__), "data", "notes.txt")
name = os.path.splitext(os.path.basename(path))[0]
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
能用,但很难读。每一行都在对一个普通字符串调用不同的 os.path 函数。pathlib 给你的是真正的 Path 对象:
from pathlib import Path
path = Path(__file__).parent / "data" / "notes.txt"
name = path.stem
path.parent.mkdir(parents=True, exist_ok=True)
三行,而且每一行都在说自己在做什么。
关键在那个斜杠
pathlib 把 / 运算符借来拼接路径,读起来就像真的路径:
base = Path("lessons")
today = base / "2026" / "august" / "notes.txt"
print(today)
# Windows: lessons\2026\august\notes.txt
# Mac/Linux: lessons/2026/august/notes.txt
你写 /,Python 会为当前系统选对分隔符。再也不用写 "folder\\file.txt" 了。
值得背下来的方法
| 代码 | 得到什么 |
|---|---|
p.name | notes.txt —— 带扩展名的文件名。 |
p.stem | notes —— 不带扩展名的名字。 |
p.suffix | .txt —— 扩展名。 |
p.parent | 文件所在的文件夹。 |
p.exists() | True 或 False。 |
p.mkdir(parents=True, exist_ok=True) | 创建文件夹。已存在也不报错。 |
p.read_text(encoding="utf-8") | 把整个文件读成字符串。不用写 open()。 |
p.write_text(s, encoding="utf-8") | 把字符串写进文件。 |
p.glob("*.txt") | 文件夹里所有 .txt 文件。是个生成器! |
p.rglob("*.txt") | 同上,但会递归进所有子文件夹。 |
from pathlib import Path
folder = Path("lessons")
for txt in folder.rglob("*.txt"):
print(txt.stem, "-", len(txt.read_text(encoding="utf-8")), "characters")
6. json — save and load data in two lines · json · json —— 两行代码存取数据
Your program has a Python dictionary. You want it to still be there tomorrow. JSON is the standard text format for that job. Every language reads it, and every web API speaks it.
import json
from pathlib import Path
student = {
"name": "Ploy",
"level": "B1",
"scores": [82, 91, 77],
"active": True,
}
# Save it
Path("student.json").write_text(json.dumps(student, indent=2), encoding="utf-8")
# Load it back
loaded = json.loads(Path("student.json").read_text(encoding="utf-8"))
print(loaded["scores"][1]) # 91
That is the whole library, for most work. Two functions.
Four names, easy to mix up
| Function | Direction | Remember it as |
|---|---|---|
json.dumps(obj) | Python → text | dump-s = dump to string |
json.loads(text) | text → Python | load-s = load from string |
json.dump(obj, f) | Python → open file | no s = works on a file |
json.load(f) | open file → Python | no s = works on a file |
With pathlib you mostly need only the two s versions, as shown above.
How Python types map to JSON
| Python | JSON |
|---|---|
dict | object { } |
list | array [ ] |
str | string |
int / float | number |
True / False | true / false — lower case |
None | null |
Two arguments worth knowing
json.dumps(student, indent=2) # readable, one item per line
json.dumps(student, ensure_ascii=False) # keeps Thai and Chinese readable
Without ensure_ascii=False, the word ครู is saved as ครู. It still loads correctly, but nobody can read the file.
การแปลกำลังจะมา
你的程序里有一个 Python 字典。你希望它明天还在。JSON 就是干这个的标准文本格式。所有语言都能读它,所有 Web API 都说这门话。
import json
from pathlib import Path
student = {
"name": "Ploy",
"level": "B1",
"scores": [82, 91, 77],
"active": True,
}
# 保存
Path("student.json").write_text(json.dumps(student, indent=2), encoding="utf-8")
# 读回来
loaded = json.loads(Path("student.json").read_text(encoding="utf-8"))
print(loaded["scores"][1]) # 91
对大多数工作来说,这个库就这么多。两个函数。
四个名字,很容易搞混
| 函数 | 方向 | 怎么记 |
|---|---|---|
json.dumps(obj) | Python → 文本 | dump-s = 倒进string |
json.loads(text) | 文本 → Python | load-s = 从string 读 |
json.dump(obj, f) | Python → 已打开的文件 | 没有 s = 操作文件 |
json.load(f) | 已打开的文件 → Python | 没有 s = 操作文件 |
配合 pathlib,你基本只需要上面那两个带 s 的版本。
Python 类型和 JSON 的对应关系
| Python | JSON |
|---|---|
dict | 对象 { } |
list | 数组 [ ] |
str | 字符串 |
int / float | 数字 |
True / False | true / false —— 小写 |
None | null |
两个值得知道的参数
json.dumps(student, indent=2) # 好读,每项一行
json.dumps(student, ensure_ascii=False) # 让中文和泰文保持可读
不写 ensure_ascii=False 的话,ครู 会被存成 ครู。读回来还是对的,但文件没人看得懂。
7. requests — fetch web pages and APIs · requests · requests —— 抓网页、调 API
The first six tools ship with Python. This one you install:
pip install requests
It is the most-used package in Python, and it is worth the install. Here is a whole API call:
import requests
r = requests.get("https://api.github.com/repos/python/cpython", timeout=10)
r.raise_for_status()
data = r.json()
print(data["name"], "has", data["stargazers_count"], "stars")
# cpython has 74328 stars
Reading the four lines
requests.get(url)asks a server for something and waits for the answer.timeout=10gives up after 10 seconds. Always set it. Without it your program can hang forever.raise_for_status()raises an error if the server said 404 or 500. Without it, a failed request looks like a successful one.r.json()turns the reply into Python dictionaries and lists. It runsjson.loadsfor you.
What comes back
| Code | What you get |
|---|---|
r.status_code | 200 is fine. 404 not found. 500 server broke. |
r.ok | True if the status is under 400. |
r.text | The reply as a string — use this for HTML pages. |
r.json() | The reply parsed from JSON — use this for APIs. |
r.content | Raw bytes — use this for images and files. |
Sending data with the request
# Query string: .../quotes?limit=3
r = requests.get("https://dummyjson.com/quotes", params={"limit": 3}, timeout=10)
for q in r.json()["quotes"]:
print(f'"{q["quote"]}" — {q["author"]}')
# Sending JSON to a server
r = requests.post(
"https://httpbin.org/post",
json={"student": "Ploy", "score": 91},
headers={"Authorization": "Bearer YOUR_KEY"},
timeout=10,
)
params=builds the?key=valuepart safely. Do not glue it on with+.json=sends a Python dictionary as a JSON body. It sets the header for you.headers=is where API keys go, almost always asAuthorization.
Saving a file you downloaded
Here requests and pathlib meet:
from pathlib import Path
import requests
r = requests.get("https://www.python.org/static/img/python-logo.png", timeout=30)
r.raise_for_status()
Path("logo.png").write_bytes(r.content)
การแปลกำลังจะมา
前六件工具都是 Python 自带的。这一件需要安装:
pip install requests
它是 Python 里使用最广的包,装它非常值得。下面就是一次完整的 API 调用:
import requests
r = requests.get("https://api.github.com/repos/python/cpython", timeout=10)
r.raise_for_status()
data = r.json()
print(data["name"], "has", data["stargazers_count"], "stars")
# cpython has 74328 stars
逐行读这四行
requests.get(url)向服务器要东西,然后等回应。timeout=10表示 10 秒后放弃。永远要写它。不写的话程序可能永远卡住。raise_for_status()在服务器返回 404 或 500 时抛出异常。不写的话,失败的请求看起来跟成功的一样。r.json()把回应变成 Python 的字典和列表,等于帮你跑了json.loads。
回应里有什么
| 代码 | 得到什么 |
|---|---|
r.status_code | 200 正常,404 找不到,500 服务器出错。 |
r.ok | 状态码小于 400 时为 True。 |
r.text | 回应的字符串形式 —— 抓 HTML 网页时用它。 |
r.json() | 按 JSON 解析后的结果 —— 调 API 时用它。 |
r.content | 原始字节 —— 下载图片和文件时用它。 |
随请求一起发送数据
# 查询字符串:.../quotes?limit=3
r = requests.get("https://dummyjson.com/quotes", params={"limit": 3}, timeout=10)
for q in r.json()["quotes"]:
print(f'"{q["quote"]}" — {q["author"]}')
# 向服务器发送 JSON
r = requests.post(
"https://httpbin.org/post",
json={"student": "Ploy", "score": 91},
headers={"Authorization": "Bearer YOUR_KEY"},
timeout=10,
)
params=会安全地拼出?key=value部分。别用+手工拼。json=把 Python 字典作为 JSON 请求体发出去,还会自动设置请求头。headers=是放 API 密钥的地方,几乎总是用Authorization。
保存下载到的文件
这里 requests 和 pathlib 碰上了:
from pathlib import Path
import requests
r = requests.get("https://www.python.org/static/img/python-logo.png", timeout=30)
r.raise_for_status()
Path("logo.png").write_bytes(r.content)
8. All seven in one small program · รวมทั้งเจ็ดอย่าง · 七件工具合成一个小程序
This script fetches quotes from a public API, saves them, and prints them. It uses all seven tools. Read it and name each one.
"""quotes.py — fetch quotes once, then read them from a local file.
pip install requests
python quotes.py
"""
import json
import time
from pathlib import Path
from typing import Iterator
import requests
CACHE_DIR = Path("cache")
def timed(fn): # 3. decorator
"""Print how long the wrapped function takes."""
def wrapper(*args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
print(f"[{fn.__name__} took {time.time() - start:.2f}s]")
return result
return wrapper
class Quote: # 1. class
"""One quote and the person who said it."""
def __init__(self, text: str, author: str): # 4. type hints
self.text = text
self.author = author
def short(self, limit: int = 50) -> str:
"""The quote, cut to `limit` characters."""
if len(self.text) <= limit:
return self.text
return self.text[:limit - 3] + "..."
@timed
def fetch_quotes(count: int) -> list[dict]: # 7. requests
"""Ask the API for `count` quotes."""
r = requests.get(
"https://dummyjson.com/quotes",
params={"limit": count},
timeout=10,
)
r.raise_for_status()
return r.json()["quotes"]
def read_quotes(path: Path) -> Iterator[Quote]: # 2. generator
"""Read the saved file, one Quote at a time."""
data = json.loads(path.read_text(encoding="utf-8")) # 6. json
for item in data:
yield Quote(item["quote"], item["author"])
def main() -> None:
CACHE_DIR.mkdir(exist_ok=True) # 5. pathlib
cache_file = CACHE_DIR / "quotes.json"
if not cache_file.exists():
print("No cache. Fetching...")
quotes = fetch_quotes(5)
cache_file.write_text(
json.dumps(quotes, indent=2, ensure_ascii=False),
encoding="utf-8",
)
else:
print(f"Reading cache: {cache_file}")
for quote in read_quotes(cache_file):
print(f" {quote.short()} — {quote.author}")
if __name__ == "__main__":
main()
Run it twice. The first run fetches from the internet. The second run reads the file, and is instant. That small change is what a cache is.
การแปลกำลังจะมา
这个脚本从一个公开 API 抓取名言,保存下来,然后打印出来。七件工具全都用上了。读一遍,把每一件都指出来。
"""quotes.py — fetch quotes once, then read them from a local file.
pip install requests
python quotes.py
"""
import json
import time
from pathlib import Path
from typing import Iterator
import requests
CACHE_DIR = Path("cache")
def timed(fn): # 3. 装饰器
"""Print how long the wrapped function takes."""
def wrapper(*args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
print(f"[{fn.__name__} took {time.time() - start:.2f}s]")
return result
return wrapper
class Quote: # 1. 类
"""One quote and the person who said it."""
def __init__(self, text: str, author: str): # 4. 类型提示
self.text = text
self.author = author
def short(self, limit: int = 50) -> str:
"""The quote, cut to `limit` characters."""
if len(self.text) <= limit:
return self.text
return self.text[:limit - 3] + "..."
@timed
def fetch_quotes(count: int) -> list[dict]: # 7. requests
"""Ask the API for `count` quotes."""
r = requests.get(
"https://dummyjson.com/quotes",
params={"limit": count},
timeout=10,
)
r.raise_for_status()
return r.json()["quotes"]
def read_quotes(path: Path) -> Iterator[Quote]: # 2. 生成器
"""Read the saved file, one Quote at a time."""
data = json.loads(path.read_text(encoding="utf-8")) # 6. json
for item in data:
yield Quote(item["quote"], item["author"])
def main() -> None:
CACHE_DIR.mkdir(exist_ok=True) # 5. pathlib
cache_file = CACHE_DIR / "quotes.json"
if not cache_file.exists():
print("No cache. Fetching...")
quotes = fetch_quotes(5)
cache_file.write_text(
json.dumps(quotes, indent=2, ensure_ascii=False),
encoding="utf-8",
)
else:
print(f"Reading cache: {cache_file}")
for quote in read_quotes(cache_file):
print(f" {quote.short()} — {quote.author}")
if __name__ == "__main__":
main()
运行两次。第一次会联网抓取,第二次直接读文件,瞬间完成。这个小小的差别就是所谓的缓存。
9. Practice and what to read next · ฝึกต่อ · 练习与后续阅读
Start with the notebook. It has all of this as runnable cells, plus eight exercises with answers.
- python_toolkit.ipynb — this page, but you can run and change every line.
- Lesson 1 — Start Python on a Mac — go back here if the Terminal, functions or imports still feel unfamiliar.
- python_basics.ipynb — the language itself, with an exercise after every section.
- python_apis_async.ipynb — decorators and
requestsagain, thenasync/await. - python_next_steps.ipynb — modules, file I/O,
try/except, comprehensions. - Official docs, all short and worth reading in full: pathlib, json, requests quickstart.
One project to try: point the capstone script at a different API. Change the URL, the params, and the keys you read from the JSON. Almost nothing else needs to change. That is the point.
การแปลกำลังจะมา
先从 notebook 开始。本页所有内容在里面都是可运行的单元格,另外还有八道带答案的练习。
- python_toolkit.ipynb —— 本页内容,但每一行你都能运行和修改。
- 第一课 —— 在 Mac 上开始学 Python —— 终端、函数或导入还不熟就回到这一页。
- python_basics.ipynb —— 语言本身,每节都有练习。
- python_apis_async.ipynb —— 再讲一遍装饰器和
requests,然后是async/await。 - python_next_steps.ipynb —— 模块、文件读写、
try/except、推导式。 - 官方文档,都很短,值得整篇读完:pathlib、json、requests quickstart。
可以试的一个小项目:把最后那个脚本指向另一个 API。改 URL、改 params、改你从 JSON 里取的键名。其他几乎什么都不用动 —— 这正是重点。