🧊 Data Classes, attrs and Pydantic · Data Classes, attrs และ Pydantic · 数据类、attrs 和 Pydantic

Three ways to make a Python object that holds data. They look the same. One difference decides which you use. · สามวิธีสร้างอ็อบเจกต์เก็บข้อมูลใน Python หน้าตาคล้ายกันมาก แต่มีข้อต่างข้อเดียวที่ตัดสินว่าจะใช้ตัวไหน · 在 Python 里做一个装数据的对象,有三种办法。它们看起来一样,但有一个区别决定你该用哪个。

🎯 What this lesson gives you · สิ่งที่คุณจะได้จากบทเรียนนี้ · 本课能给你什么

🇬🇧 English

Every program holds data. A student. A booking. A price. Python gives you three tools for this job.

Data classes come free with Python. attrs and Pydantic are libraries you install.

People argue about which is best. The argument is not useful. Each one does a different job.

By the end you will know that job. You will also have a rule you can use at work.

Every result on this page was run on real Python 3.12. Nothing here is guessed.

🇹🇭 ไทย

ทุกโปรแกรมต้องเก็บข้อมูล นักเรียนหนึ่งคน การจองหนึ่งรายการ ราคาหนึ่งราคา Python มีเครื่องมือสามตัวสำหรับงานนี้

Data classes มากับ Python ฟรี ส่วน attrs และ Pydantic เป็นไลบรารีที่ต้องติดตั้งเอง

คนชอบเถียงกันว่าตัวไหนดีที่สุด การเถียงนั้นไม่มีประโยชน์ แต่ละตัวทำงานคนละอย่าง

อ่านจบแล้วคุณจะรู้ว่างานนั้นคืออะไร และจะได้กฎง่าย ๆ ไปใช้ทำงานจริง

ผลลัพธ์ทุกอันในหน้านี้รันจริงบน Python 3.12 ไม่มีอันไหนเดาเอา

🇨🇳 中文

每个程序都要装数据。一个学生、一笔预订、一个价格。Python 为这件事准备了三样工具。

数据类(data class)是 Python 自带的,免费。attrsPydantic 是要自己安装的库。

大家爱争哪个最好。这种争论没什么用。它们各做各的事。

读完你就知道那件事是什么,还能拿到一条可以直接在工作中用的规则。

本页每一个结果都在真实的 Python 3.12 上跑过,没有一处是猜的。

1️⃣ The words you need first · คำศัพท์ที่ต้องรู้ก่อน · 先要懂的词

🇬🇧 English

Two words in this list do all the work: validate and coerce.

To validate is to check. To coerce is to change. They are not the same thing.

Click any card to hear it said aloud.

🇹🇭 ไทย

ในรายการนี้มีสองคำที่สำคัญที่สุด คือ validate กับ coerce

validate คือตรวจสอบ ส่วน coerce คือแปลงค่า สองอย่างนี้ไม่เหมือนกัน

คลิกที่การ์ดเพื่อฟังเสียงอ่าน

🇨🇳 中文

这一组词里,最重要的是两个:validate(校验)和 coerce(转换)。

validate 是检查,coerce 是改。两者不是一回事。

点任意卡片可以听发音。

🏗️ class
คลาส
A plan for making objects. Student is a class. Ploy is one object.做对象的图纸。Student 是类,Ploy 是一个对象。แบบแปลนสำหรับสร้างอ็อบเจกต์ Student คือคลาส ส่วน Ploy คืออ็อบเจกต์หนึ่งตัว
🧊 data class
ดาต้าคลาส数据类
A class that just holds data. It has no clever behaviour.只用来装数据的类,没有复杂行为。คลาสที่มีหน้าที่เก็บข้อมูลอย่างเดียว ไม่มีพฤติกรรมซับซ้อน
🏷️ field
ฟิลด์字段
One piece of data in the class. age is a field.类里的一项数据。age 就是一个字段。ข้อมูลหนึ่งชิ้นในคลาส เช่น age คือฟิลด์หนึ่ง
📐 type hint
ไทป์ฮินต์类型注解
A note saying what kind of value fits. age: int is a type hint.写明该放什么类型的值。age: int 就是类型注解。หมายเหตุบอกว่าควรใส่ค่าชนิดไหน เช่น age: int
✅ validate
ตรวจสอบความถูกต้อง校验
To check a value. If it is wrong, stop and say so.检查一个值。不对就停下来报错。ตรวจค่าที่ได้มา ถ้าผิดก็หยุดแล้วแจ้งทันที
🔁 coerce
แปลงชนิดข้อมูล强制转换
To change a value into the right kind. Text "28" becomes number 28.把值成正确的类型。文本 "28" 变成数字 28แปลงค่าให้เป็นชนิดที่ถูก เช่น ข้อความ "28" กลายเป็นตัวเลข 28
📥 parse
แปลงข้อมูลดิบ解析
To read raw text, like JSON, and turn it into real objects.读取原始文本(比如 JSON),变成真正的对象。อ่านข้อความดิบ เช่น JSON แล้วเปลี่ยนเป็นอ็อบเจกต์จริง
🧊 frozen
แก้ไขไม่ได้冻结 / 不可变
Locked after you make it. You cannot change a field later.创建后就锁住,之后不能改字段。ล็อกทันทีหลังสร้าง แก้ฟิลด์ทีหลังไม่ได้
📚 library
ไลบรารี
Code someone else wrote. You pip install it, then import it.别人写好的代码。先 pip install,再 importโค้ดที่คนอื่นเขียนไว้ ต้อง pip install ก่อน แล้วค่อย import
📦 standard library
ไลบรารีมาตรฐาน标准库
The code that comes inside Python. You install nothing.Python 自带的代码,什么都不用装。โค้ดที่มากับตัว Python เลย ไม่ต้องติดตั้งอะไรเพิ่ม

