Python 数据类型从基础到幕后(实例代码详解)
Python 是一门动态类型、强类型的编程语言。这意味着变量无需声明类型,但对象本身始终有明确的类型,不同类型之间的操作通常不会隐式转换。真正理解 Python 的数据类型,不仅要知道有哪些类型可用,更要明白它们的内部结构、内存行为以及由此衍生出的“可变性”、“哈希”等核心概念。本文将全面梳理 Python 的数据类型,穿插底层实现细节与代码示例,带你看到表象之下的真实。
1. 万物皆对象,类型在对象上
在 Python 中,一切数据都是对象,每个对象都有三个基本属性:
- 标识(identity):即内存地址,可通过
id()获取。 - 类型(type):决定对象能参与哪些操作,可通过
type()查看。 - 值(value):对象中存储的数据。
变量名只是贴在对象上的“标签”,赋值本质上是将标签贴到某个对象上。
a = 10 b = a print(id(a), id(b)) # 相同,指向同一个整数对象 print(type(a)) # <class 'int'>
2. 数字类型:不仅仅是“数字”
2.1 int — 任意精度的整数
Python 的 int 是任意精度的,只受内存限制,不像 C/Java 有固定位宽溢出。底层使用变长数组存储数字的绝对值(通常是 30 位或 15 位的“数字”片段),并搭配符号位。
big = 2**1000 print(big.bit_length()) # 1001 位二进制
小整数缓存:CPython 在启动时会预先创建 -5 到 256 之间所有整数对象。这些整数在整个解释器生命周期中复用。
a = 256
b = 256
print(a is b) # True (同一对象)
c = 257
d = 257
print(c is d) # 在脚本环境下可能是 True(因为同一代码块的常量合并),
# 但在交互环境或函数动态创建时通常是 False实用技巧:不要用 is 比较数值相等,始终使用 ==。is 仅用于判断是否为同一对象(如 is None)。
2.2 float — IEEE 754 双精度浮点数
Python float 对应 C 的 double,遵循 IEEE 754 标准。因此存在精度误差:
print(0.1 + 0.2) # 0.30000000000000004
进行精确十进制运算应使用 decimal.Decimal,有理数运算可用 fractions.Fraction。
2.3 complex — 内置复数
c = 3 + 4j print(c.real, c.imag) # 3.0 4.0 print(abs(c)) # 5.0
2.4 bool — 整数子类
bool 类型只有 True 和 False 两个实例,且是 int 的子类:
print(issubclass(bool, int)) # True print(True == 1) # True print(True is 1) # False print(True + 3) # 4
这种设计继承了历史习惯,但也可能导致一些“陷阱”,比如在字典中 True 和 1 会被视为相同键:
d = {True: 'yes', 1: 'no'}
print(d) # {True: 'no'}
3. 序列类型:有序世界的不同选择
Python 序列类型包括 list、tuple、str、bytes、bytearray、range 等。它们的核心共性是:支持索引、切片、长度、包含检测,并可迭代。
3.1 列表 list — 动态数组
list 底层是一个指向元素对象的指针数组,并采用预分配(over-allocation)策略来让 append 达到均摊 O(1)。
import sys
lst = []
prev = sys.getsizeof(lst)
for i in range(20):
lst.append(i)
size = sys.getsizeof(lst)
if size != prev:
print(f"len={len(lst)}, size={size}, extra slots={(size-prev)//8}")
prev = size
输出的分配模式大致是 0, 4, 8, 16, 25...,增长速度约为原大小的 1/8,避免每次追加都重新分配内存。
可变性影响:因为列表元素存的是对象引用,修改列表不会改变被引用的对象本身,但会改变列表内部的指针数组。这也是深浅拷贝问题的根源。
3.2 元组 tuple — 不可变序列
tuple 一旦创建,结构就固定了,无法添加、删除或修改元素。这带来两个好处:
- 哈希稳定,可作为字典的键(内部元素也必须是可哈希的)。
- 内存开销比列表更小,没有额外预分配空间。
但需注意:元组的“不可变”指的是它储存的引用不可变,若引用指向可变对象,则该对象的状态依然可以改变。
t = ([1, 2], 3) t[0].append(99) print(t) # ([1, 2, 99], 3)
字符串驻留(interning):与整数缓存类似,某些字符串也会被解释器驻留,以复用对象。标识符类似的短字符串会自动驻留;可通过 sys.intern() 手动驻留。
a = "python" b = "python" print(a is b) # True (自动驻留) c = "python!" d = "python!" print(c is d) # 通常 False (包含特殊字符的短字符串不一定驻留)
3.3 字符串 str — 不可变 Unicode 序列
Python 3 的 str 是 Unicode 字符序列,内部表示会根据字符串内容动态选择紧凑编码(1/2/4 字节每字符),称为“灵活字符串表示”。字符串不可变,因此所有操作都会返回新对象,可使用 join 而不是循环 += 来提高性能。
# 拼接大量字符串的反例:
result = ""
for s in many_strings:
result += s # 每次创建新字符串,O(n²)
# 正例:
result = "".join(many_strings) # O(n)3.4 range — 惰性数值序列
range 不是列表,它只存储 start、stop、step 三个值,按需生成整数,内存占用极小。
r = range(10**10) # 不会创建 100 亿个整数 print(r[100]) # 即时计算
3.5 二进制序列类型:bytes 与 bytearray
bytes:不可变字节串(0-255 整数序列),常用于处理二进制数据。bytearray:可变字节串,可原地修改。
b = b'hello' print(b[0]) # 104 (ASCII 码) ba = bytearray(b) ba[0] = 106 print(ba) # bytearray(b'jello')
memoryview 则允许在不复制的情况下访问不同类型二进制数据的内存,进行高效操作。
4. 字典 dict — 哈希表的魔力
dict 是 Python 的核心,底层使用散列表(hash table)。从 Python 3.6 开始,字典保留插入顺序,这得益于使用了紧凑存储结构:一个稠密数组存储键值对条目,一个稀疏的哈希表数组只存储索引。这样既节约内存,又保证了顺序。
4.1 键的要求:可哈希
字典的键必须是可哈希(hashable)的对象,即:
- 定义了
__hash__方法,且哈希值在其生命周期内不变; - 定义了
__eq__方法,用于比较相等。
所有不可变内置类型(int、float、str、tuple、frozenset 等)都是可哈希的,前提是它们内部所有元素也可哈希。列表、集合、字典本身不可哈希,因此不能作为字典的键。
# 合法的键
d = { (1, 2): "point" }
# 非法
# d = { [1, 2]: "line" } # TypeError: unhashable type: 'list'自定义对象默认可哈希(基于 id()),但如果定义了 __eq__ 而未定义 __hash__,则会被设为不可哈希。
4.2 常见操作与性能
字典的取值、赋值、删除平均时间复杂度都是 O(1)。但若哈希碰撞过多或装载因子过高,会触发重建(rehash),使某些操作变为 O(n)。
默认值技巧:setdefault、collections.defaultdict 和 dict.get 可简化逻辑。
from collections import defaultdict
counter = defaultdict(int)
for ch in 'abracadabra':
counter[ch] += 1
print(counter) # defaultdict(<class 'int'>, {'a': 5, 'b': 2, ...})5. 集合类型:不重复元素的容器
set 是可变的无序集合(元素唯一),frozenset 是其不可变版本。它们同样基于哈希表,元素必须可哈希。支持交集、并集、差集等高效集合运算。
a = {1, 2, 3}
b = frozenset([2, 3, 4])
print(a & b) # {2, 3}
a.add(4)
# b.add(4) # AttributeError: 'frozenset' object has no attribute 'add'
集合的典型应用是去重和成员资格快速判断(O(1))。
6. None — 唯一的空值类型
None 是 NoneType 的单例对象,表示“没有值”。它是用 is 检查的绝佳例子:
if x is None:
# 正确的写法
7. 可变与不可变:深入理解引用与拷贝
7.1 可变对象作为默认参数的陷阱
函数的默认参数在定义时只评估一次,若默认值是可变对象,多次调用会共享同一对象。
def add_item(item, target=[]):
target.append(item)
return target
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] 并不是空列表!正确方式:
def add_item(item, target=None):
if target is None:
target = []
target.append(item)
return target
7.2 浅拷贝与深拷贝
- 浅拷贝(
list.copy(),copy.copy等):构造一个新容器,但元素是原容器中对象的引用。 - 深拷贝(
copy.deepcopy):递归复制所有嵌套对象,创建全新的独立对象图。
import copy orig = [ [1, 2], 3 ] shallow = copy.copy(orig) deep = copy.deepcopy(orig) orig[0].append(99) print(shallow) # [[1, 2, 99], 3] —— 受影响 print(deep) # [[1, 2], 3] —— 不受影响
理解深浅拷贝对避免数据被意外修改至关重要。
8. 类型提示:让代码更自文档化
Python 3.5+ 引入了类型提示(Type Hints),虽然不强制类型检查,但极大地提高了代码可读性,并可结合 mypy、pyright 等工具做静态分析。
from typing import List, Dict, Optional
def process(data: List[int], config: Optional[Dict[str, str]] = None) -> str:
if config is None:
config = {}
return ", ".join(str(x) for x in data)
print(process([1, 2, 3])) # "1, 2, 3"更高级的用法包括泛型、联合类型、TypedDict 等。类型提示本身也催生了许多强大模式。
9. 数据类与命名元组:让数据容器更优雅
当我们需要一个仅用于保存数据的类时,传统写法需要定义 __init__、__repr__ 等样板代码。Python 提供了更简洁的解决方案。
9.1 命名元组
collections.namedtuple 创建轻量、不可变的数据容器,通过属性访问字段,且支持迭代。
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
print(p.x, p.y) # 10 20
print(p[0]) # 10
# p.x = 30 # AttributeError: can't set attribute9.2 数据类
Python 3.7+ 引入的 @dataclass 提供可变或不可变的数据容器,自动生成 __init__、__repr__、__eq__ 等方法,并支持默认值、类型注解等。
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int = 0
tags: list = None # 注意默认值问题,推荐使用 field(default_factory=list)
# 自动生成的 __init__ 和 __repr__
p1 = Person("Alice", 30)
p2 = Person("Alice", 30)
print(p1 == p2) # True (基于值的比较)若要不可变,只需设置 frozen=True:
@dataclass(frozen=True)
class Point:
x: int
y: int
数据类极大减少了样板代码,成为数据建模的首选。
10. 总结:类型是理解 Python 的关键
Python 的数据类型系统看似简单,实则蕴含着深思熟虑的设计。抓住以下主线,就能驾驭它:
- 对象模型:变量是标签,对象有类型、标识和值。
- 可变与不可变:决定了对象的哈希能力和作为参数时的行为。
- 内部结构:列表是动态数组,字典是紧凑哈希表,整数任意精度,float 遵循 IEEE 754。
- 高效惯用法:字符串用
join,字典用get/defaultdict,集合用于去重,数据类替代样板类。 - 类型提示与数据类:提升可维护性,构建自文档化代码。
Python 的类型既是基础,也是进阶的入口。当你从“会使用”走向“懂原理”,就能写出更高效、更可靠的代码,并从容解决那些令人头疼的“坑”。这篇文章只是一个深潜的起点,希望它能激发你继续探索底层实现、C API 甚至 Python 解释器源码的兴趣。
到此这篇关于Python 数据类型从基础到幕后的文章就介绍到这了,更多相关Python 数据类型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Python使用pickle模块报错EOFError Ran out of input的解决方法
这篇文章主要介绍了Python使用pickle模块报错EOFError Ran out of input的解决方法,涉及Python异常捕获操作处理相关使用技巧,需要的朋友可以参考下2018-08-08


最新评论