跳到主要内容

数据类型和变量

程序需要处理姓名、年龄、价格、登录状态等各种数据。数据类型说明一个值是什么、能参加哪些运算;变量则是程序中引用这个值的名字。

学完本章后,你应该能够:

  • 使用 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保存文本
age18int保存整数
height1.72float保存带小数的数值
is_studentTruebool表示“是”或“否”
courses["Python", "数学"]list按顺序保存多个课程名称

变量让数据有了容易理解的名字。与其在代码各处直接写 1.72,使用 height 能更清楚地表达这个值的含义。

先理解值、对象和变量​

在 Python 中,运行时的数据都以对象的形式存在。每个对象至少包含三方面信息:

  • 值:对象表示的具体内容,例如整数 18;
  • 类型:对象支持哪些操作,例如 int 可以参与整数运算;
  • 身份:用于区分两个对象的唯一标识,可以使用 id() 查看。

变量并不是一个固定形状的“盒子”,更准确的理解是:变量是指向对象的名字。

age = 18

这行代码完成两个动作:

  1. 创建或取得值为 18 的整数对象;
  2. 让变量名 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 类型:

  1. 一篇文章的标题;
  2. 商品库存数量;
  3. 用户是否同意协议;
  4. 一组按顺序排列的考试成绩;
  5. 用户尚未填写的头像地址;
  6. 一组不能重复的标签。

参考答案​

练习一​

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。
  • 清晰的变量名、稳定的变量含义和适当的类型注解能显著提高代码可读性。