2️⃣ The problem all three solve · ปัญหาที่ทั้งสามตัวแก้ให้ · 三者共同解决的问题

🇬🇧 English

You want to hold three things about a student. A name, an age, a city.

Old Python made you write every name three times. Once in the line, once on the left, once on the right.

Then you had to add more. Printing was ugly. Two equal students did not look equal.

That is a lot of typing. The box holds only three values.

🇹🇭 ไทย

คุณอยากเก็บข้อมูลนักเรียนสามอย่าง ชื่อ อายุ และเมือง

Python แบบเก่าบังคับให้พิมพ์ชื่อฟิลด์สามรอบ ในบรรทัดแรก ฝั่งซ้าย และฝั่งขวา

แล้วยังต้องเขียนเพิ่มอีก การพิมพ์ออกมาดูไม่สวย นักเรียนสองคนที่ข้อมูลเหมือนกันกลับไม่เท่ากัน

พิมพ์เยอะมาก สำหรับกล่องที่เก็บแค่สามค่า

🇨🇳 中文

你想存学生的三项信息:姓名、年龄、城市。

老式 Python 逼你把每个名字写三遍:参数一遍、左边一遍、右边一遍。

然后还得再加东西。打印出来很难看,两个一样的学生还判定为不相等。

为了一个只装三个值的盒子,打字量太大了。

The old way. Count how many times you type the word name. วิธีแบบเก่า ลองนับว่าต้องพิมพ์คำว่า name กี่ครั้ง 老写法。数一数 name 这个词要打几遍。
class Student: def __init__(self, name, age, city): self.name = name # name, again self.age = age self.city = city def __repr__(self): return f"Student({self.name!r}, {self.age!r}, {self.city!r})" def __eq__(self, other): if not isinstance(other, Student): return NotImplemented return (self.name, self.age, self.city) == (other.name, other.age, other.city) # 14 lines. Three of them are the actual data.
The new way. All three tools turn those 14 lines into four. วิธีแบบใหม่ ทั้งสามตัวย่อ 14 บรรทัดนั้นเหลือสี่บรรทัด 新写法。三样工具都能把那 14 行变成 4 行。
from dataclasses import dataclass @dataclass class Student: name: str age: int city: str = "Chiang Mai" # You now get __init__, __repr__ and __eq__ for free. ploy = Student(name="Ploy", age=28) print(ploy) Student(name='Ploy', age=28, city='Chiang Mai') print(ploy == Student("Ploy", 28, "Chiang Mai")) True
This part is not the argument. All three save you the same typing. All three give you a good __repr__ and a working ==. If that is all you want, stop here. Use a data class. It is already in the box. เรื่องนี้ไม่ใช่ประเด็นที่ต้องเถียง ทั้งสามตัวช่วยลดการพิมพ์เท่ากัน ให้ __repr__ ที่ดีและ == ที่ใช้ได้เหมือนกัน ถ้าคุณต้องการแค่นี้ หยุดตรงนี้แล้วใช้ data class ได้เลย เพราะมันมีอยู่แล้วในกล่อง 这一段不是争论的焦点。三者省下的打字量一样多,都给你好看的 __repr__ 和能用的 ==。如果你只要这些,就到此为止,用数据类,因为它本来就在盒子里。

3️⃣ The same student, three ways · นักเรียนคนเดียวกัน เขียนสามแบบ · 同一个学生,三种写法

🇬🇧 English

Here is one student written three times. Read them side by side.

They are almost the same. The shape of the class does not change much.

Look at the age line in each one. That is where they part.

🇹🇭 ไทย

นี่คือนักเรียนคนเดียวกันเขียนสามแบบ ลองอ่านเทียบกัน

แทบจะเหมือนกันเลย รูปร่างของคลาสแทบไม่ต่างกัน

ให้ดูบรรทัด age ของแต่ละแบบ นั่นคือจุดที่มันแยกทางกัน

🇨🇳 中文

这是同一个学生的三种写法,并排读一读。

它们几乎一模一样,类的形状差别不大。

看每一段里的 age 那行。它们就是在那里分道扬镳的。

🧊 dataclasses stdlib
from dataclasses import dataclass @dataclass class Student: name: str age: int city: str = "Chiang Mai"
Nothing to install. The type hints are notes only. Python never checks them. ไม่ต้องติดตั้งอะไร ไทป์ฮินต์เป็นแค่หมายเหตุ Python ไม่เคยตรวจให้ 不用安装。类型注解只是备注,Python 从不检查。
🛠️ attrs pip install attrs
from attrs import define, field from attrs import validators as v @define class Student: name: str age: int = field( validator=[v.instance_of(int), v.gt(0)]) city: str = "Chiang Mai"
You ask for checks, one field at a time. Nothing is checked unless you ask. คุณสั่งให้ตรวจทีละฟิลด์ ถ้าไม่สั่ง มันก็ไม่ตรวจให้ 你逐个字段要求检查。不要求,它就不查。
🛡️ Pydantic pip install pydantic
from pydantic import BaseModel, Field class Student(BaseModel): name: str age: int = Field(gt=0, lt=120) city: str = "Chiang Mai"
The type hints are rules. Pydantic checks every field, every time, on its own. ไทป์ฮินต์กลายเป็นกฎ Pydantic ตรวจทุกฟิลด์ทุกครั้งให้เอง 类型注解就是规则。Pydantic 自动检查每个字段、每一次。

