
咱们先唠唠,你是不是也遇到过这种情况:写 Python 项目时,想把配置(比如数据库地址、参数设置)和代码分开,不然每次改配置都要动代码,太麻烦了;或者想存点结构化数据,JSON 虽然能用,但写注释、处理多行文本总觉得别扭?
这时候 YAML 就派上用场了!今天咱们就从 0 开始,把 Python 里用 YAML 的那点事儿讲透,不管你是新手还是有点经验,看完都能直接上手用。
先搞明白基础:YAML 不是什么高深技术,就是一种 “人类能轻松看懂” 的数据序列化语言。简单说,就是把 Python 里的字典、列表这些数据,写成文件存起来;反过来,也能把文件里的内容读成 Python 能直接用的数据。
它的优点特别实在,咱们列个表更清楚:
优点 | 大白话解释 |
|---|---|
简单易读 | 不用写大括号、中括号(比如 JSON 的 {}[]),靠缩进就能表示结构,像写笔记一样 |
语言支持广 | 不光 Python 能用,Java、Go、JavaScript 都支持,跨语言传数据方便 |
支持多种数据类型 | 字符串、数字、布尔值这些基础类型,还有字典、列表,甚至嵌套结构都能搞定 |
能加注释 | 用 #开头写注释,以后看自己写的配置,一眼就知道啥意思 |
适合配置和数据存储 | 存配置文件(比如项目参数)、数据文件(比如翻译内容)都特别合适 |
Python 本身不自带处理 YAML 的工具,得装个第三方库叫pyyaml,跟装其他库一样简单,就用pip命令。
pip install pyyamlsudo:sudo pip install pyyamlpython -m pip代替pip:python -m pip install pyyamlYAML 文件的后缀一般是.yaml(推荐)或者.yml,随便用个文本编辑器就能写(比如记事本、VSCode、Sublime)。
咱们先写个最简单的,叫first.yaml,内容如下:
# 这是注释,#后面的内容不会被解析
name: 张三 # 键是name,值是字符串“张三”
age: 25 # 键是age,值是整数25
is_student: false # 键是is_student,值是布尔值false
score: 98.5 # 键是score,值是浮点数98.5
hobbies: # 键是hobbies,值是列表(用-开头表示列表元素)
- 篮球
- 编程
- 看电影
address: # 键是address,值是字典(嵌套结构,靠缩进)
province: 广东
city: 深圳
detail: 某小区12栋注意两个关键规则(刚开始容易错):
name: 张三,冒号后没空格会报错)。写好 YAML 文件后,怎么在 Python 里读它呢?就用刚装的pyyaml库,步骤特别固定,咱们写个 Python 脚本load_yaml.py:
import yaml # 导入pyyaml库
# 方法1:读取外部YAML文件(最常用)
def load_yaml_file(file_path):
try:
# 用open打开文件,指定编码utf-8(避免中文乱码)
with open(file_path, 'r', encoding='utf-8') as f:
# 用safe_load加载,重点:别用load!后面讲为什么
data = yaml.safe_load(f)
return data
except FileNotFoundError:
print(f"报错:没找到{file_path}这个文件!")
return None
except Exception as e:
print(f"加载YAML出错了:{e}")
return None
# 调用函数,加载咱们刚才写的first.yaml
yaml_data = load_yaml_file("first.yaml")
# 打印结果,看看是不是Python能直接用的数据
if yaml_data:
print("加载出来的Python数据:")
print(yaml_data)
print("n单独取某个值:")
print(f"名字:{yaml_data['name']}")
print(f"年龄:{yaml_data['age']}")
print(f"第一个爱好:{yaml_data['hobbies'][0]}")
print(f"城市:{yaml_data['address']['city']}")first.yaml和load_yaml.py放在同一个文件夹里python load_yaml.py你会看到这样的输出,说明 YAML 里的数据已经变成 Python 的字典、列表了:
加载出来的Python数据:
{'name': '张三', 'age': 25, 'is_student': False, 'score': 98.5, 'hobbies': ['篮球', '编程', '看电影'], 'address': {'province': '广东', 'city': '深圳', 'detail': '某小区12栋'}}
单独取某个值:
名字:张三
年龄:25
第一个爱好:篮球
城市:深圳YAML 支持的所有数据类型,咱们用表格整理清楚,每个类型都给 YAML 写法和对应的 Python 结果,看完直接能套用。
数据类型 | YAML 写法示例 | Python 对应类型 | Python 加载结果 | 注意事项 |
|---|---|---|---|---|
字符串 | name: 张三name: "张三"name: ' 张三' | str | ' 张三' | 一般不用引号,有特殊字符(比如空格、#)才用 |
整数 | age: 25count: -10 | int | 25-10 | 直接写数字,不用加引号 |
浮点数 | score: 98.5pi: 3.14e2 | float | 98.5314.0 | 支持科学计数法(3.14e2=314) |
布尔值 | is_ok: trueis_no: false | bool | TrueFalse | 不区分大小写(TRUE 也可以),但推荐小写 |
Null 值 | empty: ~none_val: null | None | None | ~ 或 null 都表示空,Python 里是 None |
这俩是最常用的,尤其是嵌套结构,咱们分开讲:
YAML 里的字典就是 “键:值” 的组合,嵌套字典靠缩进:
# YAML写法
person:
name: 李四
age: 30
contact: # 嵌套字典
phone: 13800138000
email: lisi@example.com加载到 Python 后是这样的字典:
{'person': {'name': '李四', 'age': 30, 'contact': {'phone': '13800138000', 'email': 'lisi@example.com'}}}YAML 里的列表用-开头,每个-代表一个元素,嵌套列表也靠缩进:
# YAML写法
fruits: # 普通列表
- 苹果
- 香蕉
- 橙子
students: # 嵌套列表(列表里套字典)
- name: 王五
age: 22
- name: 赵六
age: 23加载到 Python 后是这样的:
{'fruits': ['苹果', '香蕉', '橙子'], 'students': [{'name': '王五', 'age': 22}, {'name': '赵六', 'age': 23}]}写个test_types.yaml,把上面的类型都放进去:
# 基础类型
string_val: 测试字符串
int_val: 100
float_val: 3.14
bool_val: true
null_val: ~
# 复杂类型
dict_val:
key1: val1
key2: val2
list_val:
- 元素1
- 元素2
nested_val: # 嵌套(字典里套列表)
class: 三年级1班
students:
- 小明
- 小红再写个test_types.py加载它:
import yaml
with open("test_types.yaml", 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
# 打印每个值的类型和内容
print("string_val:", type(data['string_val']), "->", data['string_val'])
print("int_val:", type(data['int_val']), "->", data['int_val'])
print("float_val:", type(data['float_val']), "->", data['float_val'])
print("bool_val:", type(data['bool_val']), "->", data['bool_val'])
print("null_val:", type(data['null_val']), "->", data['null_val'])
print("dict_val:", type(data['dict_val']), "->", data['dict_val'])
print("list_val:", type(data['list_val']), "->", data['list_val'])
print("nested_val:", type(data['nested_val']), "->", data['nested_val'])运行后会看到每个值的类型,确认 YAML 和 Python 类型是对应的,比如bool_val是bool类型,dict_val是dict类型。
YAML 有几个特别好用的功能,学会了能省很多事,咱们一个个讲,每个都给示例。
这个简单,用#开头,后面的内容都是注释,YAML 解析的时候会忽略:
# 这是一个项目配置文件
# 作者:XXX
# 日期:不用写死,运行时Python能获取
# 数据库配置
db:
host: localhost # 数据库地址,本地测试用localhost
port: 3306 # MySQL默认端口
user: root # 用户名,生产环境别用root!
password: 123456 # 密码,实际项目要存在环境变量里,别写这注意:注释只能写在单独一行,或者键值对的后面,不能插在键和值中间(比如name: # 注释 张三这样会报错)。
有时候要存大段文本(比如日志模板、邮件内容),用字符串写一行太别扭,YAML 的多行字符串就很方便,关键是区分|(管道符)和>(折叠符)。
|:保留所有换行和空格,写的时候是什么样,加载后就是什么样。>:把换行变成空格(除了最后一个换行),适合存一段连续的文本,不想换行。multi_line.yaml# 管道符|:保留换行
text_with_newline: |
第一行文本
第二行文本(前面有2个空格)
第三行文本(前面有4个空格)
第四行文本
# 折叠符>:换行变空格
text_no_newline: >
这是一段很长的文本,
本来写了换行,
但用了折叠符后,
会变成一整行(最后会留一个换行)load_multi_line.py:import yaml
with open("multi_line.yaml", 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
print("=== 管道符|的结果(保留换行)===")
print(data['text_with_newline'])
print("=== 折叠符>的结果(换行变空格)===")
print(data['text_no_newline'])你会明显看到区别:
=== 管道符|的结果(保留换行)===
第一行文本
第二行文本(前面有2个空格)
第三行文本(前面有4个空格)
第四行文本
=== 折叠符>的结果(换行变空格)===
这是一段很长的文本, 本来写了换行, 但用了折叠符后, 会变成一整行(最后会留一个换行)注意:多行字符串的缩进要跟父元素的缩进一致(比如上面text_with_newline下面的文本,缩进都是 2 个空格,跟text_with_newline的键对齐),不然会解析错。
如果 YAML 里有重复的数据(比如多个用户用同一个地址),不用每次都写一遍,用锚点(&) 标记重复数据,再用别名(*) 引用,省事又不容易错。
anchor1.yaml)# 用&定义锚点(anchor_address是锚点名字,随便起)
address: &anchor_address
province: 浙江
city: 杭州
street: 西湖路
# 用*引用锚点
user1:
name: 小明
addr: *anchor_address # 引用上面的address
user2:
name: 小红
addr: *anchor_address # 再引用一次{'address': {'province': '浙江', 'city': '杭州', 'street': '西湖路'},
'user1': {'name': '小明', 'addr': {'province': '浙江', 'city': '杭州', 'street': '西湖路'}},
'user2': {'name': '小红', 'addr': {'province': '浙江', 'city': '杭州', 'street': '西湖路'}}}如果想在引用的基础上改点数据(比如 user2 的街道不一样),可以用<<把锚点的字典合并进来,再覆盖需要改的键:
address: &anchor_address
province: 浙江
city: 杭州
street: 西湖路
user1:
name: 小明
<<: *anchor_address # 合并锚点的字典,相当于把province、city、street直接写在这里
user2:
name: 小红
<<: *anchor_address # 先合并
street: 钱江路 # 覆盖street的值{'address': {'province': '浙江', 'city': '杭州', 'street': '西湖路'},
'user1': {'name': '小明', 'province': '浙江', 'city': '杭州', 'street': '西湖路'},
'user2': {'name': '小红', 'province': '浙江', 'city': '杭州', 'street': '钱江路'}}看,user2 的 street 变成了 “钱江路”,其他字段还是锚点里的,特别方便!
如果数据很简单,不想用缩进占太多行,可以用紧凑写法:字典用{}包起来,列表用[]包起来,跟 JSON 有点像,但 YAML 支持的更多。
compact.yaml):# 紧凑字典:用{}
person: {name: 小李, age: 28, is_student: false}
# 紧凑列表:用[]
fruits: [苹果, 香蕉, 橙子]
# 混合紧凑:列表里套字典
students: [{name: 小王, age: 20}, {name: 小刘, age: 21}]跟普通写法完全一样,就是写起来更短:
{'person': {'name': '小李', 'age': 28, 'is_student': False},
'fruits': ['苹果', '香蕉', '橙子'],
'students': [{'name': '小王', 'age': 20}, {'name': '小刘', 'age': 21}]}注意:紧凑写法只适合简单数据,复杂的嵌套还是用缩进写法,不然会乱。
前面加载 YAML 的时候,我一直用yaml.safe_load(),没提yaml.load(),不是忘了,是yaml.load()有大风险!
yaml.load()默认会用一个叫Loader的解析器,这个解析器能执行 Python 代码!如果加载的 YAML 文件里有恶意代码(比如删除你电脑文件的命令),就会直接运行,后果很严重。
如果有人给你一个malicious.yaml,内容是这样的:
!!python/object/apply:os.system ["rm -rf /"]你要是用yaml.load()加载:
import yaml
with open("malicious.yaml", 'r') as f:
yaml.load(f) # 危险!会执行rm -rf /(删除Linux所有文件)在 Linux/Mac 上运行,你的文件就全没了!Windows 上虽然命令不生效,但也可能执行其他恶意操作。
不管什么时候,都用yaml.safe_load(),或者指定yaml.SafeLoader:
# 方法1:推荐,简单
data = yaml.safe_load(f)
# 方法2:等价于safe_load,更明确
data = yaml.load(f, Loader=yaml.SafeLoader)safe_load()只会解析 YAML 的基础数据类型(字典、列表、字符串等),不会执行任何 Python 代码,绝对安全。
讲了这么多理论,咱们来个实际项目里常用的场景:多语言支持。比如你的程序要支持中文和英文,把翻译内容存在 YAML 里,用户选哪个语言就加载哪个文件,不用改代码。
先建两个 YAML 文件,分别存中文和英文的翻译:
zh.yaml(中文翻译):welcome: "欢迎使用本程序!"
select_lang: "请选择语言(zh=中文,en=英文):"
input_error: "输入错误,请重新输入!"
exit_msg: "程序已退出,再见!"en.yaml(英文翻译):welcome: "Welcome to the program!"
select_lang: "Please select language (zh=Chinese, en=English): "
input_error: "Input error, please try again!"
exit_msg: "Program exited, goodbye!"创建multi_lang.py,功能是:
import yaml
def load_translation(lang):
"""加载对应语言的翻译文件"""
# 定义语言和文件名的对应关系
lang_files = {
'zh': 'zh.yaml',
'en': 'en.yaml'
}
# 检查语言是否支持
if lang not in lang_files:
return None
# 加载YAML文件
try:
with open(lang_files[lang], 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
except Exception as e:
print(f"加载翻译文件失败:{e}")
return None
def main():
# 先提示选择语言(默认中文)
default_lang = 'zh'
print(f"默认语言是中文,如需英文请输入'en'")
user_lang = input("请输入语言(zh/en):").strip().lower() or default_lang
# 加载翻译
trans = load_translation(user_lang)
if not trans:
print("不支持的语言,使用默认中文!")
trans = load_translation('zh')
# 用翻译内容和用户交互
print("n" + trans['welcome'])
# 这里可以加其他功能,比如显示菜单
input("n按回车键退出...")
print(trans['exit_msg'])
if __name__ == "__main__":
main()zh.yaml、en.yaml、multi_lang.py放同一个文件夹multi_lang.py,输入zh或en试试默认语言是中文,如需英文请输入'en'
请输入语言(zh/en):en
Welcome to the program!
按回车键退出...
Program exited, goodbye!这个场景的好处是:以后要加日语、法语,只需要新建jp.yaml、fr.yaml,不用改 Python 代码,维护起来特别方便。
刚开始用 YAML,很容易踩坑,我整理了几个最常见的问题,告诉你怎么解决。
加载时报错yaml.scanner.ScannerError: mapping values are not allowed here,或者expected <block end>, but found '<block sequence start>'。
# 错误:address下面的province用了Tab缩进,其他用空格
person:
name: 张三
address:
province: 广东 # 这里是Tab,错了!
city: 深圳 # 这里是2个空格报错yaml.parser.ParserError: expected ':'。
name:张三,少了空格)name 张三)# 错误1:冒号后没空格
name:张三
age:25
# 错误2:漏了冒号
name 张三严格遵守 “键:值” 格式,冒号后必须加一个空格。
YAML 里写的 “yes”,加载后变成了 Python 的True,不是字符串。
YAML 会把yes/no/true/false这些词解析成布尔值,如果你想让它们是字符串,必须加引号。
# 会被解析成布尔值True
is_ok: yes
# 正确:加引号,解析成字符串"yes"
is_ok_str: "yes"加载 YAML 后,中文显示成乱码(比如'xe5xbcxa0xe4xb8x89')。
open 文件时没指定编码encoding='utf-8'。
打开文件时一定要加encoding='utf-8':
with open("first.yaml", 'r', encoding='utf-8') as f: # 加encoding
data = yaml.safe_load(f)如果面试 Python 相关岗位,可能会被问到 YAML,这些问题和回答记下来,够用了。
YAML 是一种人类可读的数据序列化语言,主要用来存配置文件或结构化数据。优点有:
需要第三方库pyyaml,先装pip install pyyaml。加载文件用yaml.safe_load(),步骤是:
open(file_path, 'r', encoding='utf-8')打开文件;yaml.safe_load(f)加载内容,得到 Python 的字典或列表;yaml.load(),因为它不安全,可能执行恶意代码。都是用来处理多行字符串的,区别在换行的处理:
|:会保留所有换行和空格,写的时候是什么格式,加载后就是什么格式;>:会把换行转换成空格(最后一个换行保留),适合存连续的文本,不想换行。用锚点和别名:
&锚点名定义要重用的数据(比如address: &addr {province: 北京});*锚点名引用数据(比如user: {name: 张三, addr: *addr});<<合并锚点字典,再覆盖键(比如user: {<<: *addr, city: 朝阳})。因为yaml.load()默认用Loader解析器,这个解析器能执行 Python 代码,如果加载的 YAML 有恶意代码(比如执行系统命令),会造成安全风险。生产环境必须用yaml.safe_load(),它只会解析基础数据类型,不会执行任何代码,更安全。
区别主要在可读性和功能:
使用场景:
看到这里,你应该明白 YAML 的核心价值了:把数据和代码分开,让配置更灵活、维护更简单。
比如:
YAML 不难,重点是记住缩进规则、安全加载,再掌握锚点、多行字符串这些实用功能,就能轻松搞定 Python 里的配置管理和数据序列化了。
赶紧动手试试吧!把上面的示例代码跑一遍,遇到问题对照 “踩坑指南” 解决,很快就能上手。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。