Python基础语法:数据类型、容器、分支与循环

完成 Hello World 与 I/O 入门之后,真正进入 Python 编程的世界,首先要掌握的是语言的"骨架":数据类型、变量、字符串与编码、容器类型(list / tuple / dict / set)、条件判断与循环。这些内容是所有业务逻辑的基石,本文在系统梳理核心知识的同时,也补充了一些原文未展开的细节(如 boolint 的关系、is== 的区别、copy 浅深拷贝、walrus 海象运算符、dict 的有序性等),让你一次学透 Python 的底层规则。

一、数据类型与变量

1.1 内建基本数据类型

Python 原生支持以下几种"立即可用"的数据类型:

类型 示例 说明
整数 int 1, -8080, 0xff00, 10_000_000_000 无大小限制(受内存约束)
浮点数 float 1.23, -9.01, 1.23e9, 1.2e-5 运算可能有舍入误差
字符串 str 'abc', "xyz", '''多行''' 以 Unicode 存储
布尔值 bool True, False 注意首字母大写
空值 NoneType None 表示"无值",不是 0

补充说明

  • 十六进制 / 二进制 / 八进制0xff0b10100o17 分别对应十六、二、八进制字面量。
  • 数字分隔符:从 Python 3.6 起允许用 _ 分隔数字位,如 1_000_000,只是可读性技巧,对值无影响。
  • 浮点精度陷阱0.1 + 0.2 == 0.3 返回 False,精确计算应使用 decimal.Decimalfractions.Fraction
  • bool 其实是 int 的子类True == 1False == 0True + 1 == 2 成立,isinstance(True, int) 返回 True
1
2
3
4
5
6
7
8
9
10
11
# 整数的无限精度
big = 2 ** 1000
print(big) # 一个 302 位数的大整数,Python 照样能算

# 浮点误差
print(0.1 + 0.2) # 0.30000000000000004
print(round(0.1 + 0.2, 2)) # 0.3

# bool 本质是 int
print(True + 1) # 2
print(sum([True, True, False])) # 2,常用于"统计满足条件的个数"
1.2 字符串与转义
  • 单、双引号等价,'I\'m OK' 等价 "I'm OK"
  • 常用转义:\n(换行)、\t(制表)、\\(反斜杠)、\'\"
  • 原始字符串 r'...':内部所有 \ 都被当成普通字符,常用于正则表达式和 Windows 路径;
  • 多行字符串 '''...'''"""...""",可以包含换行;
  • 二者可以叠加:r'''...''' 多行且不转义。
1
2
3
4
5
6
7
8
print('I\'m learning\nPython.')  # 换行
print(r'C:\Users\Python') # 原样输出 C:\Users\Python

sql = """
SELECT id, name
FROM user
WHERE age > 18;
"""
1.3 布尔运算

三种运算符:and(与)、or(或)、not(非)。

1
2
3
5 > 3 and 3 > 1   # True
5 > 3 or 1 > 3 # True
not 1 > 2 # True

**短路求值(Short-Circuit)**是 Python 的一个重要特性:

1
2
3
4
5
6
7
8
9
10
# and 短路:一旦遇到假值立即返回该假值
print(0 and 10 / 0) # 0,右侧不会执行,不会触发 ZeroDivisionError

# or 短路:一旦遇到真值立即返回该真值
name = '' or '默认名'
print(name) # 默认名

# 注意:and/or 返回的是"值"而不是 True/False
print(1 and 2) # 2
print(0 or 'x') # 'x'

Falsy 值(假值)NoneFalse00.0''[](){}set()。其他都为真值。

1.4 变量:动态类型与"指向"模型
1
2
a = 123     # a 是整数
a = 'ABC' # a 变成字符串,合法

Python 是动态语言,变量本身不固定类型,变量名只是"贴在对象上的标签"。

1
2
3
4
a = 'ABC'
b = a
a = 'XYZ'
print(b) # 'ABC' —— b 仍指向原来的 'ABC'

图示:

1
2
3
4
5
6
7
第 1 步:a = 'ABC'           第 2 步:b = a                第 3 步:a = 'XYZ'
┌───┐ ┌───────┐ ┌───┐ ┌───────┐ ┌───┐ ┌───────┐
│ a │──▶│ 'ABC' │ │ a │──▶│ 'ABC' │ │ a │──▶│ 'XYZ' │
└───┘ └───────┘ └───┘ └───────┘ └───┘ └───────┘
┌───┐ ▲ ┌───┐ ┌───────┐
│ b │─────┘ │ b │────▶│ 'ABC' │
└───┘ └───┘ └───────┘
1.5 常量

Python 没有真正的常量机制,约定以全大写字母命名:

1
2
PI = 3.14159265359
MAX_RETRY = 5

注:Python 3.8+ 可以用 typing.Final 做静态检查层面的"不可变约束",但运行期仍然允许改。

1.6 两种除法
1
2
3
4
5
6
10 / 3    # 3.333...  普通除法,结果一定是 float
9 / 3 # 3.0 整除尽也是 float
10 // 3 # 3 地板除,向下取整
10 % 3 # 1 取余
-7 // 2 # -4 注意:负数地板除是"向下取整",不是"向零截断"
divmod(10, 3) # (3, 1) 一次返回商和余数
1.7 ==is 的区别(补充)
  • ==:比较值是否相等(调用 __eq__);
  • is:比较是否是同一个对象(地址相同)。
1
2
3
4
5
6
7
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True,内容相同
print(a is b) # False,不是同一个对象

c = None
print(c is None) # ✅ 推荐写法,不要写 c == None

二、字符串与编码

2.1 编码发展史
  • ASCII:1 字节,只覆盖 127 个英文字符;
  • GB2312 / Shift_JIS / Euc-kr:各国扩展,易冲突;
  • Unicode:收录全世界字符,常用 2 字节;
  • UTF-8:Unicode 的可变长编码(1~4 字节),英文 1 字节、中文 3 字节,与 ASCII 完全兼容。
字符 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
01001110 00101101 11100100 10111000 10101101

通用工作方式:内存里用 Unicode;落盘 / 传输时用 UTF-8。

2.2 strbytes

Python 3 的 str 默认是 Unicode。

1
2
3
4
5
6
ord('A')       # 65
ord('中') # 20013
chr(66) # 'B'
chr(25991) # '文'

'\u4e2d\u6587' # '中文',Unicode 转义写法

str ↔ bytesencode()decode() 互转:

1
2
3
4
5
6
7
'ABC'.encode('ascii')        # b'ABC'
'中文'.encode('utf-8') # b'\xe4\xb8\xad\xe6\x96\x87'

b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8') # '中文'

# 容错:忽略无效字节
b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore') # '中'

len() 的含义根据对象不同:

1
2
len('中文')               # 2,字符数
len('中文'.encode('utf-8')) # 6,字节数

强烈建议:统一使用 UTF-8 对 str / bytes 进行互转。

2.3 源码编码声明

.py 文件包含中文时,推荐在头部写:

1
2
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

第一行让 Unix/macOS 将此文件视为 Python 可执行脚本;第二行告诉解释器源码使用 UTF-8 编码(Python 3 默认即 UTF-8,可省略,但保留更稳妥)。

2.4 字符串格式化三种姿势

1)% 格式化(C 风格)

1
2
3
4
5
'Hello, %s' % 'world'
'Hi, %s, you have $%d.' % ('Michael', 1000000)
'%2d-%02d' % (3, 1) # ' 3-01'
'%.2f' % 3.1415926 # '3.14'
'growth rate: %d %%' % 7 # '%%' 转义为 '%'

常见占位符:

占位符 含义
%d 整数
%f 浮点数
%s 字符串(万能)
%x 十六进制

2)str.format()

1
'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)

3)f-string(推荐,Python 3.6+)

1
2
3
4
5
6
7
r = 2.5
s = 3.14 * r ** 2
print(f'圆面积:{s:.2f}') # 圆面积:19.62

# 调试利器(3.8+):= 会原样输出表达式
x, y = 3, 4
print(f'{x=}, {y=}') # x=3, y=4

个人推荐顺序f-string > format > %。f-string 速度最快、可读性最好。

三、list 与 tuple