4️⃣ The one real difference — try it · ข้อต่างที่แท้จริงข้อเดียว ลองเล่นดู · 唯一真正的区别 —— 动手试试

🇬🇧 English

Here is the whole lesson in one screen. We send each class a bad age.

Pick an input below. Watch what the three classes do with it.

The second button is the important one. A web form always sends text, never numbers.

🇹🇭 ไทย

บทเรียนทั้งบทอยู่ในหน้าจอเดียวนี้ เราจะส่งค่า age ที่ไม่ดีเข้าไปในทั้งสามคลาส

เลือกค่าที่จะส่งด้านล่าง แล้วดูว่าแต่ละคลาสทำอะไรกับมัน

ปุ่มที่สองสำคัญที่สุด เพราะฟอร์มบนเว็บส่งมาเป็นข้อความเสมอ ไม่เคยส่งเป็นตัวเลข

🇨🇳 中文

整堂课就在这一屏里。我们往三个类里各塞一个有问题的 age

在下面选一个输入,看看三个类分别怎么处理。

第二个按钮最关键。网页表单送来的永远是文本,绝不是数字。

Student(name="Ploy", age=28)
Pick the value that arrives as age: เลือกค่าที่ส่งเข้ามาเป็น age: 选一个作为 age 传进来的值:
Say it in one line. A data class trusts you. attrs checks what you asked it to check. Pydantic checks everything, and repairs what it can. สรุปเป็นบรรทัดเดียว data class เชื่อใจคุณ attrs ตรวจเฉพาะที่คุณสั่งให้ตรวจ ส่วน Pydantic ตรวจทุกอย่าง และซ่อมให้ถ้าซ่อมได้ 一句话说完。数据类信任你;attrs 只检查你要求检查的;Pydantic 全部检查,能修的就修
⚠️ The trap nobody warns you about ⚠️ กับดักที่ไม่ค่อยมีใครเตือน ⚠️ 没人提醒你的陷阱 Pydantic repairing "28" into 28 is usually a gift. Sometimes it is not. If you need Pydantic to refuse text, turn repair off with model_config = {"strict": True}. Then "28" fails too. การที่ Pydantic ซ่อม "28" ให้เป็น 28 มักเป็นเรื่องดี แต่บางครั้งก็ไม่ดี ถ้าคุณอยากให้มันปฏิเสธข้อความไปเลย ให้ปิดการซ่อมด้วย model_config = {"strict": True} แล้ว "28" จะไม่ผ่านเหมือนกัน Pydantic 把 "28" 修成 28 通常是好事,但有时不是。如果你要它拒绝文本,就用 model_config = {"strict": True} 关掉自动修复,这样 "28" 也会报错。

5️⃣ Data classes — the one already in the box · Data classes — ตัวที่มีอยู่แล้วในกล่อง · 数据类 —— 盒子里本来就有的那个

🇬🇧 English

Data classes came with Python 3.7, in 2018. They are part of Python itself.

That is their great strength. No install. No new dependency. Nothing to keep updated.

They check nothing at all. For data your own code made, that is fine. You already trust it.

They are also the fastest of the three. They do the least work.

🇹🇭 ไทย

Data classes มากับ Python 3.7 เมื่อปี 2018 เป็นส่วนหนึ่งของตัว Python เอง

นั่นคือจุดแข็งที่สุดของมัน ไม่ต้องติดตั้ง ไม่เพิ่ม dependency ไม่ต้องคอยอัปเดต

มันไม่ตรวจอะไรเลย ถ้าข้อมูลมาจากโค้ดของคุณเอง ก็ไม่เป็นไร เพราะคุณเชื่อใจมันอยู่แล้ว

และมันเร็วที่สุดในสามตัว เพราะมันทำงานน้อยที่สุด

🇨🇳 中文

数据类随 Python 3.7 在 2018 年到来,是 Python 本身的一部分。

这是它最大的优点:不用装、不加依赖、不用维护更新。

它什么都不检查。如果数据是你自己的代码造的,这没问题,反正你信得过。

它也是三者中最快的,因为它做的事最少。

Three options are worth knowing. A safe default, a lock, a smaller object. ออปชันสามตัวที่ควรรู้ ค่าเริ่มต้นที่ปลอดภัย การล็อก และการทำให้อ็อบเจกต์เล็กลง 值得记住的三个选项:安全的默认值、锁定、以及更小的对象。
from dataclasses import dataclass, field @dataclass(frozen=True, slots=True) # frozen = locked; slots = smaller + faster class Booking: temple: str people: int guests: list = field(default_factory=list) # a NEW list each time b = Booking(temple="Wat Don Chan", people=4) b.people = 5 FrozenInstanceError: cannot assign to field 'people'
⚠️ The classic beginner error ⚠️ ข้อผิดพลาดคลาสสิกของมือใหม่ ⚠️ 新手的经典错误 Never write guests: list = []. Python refuses it, and it is right to. One list would be shared by every booking you make. Use field(default_factory=list), which builds a fresh list each time. อย่าเขียน guests: list = [] เด็ดขาด Python จะปฏิเสธ และมันปฏิเสธถูกแล้ว เพราะลิสต์ก้อนเดียวจะถูกใช้ร่วมกันทุกการจอง ให้ใช้ field(default_factory=list) ซึ่งสร้างลิสต์ใหม่ทุกครั้ง 千万别写 guests: list = []。Python 会拒绝,而且拒绝得对:那样所有预订会共用同一个列表。请用 field(default_factory=list),它每次都新建一个列表。
@dataclass class Bad: guests: list = [] ValueError: mutable default <class 'list'> for field guests is not allowed: use default_factory

