前言
Python 是动态类型语言,变量本身没有类型,类型属于它指向的对象。数据类型转换(type conversion)分两类:一类是显式转换,用int()、str()、list()这类构造器主动把对象转成另一种类型;另一类是隐式转换,比如1 + 1.0里整数会被自动提升为浮点数,但这类隐式提升只在数值类型之间发生,字符串和数字之间不会自动转换。
初学者最容易踩的误解有两个。第一,以为int("12.5")能得到12——实际上它会直接抛ValueError,int()只认整数字符串。第二,以为list("abc")是把整个字符串当成一个元素放进列表——实际上它会把字符串拆成单个字符,得到['a', 'b', 'c'],同理list()用在任何可迭代对象上都是「消费其元素」,而不是「包一层」。
还有一个反直觉的点:str()并不等于「把值转成人类可读的文本」这件事没有代价,对列表、字典这类容器,str()得到的是repr风格的表示,里面带引号和括号,它不是JSON,eval回去也不是推荐的解析方式。本文把常用类型之间的互转、转换规则和边界条件讲清楚。
一、三大构造器:int / float / str
这三个构造器的签名和边界要记准:
| 写法 | 结果 | 说明 |
|---|
int("123") | 123 | 默认按十进制解析 |
int(" -12_345 ") | -12345 | 允许前后空白、正负号、数字间单个下划线 |
int("FACE", 16) | 64206 | 第二个参数是进制,取值 0 或 2~36 |
int(123.45) | 123 | 浮点转整数向零截断,不是四舍五入 |
float("3.14") | 3.14 | 接受十进制、科学计数法、inf、nan等 |
str(42) | "42" | 调用对象的__str__ |
int()的进制参数是位置参数,int("01110011", base=2)也可以写成关键字形式,结果都是115。当base=0时,字符串按代码里的字面量规则解析,前缀决定进制:int("0x1f", 0)是31,int("0b101", 0)是5。
注意int()的截断方向和round()完全不同:
# 适用于 Python 3.8+
print(int(2.9)) # 2 —— 向零截断
print(int(-2.9)) # -2 —— 也是向零,不是向下取整
print(round(2.5)) # 2 —— 银行家舍入,.5 取偶二、字符串与容器互转
字符串到列表有两条完全不同的路,选错了结果差很远:
# 适用于 Python 3.8+
s = "hello world"
print(list(s)) # ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
print(s.split()) # ['hello', 'world'] —— 按空白切词
print(s.split(" ")) # ['hello', 'world'] —— 按单个空格切
print(s.split("o")) # ['hell', ' w', 'rld']list(s)逐字符拆分,s.split(sep)按分隔符切分,这是最容易混的一对。split()不传参数或传None时启用特殊的空白算法:把连续空白当成一个分隔符,并且结果里不会有空字符串;一旦传入具体的分隔符,就会保留空串:
# 适用于 Python 3.8+
print(" a b ".split()) # ['a', 'b']
print(",a,,b,".split(",")) # ['', 'a', '', 'b', '']列表合回字符串只有一个正确姿势:
# 适用于 Python 3.8+
parts = ["hello", "world"]
print(" ".join(parts)) # 'hello world'
print("-".join("abc")) # 'a-b-c' —— 因为字符串本身也是可迭代对象"".join()的调用者是「分隔符」,参数才是「要被连接的序列」,别把顺序记反。而且它要求序列里每一个元素都是字符串,有一个整数就会抛TypeError。
字符串与数字之间不能隐式互转,必须显式写转换:
# 适用于 Python 3.8+
n = 42
print("答案是 " + str(n)) # 拼接,先转字符串
print(f"答案是 {n}") # f-string 会自动调用格式化,不需要手写 str()
# print("答案是 " + n) # TypeError: can only concatenate str三、容器之间的互转
list()、tuple()、set()、dict()都是构造器,把可迭代对象转成对应容器:
# 适用于 Python 3.8+
t = (1, 2, 2, 3)
print(list(t)) # [1, 2, 2, 3]
print(set(t)) # {1, 2, 3} 去重,且不保证顺序
print(tuple([1, 2])) # (1, 2)
pairs = [("a", 1), ("b", 2)]
print(dict(pairs)) # {'a': 1, 'b': 2}
print(dict(a=1, b=2)) # {'a': 1, 'b': 2}几个必须记住的性质:
set()去重后会丢掉重复元素和原有顺序,需要保序去重时不能用它。dict()接受「键值对的可迭代对象」或关键字参数,但关键字参数的键必须是合法标识符,dict(**{"a-b": 1})这种带连字符的键会报错。- 容器的转换是浅拷贝:
list(inner_list)只复制外层,里面的可变对象仍然共享。
四、转换的失败模式与安全写法
转换不是总能成功,字符串转数字尤其如此。可用的手段有三层:
# 适用于 Python 3.8+
def to_int(s, default=None):
try:
return int(s)
except (ValueError, TypeError):
return default
print(to_int("42")) # 42
print(to_int("abc")) # None
print(to_int(None)) # None —— TypeError 也被接住了int("12.5")会抛ValueError,想处理小数文本要先float()再取整:int(float("12.5"))得到12。反过来,float("12")是合法的,得到12.0。
判断一个字符串是不是数字,用str自带的方法比正则更直接:
| 方法 | 判断范围 | 例子 |
|---|
s.isdigit() | 只认数字字符 | "²".isdigit()为真,但int("²")会失败 |
s.isdecimal() | 只认能构成十进制的字符 | 比isdigit更严格 |
s.isnumeric() | 范围最宽,含分数、罗马数字等 | "½".isnumeric()为真 |
所以「判断能否转成 int」不能只靠isdigit(),最稳妥的还是try/except。
常见坑点
- 以为 int() 能吃小数字符串
❌int("12.5")→ValueError: invalid literal for int() with base 10: '12.5'
✅int(float("12.5"))→12,或先用Decimal("12.5")做精确解析
- 把 list(s) 当成 split
❌list("a,b")以为得到['a', 'b'],实际得到['a', ',', 'b']
✅ 想切词用"a,b".split(",")
- join 要求元素全是字符串
❌"-".join([1, 2, 3])→TypeError: sequence item 0: expected str instance, int found
✅"-".join(str(x) for x in [1, 2, 3])
- 字符串和数字直接相加
❌"id_" + 5→TypeError,Python 不会偷偷把 5 变成"5"
✅"id_" + str(5),或用 f-stringf"id_{5}"
- 用 set() 做保序去重
❌list(set(["b", "a", "b"]))顺序不可控,可能变成['a', 'b']
✅ 3.7+ 用list(dict.fromkeys(["b", "a", "b"]))保序去重
- 把 int() 当成四舍五入
❌int(2.9)期望得到3,其实是2;int(-2.9)也不是-3
✅ 明确语义:截断用int(),取整用math.floor()/math.ceil(),舍入用round()
- 用 eval() 把字符串转回容器
❌eval("[1, 2, 3]")能从字符串还原列表,但它会执行任意代码,绝不能用于外部输入
✅ 解析 JSON 数据用json.loads();ast.literal_eval()也只适用于字面量且需谨慎评估
- 忽略浮点的转换误差
❌float("0.1")得到的并不是精确的 0.1,直接和0.1比较可能出错
✅ 需要精确十进制时用decimal.Decimal("0.1"),比较浮点用math.isclose()
总结
| 目标 | 写法 | 关键点 |
|---|
| 字符串转整数 | int(s, base=10) | 不吃小数点,向零截断 |
| 字符串转浮点 | float(s) | 接受科学计数法、inf、nan |
| 数字转字符串 | str(n)/ f-string | 拼接前必须显式转换 |
| 字符串转列表 | s.split(sep) | 不传 sep 才启用空白折叠 |
| 列表转字符串 | sep.join(seq) | 调用者是分隔符,元素必须全为 str |
| 容器互转 | list()/tuple()/set()/dict() | 浅拷贝,set丢顺序 |
数据类型转换的难点不在语法,而在边界:哪些字符串能转、截断方向、空串与None怎么处理。把int()截断、split()的空格折叠、join()的元素类型要求这三点记牢,再配合try/except兜住解析失败,绝大多数转换场景都不会出问题。