3.1 list:可变有序序列
1
2
3
4
5
6
7
8
9
10
classmates = ['Michael', 'Bob', 'Tracy']
len(classmates) # 3
classmates[0] # 'Michael'
classmates[-1] # 'Tracy' 倒数第一

classmates.append('Adam') # 末尾添加
classmates.insert(1, 'Jack') # 指定位置插入
classmates.pop() # 删末尾
classmates.pop(1) # 删指定位置
classmates[1] = 'Sarah' # 直接赋值修改

list 元素类型可以混合,也可以嵌套:

1
2
3
L = ['Apple', 123, True]
s = ['python', 'java', ['asp', 'php'], 'scheme']
s[2][1] # 'php' 二维访问

常用操作补充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
L = [3, 1, 4, 1, 5, 9, 2, 6]

L.sort() # 原地排序 [1, 1, 2, 3, 4, 5, 6, 9]
sorted(L) # 返回新列表,原 L 不变
L.reverse() # 原地反转
L[::-1] # 返回反转后的新列表
L.count(1) # 统计 1 出现的次数
L.index(5) # 5 第一次出现的下标

# 切片
L[2:5] # 取下标 2~4
L[:3] # 前三个
L[-3:] # 后三个
L[::2] # 每隔一个取一个
3.2 tuple:不可变有序序列
1
2
3
t = ('Michael', 'Bob', 'Tracy')
t[0] # 'Michael'
# t[0] = 'X' # ❌ TypeError

三个陷阱

1
2
3
t = ()        # ✅ 空 tuple
t = (1) # ❌ 这是整数 1,不是 tuple
t = (1,) # ✅ 单元素 tuple 必须加逗号

“不可变的 tuple” 里可以有可变元素

1
2
3
4
t = ('a', 'b', ['A', 'B'])
t[2][0] = 'X'
t[2][1] = 'Y'
print(t) # ('a', 'b', ['X', 'Y'])

tuple 的"不变"指的是:每个位置的引用(指向)不变,但它指向的对象本身(比如 list)可以被修改。真正"内容也不变"的 tuple,要求内部每个元素都是不可变对象。

3.3 补充:浅拷贝 vs 深拷贝

由于 list / dict 等是可变对象,赋值只是复制引用:

1
2
3
4
5
6
7
8
a = [1, 2, [3, 4]]
b = a # 同一个对象
b[0] = 99
print(a) # [99, 2, [3, 4]] —— a 也变了

import copy
b = copy.copy(a) # 浅拷贝:外层新对象,内层共享
b = copy.deepcopy(a) # 深拷贝:递归复制所有层

四、条件判断:if / elif / else

1
2
3
4
5
6
7
age = 3
if age >= 18:
print('adult')
elif age >= 6:
print('teenager')
else:
print('kid')

关键规则

  1. 缩进决定代码块(通常 4 个空格);
  2. 冒号 : 不能漏;
  3. 从上向下匹配,命中任一分支后跳出整个 if
  4. if x: 等价于 if bool(x):,Falsy 值见前文。
4.1 input 与类型转换
1
2
3
4
5
6
7
birth = input('birth: ')
# birth 是 str,不能直接和整数比较
birth = int(birth)
if birth < 2000:
print('00前')
else:
print('00后')

当输入 'abc' 这类非法字符时 int() 会抛 ValueError,正式工程中需要 try-except 包裹(后续章节会讲)。

4.2 三目表达式与海象运算符(补充)
1
2
3
4
5
6
# 三目表达式
label = 'adult' if age >= 18 else 'teenager'

# 海象运算符 :=(Python 3.8+):在表达式中赋值
while (line := input('> ')) != 'quit':
print(f'你输入了:{line}')

五、match 语句(Python 3.10+)

针对"单个变量匹配多种情况"的场景,match 比一长串 if-elif 更清晰。