6️⃣ attrs — the older, quieter one · attrs — ตัวที่เก่ากว่าและเงียบกว่า · attrs —— 更老、更低调的那个

🇬🇧 English

Most people meet attrs last. In fact it came first, in 2015.

Data classes were built later. The Python team looked at attrs first. attrs is the parent.

So why use it now? Because it kept going. It does things data classes still cannot do.

It gives you checks per field, and it never coerces. If you say int, text is refused, even "28".

🇹🇭 ไทย

คนส่วนใหญ่รู้จัก attrs เป็นตัวสุดท้าย แต่จริง ๆ มันมาก่อนใคร ตั้งแต่ปี 2015

Data classes ถูกสร้างทีหลัง และทีม Python ก็ดู attrs เป็นต้นแบบตอนสร้าง attrs จึงเป็นรุ่นพ่อ

แล้วทำไมยังใช้มันอยู่ เพราะมันพัฒนาต่อไม่หยุด มันทำสิ่งที่ data classes ยังทำไม่ได้

มันให้คุณตรวจค่าทีละฟิลด์ และมันไม่แปลงค่าให้เลย ถ้าคุณบอกว่า int ข้อความจะถูกปฏิเสธ แม้แต่ "28"

🇨🇳 中文

多数人最后才认识 attrs。其实它最早,2015 年就有了。

数据类是后来才做的,Python 团队当时参考了 attrs。attrs 是长辈。

那现在为什么还用它?因为它一直在往前走,做得到数据类至今做不到的事。

它让你按字段做检查,而且从不做转换。你说 int,文本就会被拒绝,连 "28" 也一样。

Two tools: a validator checks and refuses. A converter changes the value first. มีสองเครื่องมือ validator ตรวจแล้วปฏิเสธ ส่วน converter แปลงค่าให้ก่อน 两样工具:validator 检查并拒绝,converter 先把值改掉。
from attrs import define, field, validators as v @define class Student: age: int = field(validator=[v.instance_of(int), v.gt(0)]) Student(age="28") TypeError: 'age' must be <class 'int'> (got '28' that is a <class 'str'>). Student(age=-5) ValueError: 'age' must be > 0: -5 # Want the "28" repaired after all? Ask for it, out loud: @define class Student2: age: int = field(converter=int) print(Student2(age="28")) Student2(age=28) # now it converts — because you said so
That is the attrs personality. Nothing happens by magic. Coercion happens only where you wrote converter=. On a big team this is a feature. You read the class. You know what it will do. นั่นคือนิสัยของ attrs ไม่มีอะไรเกิดขึ้นเองแบบเวทมนตร์ การแปลงค่าเกิดเฉพาะที่คุณเขียน converter= ไว้ ในทีมใหญ่ นี่คือข้อดี คุณอ่านคลาสแล้วรู้ทันทีว่ามันจะทำอะไร 这就是 attrs 的性格。没有任何魔法。只有你写了 converter= 的地方才会转换。在大团队里这是优点:读一遍类,就知道它会做什么。

7️⃣ Pydantic — the guard at the door · Pydantic — ยามที่เฝ้าประตู · Pydantic —— 守门的那一个

🇬🇧 English

Pydantic has one job. It stands at the door of your program.

Data from outside is never safe. A web form. A JSON file. Another company's API.

Pydantic reads that mess. It checks every field. It hands you a clean object. Or it refuses, and tells you which field was wrong.

This is why FastAPI is built on it. A web server is all door.

🇹🇭 ไทย

Pydantic มีหน้าที่เดียว มันยืนเฝ้าอยู่ที่ประตูของโปรแกรมคุณ

ข้อมูลจากข้างนอกไม่เคยปลอดภัย ทั้งฟอร์มบนเว็บ ไฟล์ JSON หรือ API ของบริษัทอื่น

Pydantic อ่านของรก ๆ นั้น ตรวจทุกฟิลด์ แล้วส่งอ็อบเจกต์ที่สะอาดให้คุณ หรือไม่ก็ปฏิเสธ พร้อมบอกว่าฟิลด์ไหนผิด

นี่คือเหตุผลที่ FastAPI สร้างอยู่บนมัน เพราะเว็บเซิร์ฟเวอร์คือประตูล้วน ๆ

🇨🇳 中文

Pydantic 只有一个任务:站在你程序的门口。

外面来的数据从来不安全:网页表单、JSON 文件、别家公司的 API。

Pydantic 读那堆乱东西,逐个字段检查,然后交给你一个干净的对象;或者拒绝,并告诉你哪个字段错了。

这就是 FastAPI 建在它上面的原因 —— 一个 web 服务器,全身都是门。

JSON goes in. A real object comes out. The other two cannot do this as cleanly. ใส่ JSON เข้าไป ได้อ็อบเจกต์จริงออกมา นี่คือท่าที่อีกสองตัวทำได้ไม่สะอาดเท่า JSON 进去,真对象出来。这一招另外两个做不了这么干净。
from pydantic import BaseModel, Field, ValidationError class Student(BaseModel): name: str age: int = Field(gt=0, lt=120) city: str = "Chiang Mai" raw = '{"name": "Ploy", "age": 28}' # text from a web request ploy = Student.model_validate_json(raw) # parse + check, one step print(ploy) name='Ploy' age=28 city='Chiang Mai' print(ploy.model_dump_json()) # and back out again {"name":"Ploy","age":28,"city":"Chiang Mai"} # When it is wrong, the message names the field and the reason: Student(name="Ploy", age=150) 1 validation error for Student age Input should be less than 120 [type=less_than, input_value=150, input_type=int]
Read that error again. It names the field, the rule, and the value it got. You can show that to a user. A data class gives no error at all. The bad age goes straight into your database. ลองอ่านข้อความ error นั้นอีกครั้ง มันบอกทั้งชื่อฟิลด์ กฎที่ใช้ และค่าที่ได้รับ เอาไปแสดงให้ผู้ใช้เห็นได้เลย ส่วน data class จะไม่มี error อะไรทั้งนั้น อายุที่ผิดจะไหลลงฐานข้อมูลไปเฉย ๆ 再读一遍那条错误。它说清了字段、规则和收到的值,可以直接拿去给用户看。而数据类根本不会报错 —— 那个错误的年龄会直接进你的数据库。

