前言
在第一篇博客中,我们完成了 Python 环境的搭建,并写下了第一行代码 print("Hello, World!")。但那只是冰山一角。如果你只停留在 print 上,Python 能做的事 99% 都还没接触到。
本篇我们将系统学习 Python 的数据类型 和运算符 。这是 Python 编程的地基------任何有意义的程序,本质上都是在操作数据。数字要参与计算,文本要拼接裁剪,逻辑要判断真假,列表要增删改查。这些都离不开数据类型和运算符。
本文面向零基础读者,但讲解深度瞄准 "不仅会用,更要理解为什么这样用"。每个知识点都配有代码示例,并逐步加入 "踩坑提醒" 和 "原理剖析",帮助你建立对 Python 世界的完整认知。
1、Python 的数据类型全景图
1.1、一切皆为对象
在正式介绍数据类型之前,我们先建立一条重要认知:Python 中一切皆为对象。
这不是一句营销口号,而是一个技术事实。在 Python 中,整数 42 是一个对象,字符串 "hello" 是一个对象,函数是一个对象,甚至一个类本身也是一个对象。每个对象在内存中都有以下三个特征:
- 身份(Identity) :对象在内存中的地址,可以用
id()函数获取。你可以把它想象成对象的 "身份证号" - 类型(Type) :对象是什么种类,用
type()函数查看。就像 "人" 是一种类型,"狗" 是另一种类型 - 值(Value):对象保存的具体数据。比如整数对象 42 的值就是 42
让我们用代码来验证这一点:
python
# 一切皆为对象------我们来验证
a = 42
# type() 查看对象类型
print(type(a)) # 输出:<class 'int'>
# 解释:<class 'int'> 表示这是一个"整数类"(int = integer)的对象
# 在 Python 里,类型用 class(类)来表示
# id() 查看对象在内存中的地址(身份)
print(id(a)) # 输出:140734...(每次运行都不一样,这是计算器分配的内存地址)
# 解释:这个数字是 Python 解释器为 a 这个变量在内存中分配的房间号
# isinstance() 判断一个对象是否属于某种类型
print(isinstance(a, int)) # 输出:True(42 是整数类型)
print(isinstance(a, str)) # 输出:False(42 不是字符串类型)
print(isinstance(a, (int, float))) # 输出:True(是 int 或 float 之一)
这段代码的输出类似于:
<class 'int'>
140734819000000
True
False
True
为什么要知道这些? 理解 "一切皆为对象",你就能理解为什么 Python 的数据类型系统如此统一 ------ 整数、字符串、列表、函数甚至类本身,都可以赋值给变量、作为函数参数、放入列表中。没有任何 "基本类型" 和 "引用类型" 的割裂感,这使得 Python 代码写起来非常自然。
1.2、不可变类型 vs 可变类型------Python 最重要的区分
Python 的数据类型可以分为两大类:不可变类型 和可变类型。这个区分非常重要,它直接影响赋值、传参、比较等行为的逻辑。新手经常在这里踩坑,所以我们重点讲解。
1.2.1、什么是不可变类型?
不可变类型(Immutable) :对象创建后,其值不能被修改。如果 "修改" 了,实际上是创建了一个新对象,变量指向了新对象。
常见不可变类型:int(整数)、float(浮点数)、str(字符串)、bool(布尔值)、tuple(元组)。
1.2.2、什么是可变类型?
可变类型(Mutable):对象创建后,值可以被原地修改。多个变量可以指向同一个对象,其中一个修改了,其他变量 "看到" 的值也会变。
常见可变类型:list(列表)、dict(字典)、set(集合)。
1.2.3、用生活例子理解
想象你有一张纸条(变量),上面写着一个电话号码(值):
- 不可变类型就像:你把纸条上的号码改掉了,纸条还是那张纸条,但号码换了。原来的号码还在那里(可能被回收了),但你的纸条现在指向新号码了。
- 可变类型就像:你有一本通讯录,多个人都在用同一本通讯录。你在通讯录里加了一个联系人,大家下次翻开这本通讯录,都能看见你加的联系人------因为大家用的是同一本通讯录,不是各自的拷贝。
1.2.4、代码验证
python
# ========== 不可变类型示例:int ==========
print("===== 不可变类型:int =====")
a = 100 # 变量 a 指向整数对象 100
b = a # 变量 b 也指向 100(和 a 指向同一个对象)
print(f"修改前:a = {a}, b = {b}")
print(f"a 的内存地址:{id(a)},b 的内存地址:{id(b)}") # 相同!
a = 200 # 修改 a,让 a 指向新的整数对象 200
print(f"修改后:a = {a}, b = {b}")
# 注意:b 还是 100!因为 a 重新指向了新对象,b 没有受影响
print(f"a 的内存地址:{id(a)},b 的内存地址:{id(b)}") # 不同了!
# 输出:
# 修改前:a = 100, b = 100
# a 的内存地址:140734819000000,b 的内存地址:140734819000000
# 修改后:a = 200, b = 100
# a 的内存地址:140734819000200,b 的内存地址:140734819000000
print()
print("===== 可变类型:list =====")
# ========== 可变类型示例:list ==========
lst1 = [1, 2, 3] # 创建列表 [1, 2, 3],lst1 指向它
lst2 = lst1 # lst2 也指向同一个列表对象(没有创建新列表!)
print(f"修改前:lst1 = {lst1}, lst2 = {lst2}")
print(f"lst1 的内存地址:{id(lst1)},lst2 的内存地址:{id(lst2)}") # 相同!
lst1.append(4) # 通过 lst1 修改列表,在末尾添加 4
print(f"修改后:lst1 = {lst1}, lst2 = {lst2}")
# 注意:lst2 也变成了 [1, 2, 3, 4]!因为 lst2 和 lst1 指向同一个列表
print(f"lst1 的内存地址:{id(lst1)},lst2 的内存地址:{id(lst2)}") # 还是相同!
# 输出:
# 修改前:lst1 = [1, 2, 3], lst2 = [1, 2, 3]
# lst1 的内存地址:140734819000300,lst2 的内存地址:140734819000300
# 修改后:lst1 = [1, 2, 3, 4], lst2 = [1, 2, 3, 4] ← lst2 被影响了!
# lst1 的内存地址:140734819000300,lst2 的内存地址:140734819000300
这就是为什么这个特性特别重要: 在传参给函数时,如果传入的是可变类型,函数内部的修改会影响到外部的变量;而传入不可变类型,函数内部无论如何修改都不会影响外部。这个知识点在面试中几乎是必问的。
1.3、Python 数据类型总表
下面列出 Python 中最常用的数据类型:
| 类型 | 类别 | 说明 | 示例 |
|---|---|---|---|
int |
不可变 | 整数 | 42, -7, 0 |
float |
不可变 | 浮点数(小数) | 3.14, -0.5, 1e5 |
str |
不可变 | 字符串(文本) | "hello", '你好' |
bool |
不可变 | 布尔值(真假) | True, False |
list |
可变 | 列表(有序,可重复) | [1, 2, 3], ["a", "b"] |
dict |
可变 | 字典(键值对) | {"name": "Alice", "age": 30} |
tuple |
不可变 | 元组(有序,可重复) | (1, 2, 3), ("a", "b") |
set |
可变 | 集合(无序,不重复) | {1, 2, 3}, {"apple", "banana"} |
2、整数(int):不只是 1、2、3
2.1、整数类型的特点
Python 的 int 类型是任意精度整数(Arbitrary-Precision Integer),这是 Python 3 相对于其他语言(如 C、Java)的一个巨大优势。
具体来说:
- Python 的整数没有大小限制,只要内存够大,你可以表示任意大的数字
- 不像 C/Java 有
int(32位,最多表示约21亿)、long(64位)、short(16位)的区分 - Python 3 中
int就是长整型,而且自动扩容,你不需要手动选择类型
python
# ========== Python 的整数没有大小限制 ==========
print("===== 任意精度整数 =====")
# 计算 2 的 10000 次方
huge = 2 ** 10000 # ** 是幂运算符,表示 2 的 10000 次方
print(f"2 的 10000 次方有 {len(str(huge))} 位数字")
# len(str(huge)) 是把数字转成字符串,再求长度
# 输出:2 的 10000 次方有 3011 位数字
# 对比其他语言:
# Java int 上限:2^31 - 1 = 2147483647 ≈ 21 亿
# C long 在 64 位系统上:2^63 - 1 ≈ 922 亿亿
# 而 Python 可以轻松表示 2^1000000(100万次方),只是输出会很长
# 再来一个例子:100!(100的阶乘)
import math
factorial_100 = math.factorial(100)
print(f"100! 有 {len(str(factorial_100))} 位")
# 输出:100! 有 158 位
# Python 可以直接运算,不需要担心溢出
big_num = 9999999999999999999999999999999999999999 + 1
print(big_num)
# 输出:10000000000000000000000000000000000000000
2.2、整数的四种进制表示
Python 支持四种进制的表示方法,这在系统编程、嵌入式、通信协议、网络等领域非常有用。
什么是进制?
- 十进制(Decimal):我们平时使用的计数法,0-9,逢十进一
- 二进制(Binary):计算机内部使用的计数法,0-1,逢二进一
- 八进制(Octal):0-7,逢八进一,有时用于 Unix 文件权限
- 十六进制(Hexadecimal):0-9 + A-F,逢十六进一,常用于内存地址、颜色代码
python
# ========== 四种进制表示 ==========
print("===== 四种进制 =====")
a = 42 # 十进制(默认)------ 人类习惯的写法
b = 0b101010 # 二进制,以 0b 开头(b = binary)
c = 0o52 # 八进制,以 0o 开头(o = octal)
d = 0x2a # 十六进制,以 0x 开头(x = hex)
print(f"十进制 42 = {a}")
print(f"二进制 0b101010 = {b}")
print(f"八进制 0o52 = {c}")
print(f"十六进制 0x2a = {d}")
# 全部相等!只是表示形式不同:
# 输出:
# 十进制 42 = 42
# 二进制 0b101010 = 42
# 八进制 0o52 = 42
# 十六进制 0x2a = 42
进制之间的互相转换:
python
# ========== 进制转换函数 ==========
print("===== 进制转换 =====")
num = 42
# 十进制 → 其他进制(得到字符串)
print(bin(num)) # '0b101010' 二进制字符串
print(oct(num)) # '0o52' 八进制字符串
print(hex(num)) # '0x2a' 十六进制字符串
# 其他进制字符串 → 十进制整数
print(int('101010', 2)) # 42,按二进制解析字符串 '101010'
print(int('0b101010', 2)) # 42,也可以带上前缀
print(int('0x2a', 16)) # 42,按十六进制解析字符串 '0x2a'
# 实用的 IP 地址转换例子
ip = "192.168.1.1"
parts = ip.split('.')
print(f"IP {ip} 的四个部分:{parts}")
# 输出:IP 192.168.1.1 的四个部分:['192', '168', '1', '1']
进制的应用场景:
- 二进制:网络编程、位运算、硬件控制
- 八进制 :Unix/Linux 文件权限(
chmod 755) - 十六进制 :内存地址(
0x7fff5fbff8a0)、颜色代码(#FF5733)、调试信息
2.3、整数运算
python
# ========== 整数基本运算 ==========
print("===== 整数运算 =====")
a, b = 17, 5
print(f"{a} + {b} = {a + b}") # 22 加法
print(f"{a} - {b} = {a - b}") # 12 减法
print(f"{a} * {b} = {a * b}") # 85 乘法
print(f"{a} / {b} = {a / b}") # 3.4 除法(注意:结果永远是浮点数!)
print(f"{a} // {b} = {a // b}") # 3 整除(向下取整)
print(f"{a} % {b} = {a % b}") # 2 取余(模运算)
print(f"{a} ** {b} = {a ** b}") # 1419857 幂运算(17的5次方)
print(f"-{a} = {-a}") # -17 负号
整除的坑------向负无穷取整:
这是 Python 新手最容易踩的坑之一:
python
# ========== 整除的坑 ==========
print("===== 整除的坑 =====")
# 在 Python 中,整除 // 是向负无穷取整,不是向零取整!
print(f"-7 // 2 = {-7 // 2}") # -4(不是 -3!)
print(f"-7 % 2 = {-7 % 2}") # 1(不是 -1!)
print(f"7 // -2 = {7 // -2}") # -4(不是 -3!)
print(f"7 % -2 = {7 % -2}") # -1(不是 1!)
# 这背后的原理是什么?
# Python 的整除满足一个数学恒等式:a = (a//b) * b + a%b
# 也就是说:a除以b的商 * b + 余数 = a
# 验证 -7 的情况:
# -7 = (-4) * 2 + 1 → -8 + 1 = -7 ✅ 正确!
# 如果按向零取整:-7 = (-3) * 2 + (-1) → -6 - 1 = -7 ✅ 数学上也对
# 但 Python 选择了向负无穷取整,这是 IEEE 754 标准中关于除法取整的惯例
# 如果你需要向零取整(即 C/Java 的行为),可以这样做:
import math
print(f"向零取整:{math.trunc(-7 / 2)}") # -3(截断小数部分)
# 理解正数的整除没有坑:
print(f"7 // 2 = {7 // 2}") # 3(向下取整,也是向零取整,结果一样)
print(f"7 % 2 = {7 % 2}") # 1
练习: 自己动手运行上面的代码,感受整除在正数和负数上的不同行为。
3、浮点数(float):精度陷阱与应对
3.1、浮点数的表示
Python 的 float 类型对应 IEEE 754 双精度浮点数(64位),与 C 中的 double 相同。简单来说,float 就是 "小数",可以用来表示 3.14、-0.001、1.5e10(科学计数法)等。
python
# ========== 浮点数基础 ==========
print("===== 浮点数基础 =====")
a = 3.14 # 普通小数
b = 1.0e-5 # 科学计数法:1.0 × 10^-5 = 0.00001
c = 1e10 # 科学计数法:1.0 × 10^10 = 10000000000.0
d = -2.5 # 负浮点数
print(f"a = {a}, 类型 = {type(a)}")
print(f"b = {b}(1.0 × 10^-5)")
print(f"c = {c}(1.0 × 10^10)")
print(f"d = {d}")
print(f"全部都是 float 类型:{type(b)}, {type(c)}, {type(d)}")
# 查看浮点数的精度信息
import sys
print(f"\n浮点数精度信息:{sys.float_info}")
# 输出类似:
# sys.float_info(max=1.7976931348623157e+308, max_exp=1024, ...)
3.2、精度陷阱:0.1 + 0.2 != 0.3
这是 Python(实际上是所有 IEEE 754 浮点数语言)最著名的精度问题之一。这不是 Python 的 bug,而是 IEEE 754 浮点数标准的固有特性。
python
# ========== 著名的 0.1 + 0.2 问题 ==========
print("===== 浮点数精度陷阱 =====")
result = 0.1 + 0.2
print(f"0.1 + 0.2 = {result}")
print(f"result == 0.3: {result == 0.3}")
# 输出:
# 0.1 + 0.2 = 0.30000000000000004
# result == 0.3: False
# 为什么会这样?
# 因为 0.1 和 0.2 在二进制(计算机内部)下是无限循环小数:
# 0.1(十进制) = 0.0001100110011001100110011...(二进制,无限循环)
# 0.2(十进制) = 0.001100110011001100110011...(二进制,无限循环)
# 计算机只能存储有限位(64位),所以必须截断,产生舍入误差
# 这个微小的误差在加法后被放大,导致 0.1 + 0.2 不完全等于 0.3
# 让我们看看 0.1 的精确二进制表示:
print(f"0.1 的 repr:{repr(0.1)}") # 0.1(实际存储的是近似值)
如何正确比较浮点数? 有三种方法:
python
# ========== 正确比较浮点数 ==========
print("===== 正确比较浮点数 =====")
import math
from decimal import Decimal
a = 0.1 + 0.2
b = 0.3
# 方法1:math.isclose()(最推荐)
print(f"math.isclose(a, b): {math.isclose(a, b)}") # True
# 方法2:设置绝对容差
tolerance = 1e-9
print(f"abs(a - b) < 1e-9: {abs(a - b) < tolerance}") # True
# 方法3:使用 Decimal 精确运算(最精确,用于金融计算)
a_dec = Decimal('0.1') # 注意:要用字符串 '0.1',不要用 float 0.1
b_dec = Decimal('0.2')
result_dec = a_dec + b_dec
print(f"Decimal 精确计算:{a_dec} + {b_dec} = {result_dec}")
print(f"Decimal('0.3') == result_dec: {Decimal('0.3') == result_dec}") # True
⚠️ 踩坑提醒:绝对不能用 float 做金钱计算!
python
# ========== 金钱计算的大坑 ==========
print("===== 金钱计算不能用 float =====")
# 场景:计算 0.1 + 0.2 + 0.3 - 0.6
result = 0.1 + 0.2 + 0.3 - 0.6
print(f"0.1 + 0.2 + 0.3 - 0.6 = {result}") # 5.551115123125783e-17(接近0但不是0)
# 场景:电商计算价格
price1 = 0.1
price2 = 0.2
total = price1 + price2
print(f"0.1 + 0.2 = {total}")
if total == 0.3:
print("正好等于0.3")
else:
print(f"不等于0.3!差了 {total - 0.3}")
# 正确的做法:用 Decimal 或用整数(以"分"为单位)
print("\n正确的金钱计算方式:")
from decimal import Decimal
price1 = Decimal('0.10')
price2 = Decimal('0.20')
print(f"Decimal 精确计算:{price1} + {price2} = {price1 + price2}")
# 或者用整数(以"分"为单位)
price1_cent = 10 # 0.10 元 = 10 分
price2_cent = 20 # 0.20 元 = 20 分
total_cent = price1_cent + price2_cent
print(f"整数(分)计算:{price1_cent}分 + {price2_cent}分 = {total_cent}分 = {total_cent/100}元")
3.3、浮点数与整数的转换
python
# ========== 类型转换 ==========
print("===== 浮点数与整数转换 =====")
# 浮点数 → 整数(直接截断小数部分,不是四舍五入!)
print(f"int(3.99) = {int(3.99)}") # 3(不是 4!直接砍掉小数)
print(f"int(3.14) = {int(3.14)}") # 3
print(f"int(-3.99) = {int(-3.99)}") # -3(也是截断,不是向零取整)
# 如果需要四舍五入,用 round()
print(f"round(3.5) = {round(3.5)}") # 4(四舍五入)
print(f"round(3.4) = {round(3.4)}") # 3
print(f"round(3.14159, 2) = {round(3.14159, 2)}") # 3.14,保留两位小数
print(f"round(3.14159, 4) = {round(3.14159, 4)}") # 3.1416,保留四位小数
# 整数 → 浮点数(自动类型提升)
print(f"float(3) = {float(3)}") # 3.0
print(f"float(0) = {float(0)}") # 0.0
# 浮点数和整数比较
print(f"\n1 == 1.0: {1 == 1.0}") # True(值相等)
print(f"1 is 1.0: {1 is 1.0}") # False(身份不同,类型不同)