5.1 基础用法
1
2
3
4
5
6
7
8
9
10
score = 'B'
match score:
case 'A':
print('score is A.')
case 'B':
print('score is B.')
case 'C':
print('score is C.')
case _: # _ 表示"其他任何值"
print('invalid score.')
5.2 复杂匹配:值范围 / 多值 / 绑定变量
1
2
3
4
5
6
7
8
9
10
11
12
age = 15
match age:
case x if x < 10: # 带 guard 的模式匹配,同时把值绑定到 x
print(f'< 10 years old: {x}')
case 10:
print('10 years old.')
case 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18: # 用 | 列出多个值
print('11~18 years old.')
case 19:
print('19 years old.')
case _:
print('not sure.')
5.3 列表 / 序列解构
1
2
3
4
5
6
7
8
9
10
args = ['gcc', 'hello.c', 'world.c']
match args:
case ['gcc']:
print('gcc: missing source file(s).')
case ['gcc', file1, *files]:
print('gcc compile: ' + file1 + ', ' + ', '.join(files))
case ['clean']:
print('clean')
case _:
print('invalid command.')
5.4 补充:字典 / 类解构

match 的能力远不止匹配值,它是一种真正的结构化模式匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 字典模式:只关心字段存在性与具体值
def handle(event):
match event:
case {'type': 'click', 'x': x, 'y': y}:
print(f'click at ({x}, {y})')
case {'type': 'key', 'key': k}:
print(f'press {k}')
case _:
print('unknown')

# 类模式
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int

def where(p):
match p:
case Point(x=0, y=0):
return '原点'
case Point(x=0, y=_):
return 'Y 轴'
case Point(x=_, y=0):
return 'X 轴'
case Point():
return '平面其他位置'

六、循环:forwhile

6.1 for 循环
1
2
3
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)

配合 range() 生成整数序列:

1
2
3
4
5
6
7
8
list(range(5))        # [0, 1, 2, 3, 4]
list(range(1, 6)) # [1, 2, 3, 4, 5]
list(range(0, 10, 2)) # [0, 2, 4, 6, 8] 步长 2

s = 0
for x in range(101): # 0~100
s += x
print(s) # 5050
6.2 while 循环
1
2
3
4
5
s, n = 0, 99
while n > 0:
s += n
n -= 2 # 累加 99 + 97 + 95 + ... + 1
print(s) # 2500
6.3 break / continue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# break:提前退出整个循环
n = 1
while n <= 100:
if n > 10:
break
print(n)
n += 1

# continue:跳过本轮,进入下一轮
n = 0
while n < 10:
n += 1
if n % 2 == 0:
continue
print(n) # 只打印奇数:1 3 5 7 9

⚠️ 避免滥用 break / continue,它们会让逻辑分支变多,更易出 bug。多数场景可以用清晰的循环条件替代。

6.4 补充:for-elseenumeratezip

Python 的循环有一个冷门但很好用的 else 分支——只有循环"正常跑完"(没被 break 打断)才会执行:

1
2
3
4
5
for x in range(10):
if x == 100:
break
else:
print('没 break,来 else 了') # 会执行

enumerate 同时拿下标和元素:

1
2
3
4
for i, name in enumerate(['Alice', 'Bob'], start=1):
print(i, name)
# 1 Alice
# 2 Bob

zip 并行迭代多个序列:

1
2
3
4
names = ['Alice', 'Bob']
ages = [20, 25]
for n, a in zip(names, ages):
print(n, a)

七、dict 与 set

7.1 dict:键值映射
1
2
3
4
5
6
7
8
9
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
d['Michael'] # 95
d['Adam'] = 67 # 新增 / 覆盖

'Thomas' in d # False,判断 key 是否存在
d.get('Thomas') # None
d.get('Thomas', -1) # -1,自定义默认值

d.pop('Bob') # 删除 key 并返回其 value

key 必须是不可变(可哈希)对象intfloatstrbooltuple(且内部全为不可变)等都可以,listdictset 不行。

1
d[[1, 2, 3]] = 'x'  # ❌ TypeError: unhashable type: 'list'
7.2 dict vs list 的权衡
维度 dict list
查找 / 插入速度 O(1),非常快 O(n),随元素增加变慢
内存占用
有序性 Python 3.7+ 保留插入顺序(语言规范保证) 有序

补充:Python 3.6 CPython 实现已经让 dict 保留插入顺序,3.7 起成为语言规范。所以现在的 dict 既快又有序。