8️⃣ What does the checking cost? · การตรวจสอบมีราคาเท่าไร · 做检查要付出多少代价

🇬🇧 English

Checking is work, and work takes time. So Pydantic is slower. How much slower?

We built the same small object 100,000 times in each. Python 3.12, Pydantic 2.13, attrs 26.1.

Pydantic took about 6 times as long as a data class. That sounds bad. Look at the real number.

It is 0.75 millionths of a second per object. For data coming off a network, this cost is nothing.

It only matters in a tight loop over millions of rows. Then reach for a data class.

🇹🇭 ไทย

การตรวจสอบคืองาน และงานก็กินเวลา Pydantic จึงช้ากว่า แต่ช้ากว่าเท่าไร

เราสร้างอ็อบเจกต์เล็ก ๆ ตัวเดียวกัน 100,000 ครั้งในแต่ละตัว บน Python 3.12, Pydantic 2.13, attrs 26.1

Pydantic ใช้เวลาราว 6 เท่า ของ data class ฟังดูแย่ แต่ลองดูตัวเลขจริง

มันคือ 0.75 ในล้านวินาที ต่ออ็อบเจกต์หนึ่งตัว สำหรับข้อมูลที่มาจากเครือข่าย ราคานี้เท่ากับศูนย์

มันจะสำคัญก็ต่อเมื่อคุณวนลูปข้อมูลหลายล้านแถว ตอนนั้นค่อยหันไปใช้ data class

🇨🇳 中文

检查是活儿,干活要花时间,所以 Pydantic 更慢。慢多少?

我们用三者各建同一个小对象 10 万次。Python 3.12、Pydantic 2.13、attrs 26.1。

Pydantic 花的时间约为数据类的 6 倍。听着很糟,但看看真实数字。

每个对象 0.75 微秒。对于从网络上来的数据,这点代价等于零。

只有在几百万行的紧密循环里才要紧。那时候再用数据类。

Time to build one object (lower is faster) เวลาที่ใช้สร้างอ็อบเจกต์หนึ่งตัว (น้อยกว่าคือเร็วกว่า) 创建一个对象所需时间(越少越快)
attrs
0.11 µs
dataclass
0.12 µs
Pydantic
0.75 µs
Measured with timeit, 100,000 builds each, on Python 3.12.10. Your machine will differ. The shape will not. วัดด้วย timeit อย่างละ 100,000 ครั้ง บน Python 3.12.10 เครื่องคุณจะได้ตัวเลขต่างออกไป แต่สัดส่วนจะเหมือนเดิม timeit 各测 10 万次,Python 3.12.10。你的机器数字会不同,但比例不会。
⚠️ Do not read this as "Pydantic is slow" ⚠️ อย่าอ่านตารางนี้ว่า "Pydantic ช้า" ⚠️ 别把这读成「Pydantic 很慢」 Pydantic is doing more. It is checking. Compare it to a data class plus your own checking code. Then Pydantic usually wins. Its core is written in Rust. Pydantic ทำงานมากกว่า เพราะมันตรวจสอบให้ ลองเทียบกับ data class บวก โค้ดตรวจสอบที่คุณต้องเขียนเอง แบบนั้น Pydantic มักชนะ เพราะแกนกลางของมันเขียนด้วยภาษา Rust Pydantic 做的事更多 —— 它在检查。拿它和「数据类加上你手写的检查代码」比,通常是 Pydantic 赢,因为它的内核是用 Rust 写的。

9️⃣ So which one do I use? · แล้วจะใช้ตัวไหนดี · 那我到底该用哪个

🇬🇧 English

There is one question that matters most. Where did this data come from?

Data your own code made is safe. You wrote it. Trust it, and use a data class.

Data from outside is not safe. Check it at the door, with Pydantic.

Answer the three questions below. Watch the answer change.

🇹🇭 ไทย

มีคำถามหนึ่งที่สำคัญที่สุด ข้อมูลนี้มาจากไหน

ข้อมูลที่โค้ดของคุณสร้างเองนั้นปลอดภัย คุณเขียนมันเอง เชื่อใจได้ ใช้ data class ไปเลย

ข้อมูลจากข้างนอกไม่ปลอดภัย ต้องตรวจตั้งแต่ที่ประตู ด้วย Pydantic

ลองตอบสามคำถามด้านล่าง แล้วดูคำตอบเปลี่ยนไป

🇨🇳 中文

最要紧的只有一个问题:这份数据是哪来的?

你自己代码造的数据是安全的。是你写的,信得过,用数据类就行。

外面来的数据不安全。要在门口就查,用 Pydantic。

回答下面三个问题,看答案怎么变。

