数据类型和变量
程序需要处理姓名、年龄、价格、登录状态等各种数据。数据类型说明一个值是什么、能参加哪些运算;变量则是程序中引用这个值的名字。
学完本章后,你应该能够:
- 使用 Python 的常见内置类型表示数据;
- 声明、读取和重新赋值变量;
- 判断一个值的类型,并在需要时转换类型;
- 理解可变对象、不可变对象和共享引用;
- 避免初学变量和类型时常见的错误。
本文示例适用于 Python 3。可以把代码保存为 .py 文件后运行,也可以逐段输入 Python 交互式解释器。
从一个小程序开始
下面的程序使用变量保存一名学生的信息:
name = "小林"
age = 18
height = 1.72
is_student = True
courses = ["Python", "数学"]
print(name)
print(age + 1)
print(courses[0])
输出为:
小林
19
Python
这几个变量保存了不同类型的值:
| 变量 | 值 | 类型 | 用途 |
|---|---|---|---|
name | "小林" | str | 保存文本 |
age | 18 | int | 保存整数 |
height | 1.72 | float | 保存带小数的数值 |
is_student | True | bool | 表示“是”或“否” |
courses | ["Python", "数学"] | list | 按顺序保存多个课程名称 |
变量让数据有了容易理解的名字。与其在代码各处直接写 1.72,使用 height 能更清楚地表达这个值的含义。
先理解值、对象和变量
在 Python 中,运行时的数据都以对象的形式存在。每个对象至少包含三方面信息:
- 值:对象表示的具体内容,例如整数
18; - 类型:对象支持哪些操作,例如
int可以参与整数运算; - 身份:用于区分两个对象的唯一标识,可以使用
id()查看。
变量并不是一个固定形状的“盒子”,更准确的理解是:变量是指向对象的名字。
age = 18
这行代码完成两个动作:
- 创建或取得值为
18的整数对象; - 让变量名
age引用这个对象。
赋值符号 = 的含义是“让左边的名字引用右边的对象”,不是数学中的“左右相等”。
score = 80
score = 95
第二次赋值让 score 改为引用整数 95,因此旧值 80 不再是 score 当前引用的值。
查看值的类型
内置函数 type() 可以查看一个值的类型:
print(type(18)) # <class 'int'>
print(type(1.72)) # <class 'float'>
print(type("Python")) # <class 'str'>
print(type(True)) # <class 'bool'>
print(type([1, 2, 3])) # <class 'list'>
需要判断一个值是否属于某种类型时,通常使用 isinstance():
age = 18
print(isinstance(age, int)) # True
print(isinstance(age, str)) # False
print(isinstance(age, (int, float))) # True
与直接比较 type(value) == int 相比,isinstance() 还能正确处理继承关系,通常更适合在程序中进行类型判断。
数字类型
Python 常见的数字类型有 int、float 和 complex。
整数 int
int 用于表示没有小数部分的整数,可以是正数、负数或零:
student_count = 45
temperature = -3
empty_count = 0
Python 整数的大小不受固定的 32 位或 64 位范围限制,只要计算机内存允许,就可以保存很大的整数:
big_number = 10 ** 100
print(big_number)
整数还可以使用不同进制的字面量表示:
binary = 0b1010 # 二进制,值为 10
octal = 0o12 # 八进制,值为 10
hexadecimal = 0xA # 十六进制,值为 10
较长的数字可以使用下划线分组,下划线不影响数值:
population = 1_400_000_000
浮点数 float
float 用于表示带小数的数值,也可以使用科学计数法:
price = 19.99
distance = 1.5e3 # 1500.0
浮点数采用二进制近似表示,某些十进制小数不能被精确保存:
print(0.1 + 0.2) # 0.30000000000000004
这不是 Python 算错了,而是计算机浮点数的常见特性。比较浮点计算结果时,可以使用 math.isclose():
import math
result = 0.1 + 0.2
print(math.isclose(result, 0.3)) # True
处理金额等要求十进制精确计算的数据时,不要直接依赖二进制浮点数,可以根据业务需要使用标准库中的 decimal.Decimal。
复数 complex
Python 也内置了复数类型,虚部使用 j 表示:
number = 2 + 3j
print(number.real) # 2.0
print(number.imag) # 3.0
复数常用于科学计算。普通业务程序较少使用,初学时知道它是 Python 的内置数字类型即可。
常用数字运算
a = 7
b = 2
print(a + b) # 9,加法
print(a - b) # 5,减法
print(a * b) # 14,乘法
print(a / b) # 3.5,普通除法,结果为 float
print(a // b) # 3,向下取整除法
print(a % b) # 1,取余数
print(a ** b) # 49,幂运算
// 是向下取整,不是简单地删除小数部分。负数运算时尤其要注意:
print(-7 // 2) # -4
布尔类型 bool
布尔值用于表达条件是否成立,只有两个值:True 和 False。首字母必须大写。
is_logged_in = True
has_permission = False
比较运算通常会产生布尔值:
age = 18
print(age >= 18) # True
print(age == 20) # False
print(age != 20) # True
布尔值可以通过 and、or 和 not 组合:
age = 20
has_ticket = True
can_enter = age >= 18 and has_ticket
print(can_enter) # True
Python 会把某些值当作假值,包括:
False;None;- 数字零,如
0、0.0; - 空字符串
""; - 空容器,如
[]、()、{}和set()。
其他大多数值会被当作真值:
name = ""
if name:
print("已经填写姓名")
else:
print("姓名不能为空")
需要明确得到一个布尔值时,可以调用 bool():
print(bool(0)) # False
print(bool(1)) # True
print(bool("")) # False
print(bool("Python")) # True
字符串类型 str
字符串用于表示文本,可以使用单引号或双引号:
language = "Python"
message = '你好'
两种写法没有类型上的区别。可以选择与内容不冲突的引号:
sentence = "I'm learning Python."
quote = '他说:“你好!”'
转义字符和多行字符串
反斜杠 \ 可以引入转义字符:
message = "第一行\n第二行"
path = "C:\\Users\\Alice"
print(message)
输出为:
第一行
第二行
三引号可以表示跨越多行的字符串:
description = """这是第一行,
这是第二行。"""
字符串索引和切片
字符串中的字符从左到右使用从 0 开始的索引:
word = "Python"
print(word[0]) # P
print(word[1]) # y
print(word[-1]) # n,负数索引从末尾开始
切片可以取得一段字符串,基本格式是 字符串[开始:结束:步长]。开始位置包含在结果中,结束位置不包含在结果中:
word = "Python"
print(word[0:3]) # Pyt
print(word[:2]) # Py
print(word[2:]) # thon
print(word[::2]) # Pto
print(word[::-1]) # nohtyP
字符串不可变
字符串创建后不能直接修改其中某个字符:
word = "Python"
# word[0] = "J" # TypeError:字符串不支持按索引修改
如果要得到不同的文本,需要创建新字符串:
word = "J" + word[1:]
print(word) # Jython
格式化字符串
推荐使用 f-string 把变量放入文本:
name = "小林"
score = 92.5
message = f"{name} 的成绩是 {score:.1f} 分"
print(message)
输出为:
小林 的成绩是 92.5 分
:.1f 表示把浮点数格式化为保留一位小数的形式,它只改变输出格式,不会修改 score 本身。
空值 None
None 表示“没有值”或“值尚未确定”,它的类型是 NoneType:
result = None
print(type(result)) # <class 'NoneType'>
例如,用户还没有填写昵称时,可以先使用 None:
nickname = None
if nickname is None:
print("暂未设置昵称")
判断一个值是不是 None 时,推荐使用 is None 或 is not None,不要写成 == None:
if nickname is not None:
print(nickname)
None、0 和空字符串虽然都属于假值,但含义不同:
| 值 | 常见含义 |
|---|---|
None | 没有值或尚未设置 |
0 | 有一个明确的数字零 |
"" | 有一个明确的空字符串 |
程序应根据业务含义选择合适的值,不要把它们混为一谈。
容器类型
当一个变量需要引用多个值时,可以使用容器。初学阶段最常见的容器如下:
| 类型 | 写法示例 | 是否有序 | 能否修改 | 元素要求 |
|---|---|---|---|---|
list | ["语文", "数学"] | 有序 | 可以 | 元素可以重复 |
tuple | (10, 20) | 有序 | 不可以 | 元素可以重复 |
dict | {"name": "小林", "age": 18} | 保留插入顺序 | 可以 | 键不能重复,按键查找值 |
set | {"Python", "Java"} | 不保证按位置访问 | 可以 | 元素不重复 |
列表 list
列表适合保存一组有顺序、可能发生变化的数据:
courses = ["Python", "数学"]
courses.append("英语")
courses[1] = "高等数学"
print(courses) # ['Python', '高等数学', '英语']
print(len(courses)) # 3
列表可以包含不同类型的值,但实际项目中通常让同一列表中的元素具有一致含义,更容易阅读和处理。
元组 tuple
元组创建后不能添加、删除或替换元素,适合表示结构固定的数据:
point = (10, 20)
print(point[0]) # 10
只有一个元素的元组必须带逗号:
single = (10,)
not_a_tuple = (10)
print(type(single)) # <class 'tuple'>
print(type(not_a_tuple)) # <class 'int'>
字典 dict
字典使用键查找对应的值,适合表示具有多个具名字段的数据:
student = {
"name": "小林",
"age": 18,
"score": 92,
}
print(student["name"]) # 小林
student["score"] = 95
student["city"] = "上海"
访问不存在的键会触发 KeyError。不确定键是否存在时,可以使用 get():
print(student.get("phone")) # None
print(student.get("phone", "暂未填写")) # 暂未填写
集合 set
集合只保留不重复的元素,常用于去重和成员判断:
tags = {"Python", "入门", "Python"}
print(tags) # 只保留一个 Python
print("Python" in tags) # True
空集合必须写成 set(),因为 {} 表示空字典:
empty_set = set()
empty_dict = {}
声明和赋值变量
Python 不需要在变量名前写类型:
username = "alice"
login_count = 3
第一次为名字赋值通常称为定义或创建变量。使用变量前必须先赋值:
# print(total) # NameError:total 还没有定义
total = 100
print(total)
一个变量可以重新引用不同类型的对象
Python 是动态类型语言。类型属于对象,变量名本身不会被永久限制为某一种类型:
value = 10
print(type(value)) # <class 'int'>
value = "十"
print(type(value)) # <class 'str'>
动态类型不等于没有类型。Python 仍然会检查运行时操作是否合法:
age = 18
# print(age + "岁") # TypeError:int 和 str 不能直接相加
虽然变量可以改为引用不同类型的值,但频繁这样做会让代码难以理解。通常应让一个变量始终表达同一种含义。
同时赋值给多个变量
可以在一行中为多个变量赋值:
x, y = 10, 20
name, age, active = "小林", 18, True
左右两边的数量必须一致。这个写法也可以交换两个变量的值,不需要临时变量:
x = 10
y = 20
x, y = y, x
print(x, y) # 20 10
多个变量也可以引用同一个值:
a = b = 0
对整数、字符串等不可变对象,这种写法通常没有问题;对列表等可变对象,则要特别留意共享引用。
增强赋值
在原变量的基础上计算新值时,可以使用增强赋值:
count = 10
count += 1 # 相当于 count = count + 1
count *= 2 # 相当于 count = count * 2
print(count) # 22
对于可变对象,某些增强赋值可能直接修改原对象;对于不可变对象,则会让变量引用一个新对象。理解这一差异有助于排查共享引用问题。
可变对象和不可变对象
对象创建后内容能否改变,是 Python 数据类型的重要区别。
常见的不可变类型包括:
int、float、bool和complex;str;tuple;NoneType。
常见的可变类型包括:
list;dict;set。
不可变对象:计算后得到新对象
整数本身不能被修改:
a = 10
b = a
a = a + 1
print(a) # 11
print(b) # 10
b = a 让两个变量先引用同一个整数对象。执行 a = a + 1 时,Python 计算出新的整数 11,再让 a 引用它;b 仍然引用原来的 10。
可变对象:多个变量可能看到同一次修改
列表可以在原对象上被修改:
first = ["Python", "数学"]
second = first
first.append("英语")
print(first) # ['Python', '数学', '英语']
print(second) # ['Python', '数学', '英语']
second = first 没有复制列表,只是让两个变量引用同一个列表。因此,通过任何一个变量修改列表,另一个变量也会观察到变化。
可以用 is 检查两个变量是否引用同一个对象:
print(first is second) # True
== 比较值是否相等,is 比较是否为同一个对象:
list_a = [1, 2]
list_b = [1, 2]
print(list_a == list_b) # True,内容相等
print(list_a is list_b) # False,不是同一个列表对象
除 None 等单例判断外,业务数据通常使用 == 比较,不使用 is 比较数值或字符串。
复制列表
如果希望修改新列表时不影响原列表,可以创建浅复制:
original = ["Python", "数学"]
copied = original.copy()
copied.append("英语")
print(original) # ['Python', '数学']
print(copied) # ['Python', '数学', '英语']
浅复制只复制最外层容器。容器内部如果还有列表等可变对象,内层对象仍可能被共享:
original = [[1, 2], [3, 4]]
copied = original.copy()
copied[0].append(99)
print(original) # [[1, 2, 99], [3, 4]]
需要连同嵌套对象一起复制时,可以根据数据结构使用 copy.deepcopy()。深复制并非任何场景都适合,复杂对象还可能需要自行定义复制方式。
类型转换
外部输入或计算结果的类型不符合需要时,可以显式转换。
数字与字符串互转
age_text = "18"
age = int(age_text)
price_text = "19.99"
price = float(price_text)
message = "年龄:" + str(age)
print(age + 1) # 19
print(price) # 19.99
print(message) # 年龄:18
转换要求原值的格式合法,否则会触发 ValueError:
# number = int("十八") # ValueError
处理用户或文件提供的数据时,应该考虑转换失败:
text = "18"
try:
age = int(text)
print(f"明年 {age + 1} 岁")
except ValueError:
print("请输入有效的整数")
int() 转换浮点数时会截去小数部分,并不是四舍五入:
print(int(3.9)) # 3
print(int(-3.9)) # -3
print(round(3.9)) # 4
容器之间转换
letters = list("Python")
coordinates = tuple([10, 20])
unique_numbers = set([1, 1, 2, 3])
print(letters) # ['P', 'y', 't', 'h', 'o', 'n']
print(coordinates) # (10, 20)
print(unique_numbers) # {1, 2, 3}
转换不只是更换类型名称,它会按照目标类型的规则构造新值。例如,set() 会去除重复元素,也不会保留可按索引访问的顺序。
input() 的结果总是字符串
input() 从终端读取的结果是 str,即使用户输入的是数字:
age_text = input("请输入年龄:")
print(type(age_text)) # <class 'str'>
参与数字运算前需要转换:
age = int(input("请输入年龄:"))
print(f"明年你将 {age + 1} 岁")
真实程序还应使用 try 和 except 处理用户输入无法转换的情况。
变量命名规则与习惯
一个合法的变量名:
- 只能包含字母、数字和下划线;
- 不能以数字开头;
- 不能使用 Python 关键字;
- 区分大小写,因此
age和Age是两个不同的名字。
user_name = "小林" # 合法
score2 = 95 # 合法
# 2score = 95 # 非法:不能以数字开头
# class = "一班" # 非法:class 是关键字
可以使用 keyword 模块查看关键字:
import keyword
print(keyword.iskeyword("class")) # True
print(keyword.kwlist) # 当前 Python 版本的关键字列表
Python 社区通常遵循以下命名习惯:
- 普通变量使用小写字母,多个单词之间用下划线连接,如
user_name; - 名字应表达含义,优先使用
student_count,避免使用含义模糊的sc; - 不要使用
list、str、sum等内置名称作为变量名,以免遮蔽内置功能; - 约定不会改变的值使用全大写字母,如
MAX_RETRY_COUNT = 3。
全大写只是一种协作约定,Python 不会阻止后续重新赋值:
TAX_RATE = 0.13
变量的作用域
作用域决定一个变量名可以在哪些位置被访问。初学阶段最常见的是全局作用域和函数局部作用域。
site_name = "学习站点" # 全局变量
def greet():
message = "欢迎学习 Python" # 局部变量
print(site_name) # 函数内可以读取全局变量
print(message)
greet()
print(site_name)
# print(message) # NameError:函数外不能访问其局部变量
在函数内部给一个名字赋值时,Python 默认把它当作局部变量:
count = 0
def increase():
count = 1
print(count)
increase() # 1
print(count) # 0
确实需要在函数中重新绑定全局变量时,可以使用 global,但大量依赖全局可变状态会让程序难以理解和测试。通常更推荐通过参数传入数据,并通过返回值交回结果:
def increase(count):
return count + 1
count = 0
count = increase(count)
print(count) # 1
类型注解
Python 允许为变量和函数添加类型注解,帮助读者和类型检查工具理解代码:
name: str = "小林"
age: int = 18
courses: list[str] = ["Python", "数学"]
def calculate_total(price: float, quantity: int) -> float:
return price * quantity
类型注解默认不会在运行时强制限制赋值:
age: int = 18
age = "十八" # Python 运行时通常不会仅因注解而阻止这次赋值
编辑器、静态类型检查器和代码审查可以提前发现这种不一致。类型注解的主要价值是提高可读性和静态检查能力,它不会把 Python 变成静态类型语言。
可能没有值的变量,可以使用 None 的联合类型:
nickname: str | None = None
str | None 表示 nickname 可以引用字符串,也可以引用 None。这种写法需要 Python 3.10 或更高版本;旧版本通常使用 typing.Optional[str]。
删除变量名
del 可以删除一个变量名:
temporary = "临时数据"
del temporary
# print(temporary) # NameError
删除名字不一定会立即销毁对象。如果还有其他变量引用同一个对象,对象仍然可以通过那些变量访问。Python 会在对象不再被需要时管理其内存,普通业务代码通常不需要手动释放变量占用的内存。
del 也能删除容器中的元素,此时修改的是容器:
numbers = [10, 20, 30]
del numbers[1]
print(numbers) # [10, 30]
常见错误
混淆赋值与比较
单个等号 = 用于赋值,两个等号 == 用于比较:
age = 18
print(age == 18) # True
把字符串和数字直接相加
age = 18
# print("年龄:" + age) # TypeError
print("年龄:" + str(age))
print(f"年龄:{age}")
忘记 input() 返回字符串
# 错误思路:input() 的结果不能直接和整数相加
# age = input("请输入年龄:")
# print(age + 1)
age = int(input("请输入年龄:"))
print(age + 1)
用 is 比较普通值
answer = 1000
print(answer == 1000) # 比较值,正确
# print(answer is 1000) # 不应依赖对象身份比较普通数值
记住:== 关注“值是否相等”,is 关注“是否为同一个对象”。判断空值时使用 is None。
意外共享可变对象
first = second = []
first.append("Python")
print(second) # ['Python']
如果需要两个独立列表,应分别创建:
first = []
second = []
使用内置类型名作为变量名
# list = [1, 2, 3]
# numbers = list("123") # TypeError:list 已经不再引用内置类型
更清晰的名字是:
numbers = [1, 2, 3]
综合示例:计算购物金额
下面的程序把本章多个知识点组合起来:
product_name: str = "Python 入门书"
unit_price: float = 59.9
quantity: int = 2
discount: float = 0.8
has_coupon: bool = True
subtotal = unit_price * quantity
if has_coupon:
total = subtotal * discount
else:
total = subtotal
print(f"商品:{product_name}")
print(f"数量:{quantity}")
print(f"应付金额:{total:.2f} 元")
输出为:
商品:Python 入门书
数量:2
应付金额:95.84 元
在这个例子中:
- 字符串、浮点数、整数和布尔值分别表示不同性质的数据;
- 变量名描述了每个值的业务含义;
subtotal和total保存计算得到的结果;if根据布尔值决定是否应用折扣;- f-string 把不同类型的值格式化为便于阅读的文本。
练习
练习一:个人信息
创建变量保存姓名、年龄、身高和是否正在学习 Python,然后用一条 f-string 输出完整信息。
预期输出形式如下:
我叫小林,今年 18 岁,身高 1.72 米,正在学习 Python:True
练习二:交换变量
已有以下代码:
left = "左边"
right = "右边"
不创建第三个变量,交换 left 和 right 的值。
练习三:处理输入
让用户输入长方形的长和宽,把输入转换为浮点数,再输出面积并保留两位小数。
练习四:观察共享引用
先猜测下面的输出,再实际运行并解释原因:
original = [1, 2]
another = original
another.append(3)
print(original)
print(original is another)
练习五:选择合适的类型
为下列数据选择合适的 Python 类型:
- 一篇文章的标题;
- 商品库存数量;
- 用户是否同意协议;
- 一组按顺序排列的考试成绩;
- 用户尚未填写的头像地址;
- 一组不能重复的标签。
参考答案
练习一
name = "小林"
age = 18
height = 1.72
is_learning_python = True
print(
f"我叫{name},今年 {age} 岁,身高 {height} 米,"
f"正在学习 Python:{is_learning_python}"
)
相邻的字符串字面量会自动拼接,因此可以把较长的 f-string 放在括号内分行书写。
练习二
left = "左边"
right = "右边"
left, right = right, left
print(left) # 右边
print(right) # 左边
右侧的值会先组成待分配的数据,再分别赋给左侧的变量,因此不会因为先修改 left 而丢失原值。
练习三
length = float(input("请输入长:"))
width = float(input("请输入宽:"))
area = length * width
print(f"长方形面积为:{area:.2f}")
这里使用 float(),所以用户既可以输入 5,也可以输入 5.5。真实程序还需要处理空输入和无法转换成数字的输入。
练习四
输出为:
[1, 2, 3]
True
another = original 没有创建新列表,两个变量引用同一个可变列表。通过 another.append(3) 修改列表后,从 original 也能看到这个变化。
练习五
| 数据 | 合适的类型 | 原因 |
|---|---|---|
| 文章标题 | str | 标题是文本 |
| 商品库存数量 | int | 库存通常按整数计数 |
| 是否同意协议 | bool | 只有同意和不同意两种状态 |
| 按顺序排列的考试成绩 | list | 有顺序,并可能增删或修改 |
| 尚未填写的头像地址 | None | 当前没有可用值 |
| 不能重复的标签 | set | 集合会保证元素不重复 |
类型选择还要结合业务规则。例如,考试成绩一旦生成便不允许修改时,也可以使用 tuple;头像地址填写后通常是 str,所以类型注解可以写成 str | None。
小结
- 数据类型决定值能表达什么以及支持哪些操作,变量是引用对象的名字。
- 常见标量类型包括
int、float、bool、str和NoneType。 list、tuple、dict和set用于组织多个值,各自具有不同的顺序、修改和去重特性。- Python 是动态类型语言,但每个运行时对象都有明确类型,不合法的类型操作仍会报错。
type()用于查看类型,isinstance()通常更适合判断类型,int()、float()和str()等函数用于显式转换。- 列表、字典和集合是可变对象;多个变量引用同一个可变对象时,一处修改会被其他变量观察到。
==比较值,is比较对象身份;判断空值时使用is None。- 清晰的变量名、稳定的变量含义和适当的类型注解能显著提高代码可读性。