7.3 dict 常用操作补充
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
d = {'a': 1, 'b': 2, 'c': 3}

list(d.keys()) # ['a', 'b', 'c']
list(d.values()) # [1, 2, 3]
list(d.items()) # [('a', 1), ('b', 2), ('c', 3)]

# 遍历
for k, v in d.items():
print(k, v)

# 合并(3.9+)
d1 = {'a': 1}
d2 = {'b': 2}
merged = d1 | d2 # {'a': 1, 'b': 2}

# setdefault:key 不存在时写入并返回,存在则返回原值
d.setdefault('d', 4)

# 字典推导式
sq = {x: x*x for x in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
7.4 set:无序不重集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
s = {1, 2, 3}
s = set([1, 2, 3]) # 从 list 构造
s = set() # 空集合(注意不能写 {},那是 dict)

s.add(4)
s.remove(4) # 元素不存在会 KeyError
s.discard(99) # 元素不存在不会报错

# 集合运算
s1, s2 = {1, 2, 3}, {2, 3, 4}
s1 & s2 # 交集 {2, 3}
s1 | s2 # 并集 {1, 2, 3, 4}
s1 - s2 # 差集 {1}
s1 ^ s2 # 对称差 {1, 4}

常见用途:去重成员检查(同样是 O(1))。

1
unique = list(set([1, 1, 2, 2, 3, 3]))  # 快速去重
7.5 可变对象与不可变对象
1
2
3
4
5
6
a = ['c', 'b', 'a']
a.sort() # 原地修改 a == ['a', 'b', 'c']

s = 'abc'
s.replace('a', 'A') # 返回新字符串 'Abc'
print(s) # 'abc' —— 原对象不变

对于不可变对象(strinttuplefrozenset),所有方法都"返回新对象",原对象永不改变——这正是它们可以作为 dict key / set 元素的原因。

八、综合示例:通讯录管理器

下面把本文所有知识点串起来,做一个小小的通讯录程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

contacts: dict[str, dict] = {}

def add(name, phone, age):
if name in contacts:
print(f'⚠️ {name} 已存在,将被覆盖')
contacts[name] = {'phone': phone, 'age': int(age)}

def remove(name):
if contacts.pop(name, None) is None:
print(f'❌ 没有找到 {name}')
else:
print(f'✅ 已删除 {name}')

def show_all():
if not contacts:
print('(通讯录为空)')
return
for i, (name, info) in enumerate(contacts.items(), start=1):
print(f'{i}. {name} | {info["phone"]} | {info["age"]}岁')

def main():
add('Alice', '13800000001', 20)
add('Bob', '13800000002', 25)
add('Carol', '13800000003', 30)

while True:
cmd = input('\n请输入命令 (add/del/list/quit): ').strip().split()
if not cmd:
continue
match cmd:
case ['quit']:
print('bye~')
break
case ['list']:
show_all()
case ['add', name, phone, age]:
add(name, phone, age)
case ['del', name]:
remove(name)
case _:
print('❓ 无效命令,用法:add <name> <phone> <age> | del <name> | list | quit')

if __name__ == '__main__':
main()

这个例子涉及了:dict 嵌套、input + splitmatch 列表解构、for-enumeratef-string、函数定义与默认返回值等多项内容,是一个很好的综合练习。

九、学习建议与下一步

  1. 先打好数据类型基础:所有高级特性都建立在 int / float / str / list / tuple / dict / set / bool / None 之上。
  2. 理解"引用模型":变量只是"贴在对象上的标签",这是理解赋值、函数参数传递、拷贝等行为的关键。
  3. 牢记可变 vs 不可变:决定了能否做 dict key、默认参数陷阱、是否需要深浅拷贝。
  4. 熟练使用 f-string:现代 Python 的"首选"字符串格式化方式。
  5. 多写代码:把通讯录、成绩录入、单词计数等小程序反复写几遍,比读十篇文章都管用。

掌握了本文内容,你已经可以用 Python 写出带基础逻辑的实用脚本了。下一步的重点将是函数、作用域与模块,让代码从"脚本"变成"可复用的结构"。

参考资料