1. Where does the data come from? 1. ข้อมูลมาจากไหน 1. 数据从哪来?
2. Can you add a library to this project? 2. โปรเจกต์นี้เพิ่มไลบรารีได้ไหม 2. 这个项目可以加库吗?
3. Do you need to check the values, not just the types? 3. ต้องตรวจ "ค่า" ด้วยไหม ไม่ใช่แค่ "ชนิด" 3. 你需要检查「值」吗,而不只是「类型」?
Answer all three questions above. ตอบให้ครบทั้งสามข้อด้านบน 把上面三个问题都选一下。
Questionหัวข้อ项目 🧊 dataclasses 🛠️ attrs 🛡️ Pydantic
Need to install it?ต้องติดตั้งไหม需要安装吗 No — it is in Pythonไม่ — มากับ Python不用 —— Python 自带 Yesต้อง Yesต้อง
Checks types?ตรวจชนิดข้อมูลไหม检查类型吗 Neverไม่เลย从不 If you askถ้าคุณสั่ง你要求才查 Alwaysตลอดเวลา总是
Changes "28" to 28?แปลง "28" เป็น 28 ไหม"28"28 Noไม่ Only with a converterเฉพาะเมื่อใส่ converter加了 converter 才会 Yes, by defaultใช่ เป็นค่าเริ่มต้น是,默认就会
Reads JSON in one step?อ่าน JSON ได้ในขั้นตอนเดียวไหม一步读 JSON 吗 No, by handไม่ ต้องทำเอง不,要手写 No, by handไม่ ต้องทำเอง不,要手写 Yesได้可以
Speed building objectsความเร็วในการสร้างอ็อบเจกต์建对象的速度 0.12 µs 0.11 µs 0.75 µs
Best forเหมาะกับ最适合 Data you already trustข้อมูลที่เชื่อใจอยู่แล้ว你已经信得过的数据 Careful classes, no surprisesคลาสที่ควบคุมเองทุกอย่าง要精确控制、不要意外 Data from outsideข้อมูลจากข้างนอก从外面来的数据

🔟 You can use two of them together · ใช้สองตัวร่วมกันก็ได้ · 两个可以一起用

🇬🇧 English

This is not a fight. Most real programs use two.

Pydantic guards the door. Data classes hold everything inside.

Pydantic also has a bridge. It can add checking to a normal data class.

Use the bridge if you have data classes already. It adds checks without a rewrite.

🇹🇭 ไทย

นี่ไม่ใช่การแข่งกัน โปรแกรมจริงส่วนใหญ่ใช้สองตัว

Pydantic เฝ้าประตู ส่วน data class เก็บข้อมูลทุกอย่างข้างใน

Pydantic ยังมีสะพานเชื่อมด้วย มันเพิ่มการตรวจสอบให้ data class ธรรมดาได้

ใช้สะพานนี้เมื่อคุณมี data class อยู่แล้ว และอยากได้การตรวจสอบโดยไม่ต้องเขียนใหม่

🇨🇳 中文

这不是一场比武。多数真实程序会同时用两个。

Pydantic 守门,数据类在里面装东西。

Pydantic 还给了一座桥:它能给普通数据类加上检查。

如果你已经有一堆数据类,又想要检查但不想重写,就走这座桥。

import pydantic.dataclasses as pdc @pdc.dataclass # looks like a dataclass, checks like Pydantic class Student: name: str age: int Student(name="Ploy", age="twenty") 1 validation error for Student # It is still a real dataclass, so dataclasses.asdict() still works: import dataclasses print(dataclasses.is_dataclass(Student)) True
The shape of a healthy program. Check once, at the edge. Inside, everything is already clean. Plain data classes are enough. They are also the fast ones. รูปร่างของโปรแกรมที่ดี ตรวจครั้งเดียวที่ขอบนอก ข้างในข้อมูลสะอาดหมดแล้ว ใช้ data class ธรรมดาก็พอ และมันคือตัวที่เร็วที่สุดด้วย 健康程序的形状。在边界检查一次。里面的数据已经干净了,用普通数据类就够,而且它们最快。

1️⃣1️⃣ Real job: Gmail and Yahoo logins · งานจริง: การล็อกอิน Gmail และ Yahoo · 实战:Gmail 和 Yahoo 的登录

🇬🇧 English

Now use the lesson on a real job. Your program must read and send email.

To do that it needs a login. That login comes from outside your code.

So this is Pydantic's job. It is the door again.

Never put a password in your code. Never commit one to git.

🇹🇭 ไทย

ทีนี้เอาบทเรียนไปใช้กับงานจริง โปรแกรมของคุณต้องอ่านและส่งอีเมล

มันจึงต้องมีข้อมูลล็อกอิน และข้อมูลนั้นมาจากข้างนอกโค้ด

นี่จึงเป็นงานของ Pydantic มันคือประตูอีกครั้ง

อย่าใส่รหัสผ่านไว้ในโค้ด และอย่า commit ขึ้น git เด็ดขาด

🇨🇳 中文

现在把这堂课用到真活儿上。你的程序要读邮件、发邮件。

那就需要一份登录信息。这份信息来自代码外面。

所以这是 Pydantic 的活儿。又是那道门。

绝不要把密码写进代码,也绝不要提交到 git。

