1. 概览
变量只是一个名字,真正参与计算、拼接、判断和传输的是它当前指向的数据对象。数据对象的类型决定了它能做什么:数字可以相加,字符串可以拼接,列表可以追加元素,字典可以通过键找到值。
1price = 302name = "Python"3topics = ["变量", "模块"]45print(price + 10)6print(name.lower())7topics.append("数据类型")8print(topics)
如果不清楚一个值的类型,就很容易出现这类错误:把数字当成字符串拼接,把列表当成字典读取,或者把 None 当成已经存在的结果。学习数据类型,是为了知道每种数据应该如何使用
2. 数字
int 表示整数,float 表示带小数的浮点数,complex 表示复数。日常的 FastAPI 和 LangChain 代码中,最常使用的是前两种:
1count = 32ratio = 0.7534print(count + 2)5print(ratio * 100)6print(type(count)) # <class 'int'>7print(type(ratio)) # <class 'float'>
Python 的整数不会被固定在 32 位或 64 位范围内,通常会根据可用内存自动扩展。浮点数适合表示一般的小数,但由于计算机使用二进制保存浮点数,金额等对精度要求很高的场景不应直接依赖普通 float。
需要精确计算金额时,可以使用 decimal.Decimal:
1from decimal import Decimal23price = Decimal("0.10")4quantity = 356print(price * quantity) # 0.30
3. 布尔
布尔类型只有两个值:True 和 False。它通常用于条件判断、开关配置和接口状态:
1debug = True2is_authenticated = False34if debug:5print("当前处于调试模式")67if not is_authenticated:8print("请先登录")
Python 中 False、None、数字 0、空字符串、空列表、空字典等值,在条件判断里会被当作“假”;其他大多数值会被当作“真”。这种规则叫真值判断:
1values = [0, "", [], "Python", [1]]23for value in values:4if value:5print(f"{value!r} 会被当作真")
不要把“真值判断”和“类型是 bool”混为一谈。空字符串的类型仍然是 str,只是它在 if 中表现为假。
4. 字符串
字符串表示文本,可以使用单引号或双引号。项目中建议统一一种写法,避免同一文件中风格混杂:
1course = "LangChain Python"23print(course[0]) # L4print(course[0:9]) # LangChain5print(course.lower()) # langchain python6print(len(course)) # 15
字符串是不可变对象。lower()、replace() 等方法不会在原字符串上偷偷修改,而是返回一个新的字符串:
1message = "Hello"2new_message = message.upper()34print(message) # Hello5print(new_message) # HELLO
拼接变量和文本时,优先使用 f-string:
1name = "小微"2topic = "数据类型"34message = f"你好,{name},今天学习{topic}。"5print(message) # 你好,小微,今天学习数据类型。
5. 空值
None 表示“没有值”或“目前还没有结果”,它不是字符串,也不是数字:
1result = None23if result is None:4print("查询还没有完成")
判断空值时使用 is None,而不是把它写成字符串 "None"。在 FastAPI 接口中,数据库里没有找到的可选字段,也经常用 None 表示:
1nickname = None23response = {4"nickname": nickname,5}67print(response) # {'nickname': None}
6. 容器
容器用于保存多个值。它们的区别主要体现在顺序、是否允许重复、是否可以修改,以及如何读取数据:
| 类型 | 示例 | 有顺序 | 是否允许重复 | 是否可修改 | 常见用途 |
|---|---|---|---|---|---|
list | ["Python", "FastAPI"] | 是 | 是 | 是 | 需要增删改的一组数据 |
tuple | ("Python", "FastAPI") | 是 | 是 | 否 | 不希望被修改的一组数据 |
set | { "Python", "FastAPI" } | 不保证 | 否 | 是 | 去重和集合运算 |
dict | { "name": "小微" } | 保持插入顺序 | 键不能重复 | 是 | 用键描述结构化数据 |
列表通过下标读取,下标从 0 开始:
1topics = ["变量", "模块"]2topics.append("数据类型")34print(topics[0])5print(topics)
元组使用圆括号,创建后不能直接追加或替换元素:
1coordinate = (120.1, 30.2)23print(coordinate[0])4# coordinate[0] = 121.0 # TypeError:元组不能修改
集合会自动去重,但不适合依赖元素的下标:
1tags = {"python", "fastapi", "python"}23print(tags) # {'python', 'fastapi'},顺序不作为业务依据
字典通过键读取值,键通常使用字符串:
1profile = {2"name": "小微",3"age": 18,4}56print(profile["name"])7profile["city"] = "杭州"
3. 对象
在 JavaScript 的入门教材中,经常把数据分为“基础类型”和“引用类型”。这个说法用于帮助理解 JavaScript 的行为,但 Python 没有一套完全对应的官方类型分类。
在 Python 中,整数、字符串、列表、字典和函数都是对象。变量名不是一个固定大小的盒子,而是指向对象的名字:
1number = 182message = "Python"34print(type(number))5print(type(message))
所以,我们不问一个变量是“基础类型还是引用类型”,而是思考:这个变量当前指向什么类型的对象?这个对象能不能在原地修改?
- 这个名字当前指向什么类型的对象?
- 这个对象能不能在原地修改?
因此,当执行 second = first 时,Python 会让两个名字指向同一个对象,不会自动复制一份:
1first = ["Python"]2second = first34second.append("LangChain")56print(first) # ['Python', 'LangChain']7print(second) # ['Python', 'LangChain']8print(first is second) # True
is 比较的是两个名字是否指向同一个对象,== 比较的是两个对象的值是否相等:
1first = ["Python"]2second = ["Python"]34print(first == second) # True:内容相同5print(first is second) # False:不是同一个列表对象
整数、浮点数、布尔值、字符串和元组通常被称为不可变对象。重新“改变”它们时,实际上是创建新对象,再让变量名指向新对象:
1message = "你好"2old_message = message3message += ",小微"45print(old_message) # 你好6print(message) # 你好,小微7print(old_message is message) # False
列表、字典和集合是常见的可变对象。调用 append()、修改字典键或向集合中添加元素,会直接改变原对象:
1profile = {"name": "小微"}2same_profile = profile34same_profile["age"] = 1856print(profile) # {'name': '小微', 'age': 18}7print(profile is same_profile) # True
这就是 Python 中最重要的区别:不是“某类变量保存值、另一类变量保存引用”,而是所有变量名都绑定对象,只是对象有的能修改,有的不能修改。
如果想让两个列表互不影响,需要显式创建副本
1topics = ["变量"]2copied_topics = topics.copy()34copied_topics.append("JSON")56print(topics) # ['变量']7print(copied_topics) # ['变量', 'JSON']
copy() 只复制当前这一层。如果列表中还有嵌套列表或字典,还需要根据数据结构决定使用 copy.deepcopy(),或者重新设计数据的创建方式。初学时先记住:给一个新变量赋值,不等于复制
4. JSON
JSON 是一种文本格式,用于在浏览器、FastAPI 服务和其他程序之间传输结构化数据。JSON 支持六类值:
- 对象(object):使用
{},由字符串键和值组成; - 数组(array):使用
[],保存有顺序的一组值; - 字符串(string):必须使用双引号;
- 数字(number):整数或小数;
- 布尔值(boolean):只能是
true或false; - 空值(null):表示没有值。
下面是一段合法的 JSON:
01{02"name": "小微",03"age": 18,04"isStudent": true,05"skills": ["Python", "FastAPI"],06"profile": {07"city": "杭州"08},09"lastLogin": null10}
最外层的 {} 表示一个对象,里面的每一项都是 "键": 值。值可以继续是数组或对象,因此 JSON 可以表达嵌套结构。
JSON 对语法比较严格:键和字符串必须使用双引号,不能写注释,最后一项后面不能多逗号。下面这些写法不是标准 JSON:
1{ name: '小微' } # 键没有双引号,字符串用了单引号2{ "age": 18, } # 最后一项后面多了逗号3{ "enabled": True } # JSON 使用 true,不是 Python 的 True
Python 中最接近 JSON 对象的是 dict,最接近 JSON 数组的是 list:
1user = {2"name": "小微",3"age": 18,4"is_student": True,5"skills": ["Python", "FastAPI"],6"last_login": None,7}
两者并不是完全相同的语法。常见对应关系如下:
| Python | JSON | 说明 |
|---|---|---|
dict | object | Python 字典通常使用字符串键表达 JSON 对象 |
list、tuple | array | 元组序列化后通常也会变成 JSON 数组 |
str | string | JSON 字符串必须使用双引号表示 |
int、float | number | JSON 没有区分整数和浮点数的类型名 |
True、False | true、false | 大小写不同 |
None | null | 表示空值 |
set | 没有直接对应类型 | 需要先转换为列表 |
Python 的 dict 可以使用整数、元组等可哈希对象作为键,但 JSON 对象的键应当使用字符串。为了让接口行为稳定,定义准备发送给前端的字典时,直接使用字符串键。
7. JSON 转换
json.dumps() 把 Python 对象转换成 JSON 字符串,json.loads() 把 JSON 字符串解析回 Python 对象:
01import json0203user = {04"name": "小微",05"is_student": True,06"last_login": None,07}0809json_text = json.dumps(user, ensure_ascii=False)10print(json_text)1112parsed_user = json.loads(json_text)13print(parsed_user["name"])14print(type(parsed_user)) # <class 'dict'>
dumps() 的返回值是 str,它适合写入 HTTP 响应或文件;loads() 的输入必须是合法 JSON 文本,返回值通常是 dict 或 list。
集合不能直接转换为 JSON:
1import json23tags = {"python", "fastapi"}45# json.dumps(tags) # TypeError:set 不是 JSON 支持的类型6json_text = json.dumps(sorted(tags))7print(json_text)
在转换前把集合明确变成列表,才能决定输出顺序和接口含义,而不是依赖某种偶然的集合遍历顺序。
8. FastAPI
FastAPI 的接口通常接收 JSON 请求体,再返回 JSON 响应。使用 Pydantic 模型可以声明请求数据的结构和类型:
01from fastapi import FastAPI02from pydantic import BaseModel0304app = FastAPI()0506# 定义请求数据结构07class ChatRequest(BaseModel):08question: str09temperature: float = 0.210stream: bool = False1112@app.post("/chat")13async def chat(request: ChatRequest) -> dict[str, object]:14return {15"question": request.question,16"temperature": request.temperature,17"stream": request.stream,18}
客户端发送的 JSON 可以这样写:
1{2"question": "请介绍 Python 的字典。",3"temperature": 0.2,4"stream": false5}
FastAPI 会把请求体解析成 ChatRequest 对象,request.question 是字符串,request.temperature 是浮点数,request.stream 是布尔值。返回的 Python dict 会被 FastAPI 转换为 JSON:False 会变成 false,None 会变成 null。
如果请求字段的类型不符合声明,FastAPI 会在进入函数前返回校验错误。这比在函数内部手动检查每个字段更清晰,也让接口文档能够展示准确的数据结构。
9. 操作
type() 可以查看一个对象的具体类型,isinstance() 可以在业务代码中判断一个值是否属于某个类型或类型集合:
1value = "Python"23print(type(value))4print(isinstance(value, str))5print(isinstance(value, (list, tuple)))
通常不要把 type(value) == str 当成唯一的类型判断方式。isinstance() 能正确处理继承关系,也更方便同时接受多个类型。
10. 类型转换
Python 提供了很多构造函数进行转换:
| 函数 | 说明 |
|---|---|
int(x [,base]) | 将x转换为一个整数 |
long(x [,base] ) | 将x转换为一个长整数 |
float(x) | 将x转换到一个浮点数 |
complex(real [,imag]) | 创建一个复数 |
str(x) | 将对象 x 转换为字符串 |
repr(x) | 将对象 x 转换为表达式字符串 |
eval(str) | 用来计算在字符串中的有效Python表达式,并返回一个对象 |
tuple(s) | 将序列 s 转换为一个元组 |
list(s) | 将序列 s 转换为一个列表 |
set(s) | 转换为可变集合 |
dict(d) | 创建一个字典。d 必须是一个序列 (key,value)元组。 |
frozenset(s) | 转换为不可变集合 |
chr(x) | 将一个整数转换为一个字符 |
unichr(x) | 将一个整数转换为Unicode字符 |
ord(x) | 将一个字符转换为它的整数值 |
hex(x) | 将一个整数转换为一个十六进制字符串 |
oct(x) | 将一个整数转换为一个八进制字符串 |
1age_text = "18"2age = int(age_text)3score = float("98.5")4message = str(age)56print(age + 2)7print(score)8print(message)
转换不是总能成功。int("十八") 会抛出 ValueError,所以处理用户输入时,应当做好异常处理或交给 Pydantic 校验:
1try:2age = int("十八")3except ValueError:4age = None56print(age)