Step 1 — get an app password. This is not your real password. It is a separate one, for programs only. ขั้นที่ 1 — ขอ app password นี่ไม่ใช่รหัสผ่านจริงของคุณ แต่เป็นอีกอันหนึ่ง สำหรับโปรแกรมเท่านั้น 第 1 步 —— 申请应用专用密码。它不是你的真密码,是另外一个,只给程序用。
📧 Gmail
1. Turn on 2-Step Verification first. Google hides app passwords until you do.
2. Go to myaccount.google.com/apppasswords
3. Name it python. Google shows 16 letters.
4. Copy them now. Google will not show them again.
1. เปิด 2-Step Verification ก่อน ถ้าไม่เปิด Google จะซ่อนเมนูนี้ไว้
2. ไปที่ myaccount.google.com/apppasswords
3. ตั้งชื่อว่า python แล้ว Google จะแสดงตัวอักษร 16 ตัว
4. คัดลอกทันที Google จะไม่แสดงให้ดูอีก
1. 先开启两步验证。不开的话,Google 不会显示这个入口。
2. 打开 myaccount.google.com/apppasswords
3. 命名为 python,Google 会给你 16 个字母。
4. 立刻复制。Google 不会再显示第二次。
💜 Yahoo
1. Go to login.yahoo.com/account/security
2. Click Generate app password.
3. Name it python. Yahoo shows 16 letters.
4. Yahoo shows them in four groups. The spaces do not matter.
1. ไปที่ login.yahoo.com/account/security
2. กด Generate app password
3. ตั้งชื่อว่า python แล้ว Yahoo จะแสดงตัวอักษร 16 ตัว
4. Yahoo แสดงเป็นสี่กลุ่ม ช่องว่างไม่มีผล ลบออกได้
1. 打开 login.yahoo.com/account/security
2. 点 Generate app password
3. 命名为 python,Yahoo 会给你 16 个字母。
4. Yahoo 分成四组显示。空格没有影响,可以删掉。
🔐 Why an app password is safer 🔐 ทำไม app password ถึงปลอดภัยกว่า 🔐 为什么应用专用密码更安全 It opens mail, and nothing else. It cannot change your account. You can delete it at any time. Only that one program stops working. Your real password stays secret. มันเปิดได้แค่อีเมล ไม่ได้อย่างอื่น มันแก้ไขบัญชีของคุณไม่ได้ คุณลบมันเมื่อไรก็ได้ แล้วจะมีแค่โปรแกรมนั้นที่หยุดทำงาน รหัสผ่านจริงของคุณยังเป็นความลับอยู่ 它只能打开邮件,别的都不行,也改不了你的账号。你随时可以删掉它,只有那一个程序会失效。你的真密码始终是秘密。
Step 2 — put them in a .env file. This file sits next to your code. It never goes into git. ขั้นที่ 2 — เก็บไว้ในไฟล์ .env ไฟล์นี้วางไว้ข้าง ๆ โค้ด และไม่ขึ้น git เด็ดขาด 第 2 步 —— 放进 .env 文件。这个文件放在代码旁边,永远不进 git。
# .env — this file holds secrets. It never goes into git. GMAIL_ADDRESS=you@gmail.com GMAIL_APP_PASSWORD=abcdefghijklmnop YAHOO_ADDRESS=you@yahoo.com YAHOO_APP_PASSWORD=qrstuvwxyzabcdef SMTP_TIMEOUT=30 # .gitignore — add this line before your first commit .env
Step 3 — model them with Pydantic. One small library reads the file for you. ขั้นที่ 3 — ทำโมเดลด้วย Pydantic มีไลบรารีเล็ก ๆ ตัวหนึ่งอ่านไฟล์นี้ให้ 第 3 步 —— 用 Pydantic 建模。有个小库会替你读这个文件。
# pip install pydantic-settings from pydantic import Field, SecretStr from pydantic_settings import BaseSettings, SettingsConfigDict class MailSettings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") gmail_address: str gmail_app_password: SecretStr = Field(min_length=16) yahoo_address: str yahoo_app_password: SecretStr = Field(min_length=16) smtp_timeout: int = 30 settings = MailSettings() # reads .env, then the real environment print(settings) gmail_address='you@gmail.com' gmail_app_password=SecretStr('**********') yahoo_address='you@yahoo.com' yahoo_app_password=SecretStr('**********') smtp_timeout=30
Forget the .env file, and the program stops at once. It names every field that is missing. ถ้าลืมไฟล์ .env โปรแกรมจะหยุดทันที พร้อมบอกชื่อทุกฟิลด์ที่ขาดไป 如果忘了 .env,程序立刻停下,并点名每一个缺失的字段。
MailSettings() 4 validation errors for MailSettings gmail_address Field required [type=missing, input_value={}, input_type=dict] gmail_app_password Field required [type=missing, input_value={}, input_type=dict] ... # And a password that was pasted badly: gmail_app_password Value should have at least 16 items after validation, not 5 [type=too_short, input_value='short', input_type=str]
🙈 What SecretStr is for 🙈 SecretStr มีไว้ทำอะไร 🙈 SecretStr 是干什么的 Logs leak passwords. SecretStr stops that. We tested all three ways out. None of them printed the password. ล็อกไฟล์คือจุดที่รหัสผ่านรั่ว SecretStr ช่วยกันไว้ เราทดสอบทางออกทั้งสามทางแล้ว ไม่มีทางไหนพิมพ์รหัสผ่านออกมาเลย 日志是密码泄露的地方。SecretStr 挡住了它。三条出口我们都测过,没有一条会把密码打出来。
print(settings) # SecretStr('**********') — safe print(settings.model_dump()) # still masked — safe print(settings.model_dump_json()) # still masked — safe # The real value comes out in exactly one place, where you log in: password = settings.gmail_app_password.get_secret_value()
Step 4 — send a message. The settings object carries the login for you. ขั้นที่ 4 — ส่งอีเมลจริง อ็อบเจกต์ settings พกข้อมูลล็อกอินไปให้เอง 第 4 步 —— 发一封信。settings 对象替你带着登录信息。
import smtplib from email.message import EmailMessage msg = EmailMessage() msg["From"] = settings.gmail_address msg["To"] = "friend@example.com" msg["Subject"] = "Sawadee from Chiang Mai" msg.set_content("Sent from Python.") with smtplib.SMTP_SSL("smtp.gmail.com", 465, timeout=settings.smtp_timeout) as server: server.login(settings.gmail_address, settings.gmail_app_password.get_secret_value()) server.send_message(msg) # Yahoo is the same code. Only the host changes: # smtplib.SMTP_SSL("smtp.mail.yahoo.com", 465)
Serviceบริการ服务 Send (SMTP)ส่ง (SMTP)发送 (SMTP) Read (IMAP)อ่าน (IMAP)读取 (IMAP) Loginการล็อกอิน登录方式
📧 Gmail smtp.gmail.com:465 SSL imap.gmail.com:993 SSL App passwordApp password应用专用密码
💜 Yahoo smtp.mail.yahoo.com:465 SSL imap.mail.yahoo.com:993 SSL App passwordApp password应用专用密码
⚠️ The Gmail API is a different road ⚠️ Gmail API เป็นอีกเส้นทางหนึ่ง ⚠️ Gmail API 是另一条路 Everything above uses SMTP and IMAP, the old mail protocols. An app password is enough for them. The Gmail API is different. It uses OAuth, with a credentials.json and a token.json file. There is no password at all. That token also expires, so it needs renewing. Yahoo has no such option — the app password is the only road. ทุกอย่างข้างบนใช้ SMTP และ IMAP ซึ่งเป็นโปรโตคอลอีเมลแบบเดิม app password ก็เพียงพอแล้ว แต่ Gmail API ต่างออกไป มันใช้ OAuth พร้อมไฟล์ credentials.json และ token.json ไม่มีรหัสผ่านเลย และโทเคนนั้นหมดอายุได้ ต้องต่ออายุเป็นระยะ ส่วน Yahoo ไม่มีทางเลือกนี้ app password คือทางเดียว 上面全部走 SMTP 和 IMAP,也就是老牌邮件协议,用应用专用密码就够。Gmail API 不一样:它走 OAuth,要 credentials.jsontoken.json 两个文件,根本没有密码。那个 token 还会过期,需要续期。Yahoo 没有这个选项,应用专用密码是唯一的路。
Notice what happened here. A data class would take an empty password without a word. You would find out at the mail server. Inside a try block. At midnight. Pydantic stops at start-up, and names the field. สังเกตสิ่งที่เกิดขึ้นตรงนี้ data class จะรับรหัสผ่านว่างเปล่าโดยไม่พูดอะไรเลย คุณจะไปรู้ตอนที่เมลเซิร์ฟเวอร์ปฏิเสธ ใน try block ตอนเที่ยงคืน ส่วน Pydantic หยุดตั้งแต่ตอนเริ่มโปรแกรม และบอกชื่อฟิลด์ให้ด้วย 注意这里发生了什么。数据类会一声不吭地收下一个空密码。你要等到邮件服务器拒绝时才发现 —— 在半夜的某个 try 块里。Pydantic 在启动时就停下,并且点名是哪个字段。

1️⃣2️⃣ Check yourself · ทดสอบตัวเอง · 自我检测

0 / 6 correct
A web form sends age = "28" as text. Which one silently stores the text?
表单送来文本 "28",哪个会不声不响地把文本存下来? · ฟอร์มส่ง "28" มาเป็นข้อความ ตัวไหนเก็บข้อความนั้นเงียบ ๆ
A plain @dataclass
attrs with a validator
Pydantic
All three
A data class never checks the type hint. age holds the string '28', and nothing warns you. The bug appears later, when you try to do maths with it.
Same input, "28". What does Pydantic do by default?
同样是 "28",Pydantic 默认会怎么做? · ค่าเดิม "28" Pydantic ทำอะไรโดยค่าเริ่มต้น
Refuses it
Turns it into the number 28
Stores the text, like a data class
Sets age to None
Pydantic coerces by default — "28" becomes 28. This is what makes it good at web forms. Turn it off with model_config = {"strict": True}.
attrs, with validator=validators.instance_of(int). What happens with "28"?
attrs 用 instance_of(int),传 "28" 会怎样? · attrs ที่ใส่ instance_of(int) ถ้าส่ง "28" จะเกิดอะไรขึ้น
A TypeError — attrs never coerces
It becomes 28, like Pydantic
It is stored as text
A warning, then it continues
attrs checks, it does not repair. You asked for an int and got a str, so it raises. If you want repair, add converter=int yourself.
You write guests: list = [] in a data class. What happens?
在数据类里写 guests: list = [],会怎样? · เขียน guests: list = [] ใน data class จะเกิดอะไรขึ้น
Python refuses — use default_factory
It works fine
Every object shares one list, silently
The list becomes frozen
ValueError: mutable default <class 'list'> ... use default_factory. Python stops you, because one shared list would be a real bug. Write field(default_factory=list).
You are looping over 5 million rows inside your own program. Which fits best?
在自己程序里循环 500 万行,用哪个最合适? · วนลูป 5 ล้านแถวในโปรแกรมตัวเอง ควรใช้ตัวไหน
A data class — the data is already trusted
Pydantic, for safety
attrs with every validator on
A plain dict
The data is yours already, so there is nothing to check. At 5 million rows the 0.75 µs adds up. That is about 3 seconds of pure checking. Check once at the edge instead.
Your program reads JSON from another company's API. Where does Pydantic belong?
程序要读别家公司的 JSON,Pydantic 该放在哪? · โปรแกรมอ่าน JSON จาก API บริษัทอื่น ควรวาง Pydantic ไว้ตรงไหน
At the edge, where the JSON arrives
On every class in the program
Only in your tests
Nowhere — JSON is already clean
Check once, at the door. After that the data is clean. Plain data classes carry it around. They are five times faster.