ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python数据类型完整拆解:从int、str到list、dict、set及类型转换实战

Python数据类型完整拆解:从int、str到list、dict、set及类型转换实战 刚开始学 Python 的人经常被一行报错弄得不知所措。比如TypeError: can only concatenate str (not int) to str或者AttributeError: int object has no attribute append。这些报错看起来千奇百怪根子往往只有一个对 Python 数据类型缺乏系统认识。本文就用实际场景把 Python 数据类型完整拆解一遍从 int、float、str 等基本类型到 list、dict、tuple、set 等容器类型再到类型转换、常见误区和实战代码尽量让你看完后遇到类型相关报错时有清晰的排查思路。很多初学者会觉得“数据类型”是特别基础的知识点翻几页文档就能过。但在真实项目中类型问题恰恰是一周能遇到好几次的坑别人调用你写的函数时传入的参数不是预期类型读取配置文件时全部是字符串需要转换成数字和布尔值爬虫解析出来的数据要组织成什么结构才能方便后续统计。这些问题并不需要背文档而是需要建立一套“数据在 Python 中如何组织和变化”的认知框架。读完之后你会更清楚变量背后的对象到底是什么类型、哪些类型可以修改、哪些类型可以放进字典作为键以及究竟如何完成安全可靠的数据类型转换。1. 为什么必须理解 Python 数据类型1.1 一个报错引发的思考先看一段最常见的代码age 18 message 我今年 age 岁运行这段代码会立刻得到一个 TypeErrorTypeError: can only concatenate str (not int) to str原因是我今年 是字符串类型它只能和字符串拼接而变量age当前保存的是整数对象18。计算机把18和18看成完全不同的两类数据前者是一个由字符组成的文本序列后者是一个可以直接参与加减乘除的数值。这个报错提示的就是“可执行的操作”不同以及 Python 不会自动帮你完成隐式类型转换。从这段代码能看出理解数据类型的关键不只是记住有哪些类型还要清楚每种类型支持哪些操作。字符串支持拼接、切片、查找、替换数字支持算术运算列表支持追加元素字典支持按键取值。如果你希望对一个对象执行它不支持的运算Python 就会抛出异常。1.2 Python 类型系统的三个关键词为了从更高维度理解 Python 数据类型可以记住三个关键词动态类型、强类型、一切皆对象。动态类型意味着变量本身没有固定类型类型属于对象。同一个变量名可以先保存整数再被重新赋值为字符串x 100 print(type(x)) # class int x hello print(type(x)) # class str这种写法在静态类型语言里很难直接做到但在 Python 里是合法的。好处是灵活、上手快坏处是代码稍微复杂后如果不加约束函数参数到底传入什么类型会变得难以猜测这也是后面讲类型标注的重要原因。强类型意味着 Python 不会轻易做隐式类型转换。字符串加整数会报错数字和字符串比较也会报错。大部分情况下你必须显式调用int()、str()、float()等方法完成转换。很多从 JavaScript 转过来的同学初期会觉得不习惯因为 JavaScript 中1 1会自动变成字符串11而 Python 会直接告诉你类型错误。一切皆对象则说明在 Python 里整数是对象字符串是对象函数也是对象。每个对象都有自己的类型对象的类型决定了它可以被哪些方法处理。正因为如此我们可以随时使用type()或isinstance()询问这个对象的类型。2. 环境准备与版本说明在看数据类型代码时最好有一台已经安装好 Python 的电脑或者一个能运行 Python 的在线环境。版本方面下面所有示例都基于 Python 3 语法建议使用 3.8 以上版本。这里不写出具体“某一版本”的绝对号召因为不同操作系统的安装方式有差异更重要的是掌握type()、isinstance()、int()、list()这些内置函数的使用方式它们在 Python 3 中表现一致。你可以先检查自己的环境python --versionWindows 下如果没有把 Python 加入 PATH可能需要使用py -3 --version或直接打开 IDE 内置终端。进入 Python 交互式解释器也很简单python在命令行看到类似的提示符后就可以输入代码并立刻看到输出。比如 print(type(3)) class int我平时更多使用 VS Code 编写.py文件再配合 Python 插件运行。如果想做数据分析和脚本调试PyCharm 也是不错的选择。对于本文的简单示例不需要额外安装第三方库只用 Python 自带的内置函数即可。3. 核心基本数据类型详解3.1 整型、浮点型、复数与布尔值Python 的整型用int表示常见的写法就是普通十进制数比如0、100、-3。与 C 语言不同Python 3 的整型没有位数上限理论上可以表示很大的数字。当然超过一定长度后计算速度会变慢但不会因为“溢出”直接报错。整型还可以写成二进制、八进制、十六进制形式例如0b1010表示二进制数字 100xFF表示十六进制数字 255。这部分在刷算法题或者做底层协议解析时会用到平时写业务代码默认十进制即可。浮点型用float表示它对应数学中的小数但计算机内部使用二进制存储小数因此它并不总能精确表示十进制小数。一个经典例子是print(0.1 0.2) # 0.30000000000000004这不是 bug而是浮点数存储机制决定的。如果你在写支付、金额、账本相关代码千万不要直接使用浮点数做精确计算更安全的方案是使用Decimal。普通科学计算、数据分析中的绝大多数场景float的精度已经足够。复数用complex表示数学上复数分为实部和虚部Python 中虚数单位是jc 3 4j print(c.real) # 3.0 print(c.imag) # 4.0 print(type(c)) # class complex复数主要出现在科学计算、信号处理、数学计算等领域。对于刚入门或主要写 Web、自动化脚本的同学先知道有这种类型即可不需要花太多时间。布尔值bool只有两个取值True和False。它通常用于条件判断比如if score 60:中的score 60会得到一个布尔值。这段表达式成立时返回True不成立时返回False。从类型体系上看bool有点特殊它是int的子类这意味着True可以当整数 1 使用False可以当整数 0 使用。写程序时不建议依赖这一点否则可读性会很差。3.2 字符串最常用的文本类型字符串类型str用来保存文本例如用户名、日志信息、文件路径、HTML 内容。Python 中定义一个字符串有多种方式name Alice city Beijing info 多行字符串 可以跨行书写 path rC:\Users\admin\test.txt单引号和双引号功能基本一致你可以在包含单引号的文本周围使用双引号避免转义。三引号适合保存长文本、文档字符串。r开头表示原始字符串里面的反斜杠不会做转义Windows 文件路径、正则表达式里经常用它。字符串是不可变类型。s abc之后你无法修改s[0]这个字符只能重新创建新的字符串。这让字符串在作为字典键、集合元素时非常安全。不要混淆“变量重新赋值”和“修改原对象”例如s s.upper()是让变量指向一个新的大写字符串原来的字符串对象并没有变。索引和切片是处理字符串最常用的手段s Python print(s[0]) # P print(s[-1]) # n print(s[0:3]) # Pyt print(s[::-1]) # nohtyP[::-1]这种写法能快速实现字符串反转。实际项目中还会常用以下方法text hello, python print(text.strip()) # 去掉首尾空白 print(text.split(,)) # 按逗号拆分成列表 print(text.replace(python, world)) # 替换 print(text.title()) # Hello, Python如果想拼接多个字符串直接用就行但多个字符串拼接时更推荐join()parts [2025, 01, 01] print(-.join(parts)) # 2025-01-013.3 空值 NonePython 的None是一个特殊对象也是NoneType这个类型的唯一实例。它表示“这里什么都没有”或“值暂时未知”。例如用户没有上传头像时头像字段可以保存为None表示不存在。定义一个不写return的函数时函数默认返回Nonedef do_nothing(): pass result do_nothing() print(result) # None判断是否为None应该用is None而不是直接用 None。虽然大多数时候二者结果一样但is是比较对象身份更符合None的语义。下面的写法才是规范做法value None if value is None: print(值为空)许多新手会把None、False、0、空字符串、空列表[]混在一起。它们在布尔上下文中都存在“假值”的语义但代表的意义不同0是数字中的零是长度为 0 的字符串[]是空列表None是真正的空对象。如果写if not value:那么以上情况都会被当成假如果你只想筛选出没有数据的空值应该更明确地判断。4. 容器数据类型list、tuple、dict、set4.1 list 列表列表list是有序、可变的容器它允许元素重复也允许不同类型混合存放fruits [apple, banana, pear] mixed [1, two, 3.0, True]列表支持追加、插入、删除和修改fruits.append(orange) fruits.insert(1, grape) fruits[0] watermelon fruits.remove(pear) print(fruits)输出为[watermelon, grape, banana, orange]因为列表有序所以可以像字符串一样使用索引和切片nums [10, 20, 30, 40, 50] print(nums[1:3]) # [20, 30] print(nums[-1]) # 50列表最重要的特性是可变。这意味着多个变量保存同一个列表对象的引用时修改会互相影响a [1, 2, 3] b a b.append(4) print(a) # [1, 2, 3, 4]这里a和b指向同一个列表对象所以通过b修改后a看到的数据也变了。理解这个现象有助于避免很多副作用问题。列表推导式是列表常用的高效写法。比如生成 0 到 9 中的偶数列表evens [x for x in range(10) if x % 2 0] print(evens) # [0, 2, 4, 6, 8]4.2 tuple 元组元组tuple与列表一样是有序容器区别在于元组是不可变对象。普通元组使用小括号point (10, 20) color red, green, blue print(point[0]) # 10一个容易踩坑的地方是定义只有一个元素的元组时必须写逗号t1 (5) t2 (5,) print(type(t1)) # class int print(type(t2)) # class tuple(5)并不会创建元组它只是括号表达式会得到整数 5。(5,)才代表包含元素 5 的元组。元组的不可变指的是“元素指向的对象不能替换”不是里面的所有内容都不能修改。比如元组中包含一个列表列表本身仍然可以变t (1, 2, [3, 4]) t[2].append(5) print(t) # (1, 2, [3, 4, 5])正因为元组不可变它能作为字典的键而列表不能。函数需要返回多个结果时元组是很自然的选择例如返回坐标、状态码和消息。4.3 dict 字典字典dict保存的是键值对每个键对应一个值。字典非常适合表示结构化数据例如一个学生的姓名和成绩student { name: Alice, score: 88, courses: [Python, Math], }可以直接修改、添加键值对student[age] 18 student[score] 95 print(student[name]) # Alice字典的查找速度非常快即使是很大的字典按键取值也能在很短时间完成。原理上字典利用键的哈希值定位存储位置所以一个对象能否作为键取决于它是否可哈希。不可变对象通常可哈希比如字符串、数字、元组可变对象通常不可哈希所以列表、字典、集合都不能作为字典键。需要特别注意直接使用student[age]时如果键不存在会抛出KeyError。为了避免异常可以使用get方法并返回一个默认值age student.get(age, 0) print(age)遍历字典也有多种方式for key in student: print(key) for value in student.values(): print(value) for key, value in student.items(): print(key, value)Python 3.7 开始普通字典会保持键的插入顺序也就是说你按什么顺序放进去遍历时基本就是什么顺序。这一点在很多业务场景中很有用但不要依赖在旧版本上的行为。4.4 set 集合集合set是一个无序且元素唯一的容器。它最常用的场景是去重和集合判断words [apple, banana, apple, orange] unique_words set(words) print(unique_words)输出可能为{banana, apple, orange}集合不会保证输出顺序而且你可以看到重复的apple只剩一个。数值型集合还支持交集、并集、差集等运算a {1, 2, 3} b {2, 3, 4} print(a b) # 交集{2, 3} print(a | b) # 并集{1, 2, 3, 4} print(a - b) # 差集{1}因为集合是无序的所以它不支持通过下标访问。如果需要可哈希的不可变集合可以使用frozenset例如frozenset({1, 2, 3})它可以作为字典键或嵌套到另一个集合里。4.5 可变与不可变对比类型是否可变是否可哈希典型使用场景int不可变是数值运算float不可变是科学计算、小数str不可变是文本、日志、标识tuple不可变元素均可哈希时可哈希固定结构数据list可变否动态数组、队列dict可变否键值映射set可变否去重、集合判断frozenset不可变是需要哈希的集合这张表在面试和笔试中经常出现核心要记住可变对象通常不能作为字典键也不能放进集合不可变对象在跨函数传递时不容易被函数内部意外修改。实际项目中如果你希望一个数据能传给函数并保持不变优先考虑元组或字符串这类不可变类型。5. 类型转换与类型判断5.1 常用转换函数Python 提供了一批内置函数用于在不同数据类型之间转换。最常见的包括int()、float()、str()、bool()、list()、tuple()、set()、dict()。从字符串转换成数字时字符串内容必须是合法的数字文本print(int(42)) # 42 print(float(3.14)) # 3.14把字符串3.14直接转成整数会失败因为里面含有小数点int(3.14)运行结果ValueError: invalid literal for int() with base 10: 3.14正确做法是先转成浮点数再转整数print(int(float(3.14))) # 3数值之间的转换则更直接print(int(3.99)) # 3 print(float(10)) # 10.0需要注意的是int(3.99)会向零方向截断小数部分而不是四舍五入。如果你想要四舍五入应该使用round(3.99)或者借助Decimal控制精确舍入规则。布尔值的转换也比较容易理解非零数字转为True零转为Falseprint(bool(0)) # False print(bool(-1)) # True print(bool()) # False print(bool(a)) # True容器之间互相转换常用于数据处理。例如把字符串转成列表、把列表转成元组、把列表转成集合去重s hello print(list(s)) # [h, e, l, l, o] print(tuple([1, 2, 3])) # (1, 2, 3) print(set([1, 2, 2, 3])) # {1, 2, 3}5.2 转换误区与安全建议初学者最容易犯的典型错误是混淆“字符串内容”和“显示形式”。比如用户输入age input(请输入年龄)得到的age永远是字符串。如果直接执行age 1会立刻报错必须先int(age)再参与运算。还有一个特别容易出错的例子flag bool(False) print(flag) # True字符串False并不是空字符串所以在布尔转换时结果为True。只有空字符串才会转换为False。如果想根据字符串内容False得到布尔值False需要自己判断text False flag text True print(flag) # False另外不建议使用eval()随意把字符串执行成对象。很多教程里会出现eval(3 5)看起来能计算字符串表达式但如果你接收的是用户输入eval(__import__(os).system(...))这类构造能让攻击者执行任意代码安全风险非常高。转换数字用int()或float()解析布尔值用显式判断即可。5.3 type 与 isinstance 的区别动态类型语言中经常需要判断数据类型有人习惯用type()有人习惯用isinstance()。两者都能判断对象的类型但推荐优先使用isinstance()因为它在面对继承时结果更符合直觉。看一个例子class Animal: pass class Dog(Animal): pass dog Dog() print(type(dog) Animal) # False print(isinstance(dog, Animal)) # Truetype(dog) Animal会判断 dog 的类型是否严格等于 Animal因为 dog 的类型是 Dog所以结果为 False。isinstance(dog, Animal)则会判断 dog 是不是 Animal 或 Animal 子类的实例结果自然为 True。在 Python 中这种子类关系很常见因此条件判断时用isinstance更合理。布尔值也有一个特殊现象print(type(True) int) # False print(isinstance(True, int)) # True因为bool是int的子类所以True是int的实例但type(True)并不是int。如果业务上必须严格区分整数和布尔值需要结合type判断否则按正常逻辑使用isinstance即可。6. 实战案例学生成绩统计程序6.1 需求与数据结构设计前面讲了很多概念这里用一个完整的小项目把它们串起来。需求很简单输入若干学生的姓名和成绩最后统计平均分、最高分、最低分以及不及格学生名单。在设计数据结构时最合适的选择是字典因为学生姓名和成绩天然是键值对关系students { Alice: 92, Bob: 57, Cindy: 76, }姓名作为键成绩作为值。这样后续取某个学生成绩、统计最高分、按分数筛选都很方便。输入过程可能包含非法格式因此还要对字符串进行拆分和转换并处理ValueError。6.2 代码实现将完整代码写到score_statistics.pydef input_students(): students {} while True: line input(请输入姓名和成绩用空格分隔输入 end 结束) if line.strip().lower() end: break parts line.split() if len(parts) ! 2: print(格式错误请输入姓名 成绩) continue name, score_str parts try: score float(score_str) except ValueError: print(成绩必须是数字) continue students[name] score return students def calculate_statistics(students): if not students: return None, None, None, [] scores list(students.values()) avg_score sum(scores) / len(scores) max_score max(scores) min_score min(scores) failed_students [ name for name, score in students.items() if score 60 ] return avg_score, max_score, min_score, failed_students if __name__ __main__: student_dict input_students() print(学生数据, student_dict) avg_score, max_score, min_score, failed_students calculate_statistics(student_dict) if avg_score is None: print(没有录入任何学生数据) else: print(f平均分{avg_score:.2f}) print(f最高分{max_score:.2f}) print(f最低分{min_score:.2f}) if failed_students: print(不及格名单, failed_students) else: print(本次没有不及格学生)这段代码使用了前面提到的几种数据类型字典保存学生信息list(students.values())把字典的值转换为列表sum()和max()接受可迭代对象列表推导式筛选不及格学生。函数返回多个结果时Python 会打包成一个元组接受时可以用多个变量解包。6.3 运行与结果说明在终端运行python score_statistics.py输入示例请输入姓名和成绩用空格分隔输入 end 结束Alice 92 请输入姓名和成绩用空格分隔输入 end 结束Bob 57 请输入姓名和成绩用空格分隔输入 end 结束Cindy 76 请输入姓名和成绩用空格分隔输入 end 结束David 88 请输入姓名和成绩用空格分隔输入 end 结束end预期输出学生数据 {Alice: 92.0, Bob: 57.0, Cindy: 76.0, David: 88.0} 平均分78.25 最高分92.00 最低分57.00 不及格名单 [Bob]为什么成绩会从整数变成浮点数因为代码中使用float(score_str)把输入文本转换成了浮点类型。如果希望保留整数可以使用int(score_str)但如果用户输入88.5就会报错。实际项目中需要根据业务对成绩精度的要求选择合适的数据类型。分数通常需要小数所以浮点数是合理选择。这个案例虽然不大却覆盖了本文的核心思路先判断业务数据适合用什么容器再设计输入转换逻辑最后利用列表推导式、元组解包等特性完成统计。如果你能独立把这个流程写出来说明你对 Python 数据类型已经有了实际使用能力。7. 常见问题与排查思路类型问题在真实开发中非常高频下面把几个最典型的现象、原因和解决思路整理成表。问题现象常见原因解决思路TypeError: can only concatenate str (not int) to str字符串和数字直接拼接先把数字转成字符串str(age)ValueError: invalid literal for int() with base 10: abc字符串内容不是数字转换前先校验字符串格式或用正则表达过滤TypeError: int object is not iterable对整数使用 for 循环或list()确认需要遍历的是列表、元组、字典等可迭代对象TypeError: unhashable type: list把列表作为字典键或放入集合改用元组或重新设计数据结构KeyError: xxx字典中不存在对应键使用get(key, default)安全取值AttributeError: NoneType object has no attribute xxx函数返回 None但继续调用方法先判断是否为 None再执行后续操作list默认参数导致数据串用函数定义时使用可变对象作为默认值默认值写None函数内部重新创建列表看最容易被忽视的默认参数问题。不要这样写def add_item(item, container[]): container.append(item) return container第一次调用add_item(1)返回[1]第二次调用add_item(2)返回[1, 2]。原因是默认参数container[]只在函数定义时创建一次多次调用共用同一个列表对象。安全写法是def add_item(item, containerNone): if container is None: container [] container.append(item) return container排查这类问题时可以先查看报错栈里最后一行提示的是哪个操作不支持再向上一层找到变量是哪里来的。动态类型语言里最常见的排查手段就是打印type(variable)和variable的值确认它到底是不是你预想的类型。养成这个习惯后大多数类型问题都能在本机快速定位。8. 最佳实践与工程建议数据类型是代码的地基。地基不稳上层再漂亮的业务逻辑都会不断出 bug。我在实际项目中总结了几条经验。第一善用类型标注。Python 的优势是动态灵活但大型项目中完全靠肉眼推测类型会提高沟通成本。给函数参数和返回值加上类型标注可以让静态检查工具帮助你提前发现错误。比如成绩统计函数可以写成from typing import Dict, List, Tuple, Optional def calculate_statistics( students: Dict[str, float] ) - Tuple[Optional[float], Optional[float], Optional[float], List[str]]: if not students: return None, None, None, [] scores list(students.values()) avg_score sum(scores) / len(scores) max_score max(scores) min_score min(scores) failed_students [name for name, score in students.items() if score 60] return avg_score, max_score, min_score, failed_students类型标注不会影响程序运行但能让 PyCharm、mypy、Pylance 等工具正确推断类型也能让其他人一眼看出函数应该传入什么数据。第二判断 None 使用is None判断容器内容使用。字符串比较也使用而不是is。is比较的是对象身份虽然 Python 对小整数和短字符串有一些缓存机制偶尔a is b会返回 True但这并不是可依赖的语言保证。规范写法能避免很多玄学问题。第三理解赋值与复制的区别。list_b list_a只是把引用复制了一份两个变量指向同一个列表。如果希望得到一个独立的副本可以使用切片list_a[:]、list(list_a)或copy.copy()浅拷贝。嵌套列表时浅拷贝仍然共享内层列表需要深拷贝时再考虑copy.deepcopy()。第四拼接字符串不要过分依赖。虽然写起来直观但在循环中反复拼接长字符串会不断创建新字符串影响性能。只需要把每一段文本放到列表中然后用.join(parts)一次完成。第五不要用可变对象作为函数默认参数。这个原因前面已经说了它属于高频 bug 来源之一一旦业务复杂后很难排查因为每次调用的结果都取决于之前调用过程留下的状态。第六对用户输入保持警惕。从input()、网络接口、文件读取得到的数据默认都是字符串甚至可能是空字符串、带空格的内容。在转换成数字之前先用strip()去掉首尾空白再做合法性校验。需要计算或解析表达式时不要对不可信输入使用eval()否则等于把代码执行入口暴露给外部。第七选择合适的数据结构是“磨刀不误砍柴工”。需要频繁按键取值时用字典需要保持顺序且频繁追加时用列表固定不变的结构用元组去重用集合。数据结构选得对代码会更短且更少出错。9. 总结与下一步学习方向本文从底层类型系统讲起介绍了 Python 中 int、float、bool、str、None 等基础类型也分析了 list、tuple、dict、set 四种常见容器的特性。理解可变与不可变是学习 Python 数据类型的关键分水岭不可变对象更安全可以作为字典键可变对象更灵活但要注意共享引用和默认参数带来的副作用。在实际开发里你不需要把所有方法都背下来但应该知道每个容器适合什么业务结构看到异常时能根据TypeError、ValueError、KeyError等关键词快速定位到数据和操作之间是否匹配。类型判断优先用isinstance输入转换前先想清楚字符串格式这些习惯能帮你省下大量调试时间。数据类型只是 Python 的基础模块。接下来建议按这个顺序继续学习先温习分支、循环和函数定义再尝试读写文件把字符串、字典、列表组合起来完成一个小的数据统计任务。等熟练之后可以进入面向对象编程学习类与self、属性与方法、继承和多态。如果对数据分析感兴趣早期接触 pandas 时也要先弄清楚 DataFrame 中每个列的对象类型因为那背后本质上还是本文讲的这些 Python 基本类型在发挥作用。可以试试不看代码自己完成一个“学生成绩统计”的简化版本不断输入姓名和成绩按end结束打印平均分和最高分并筛选出成绩低于 60 分的同学。能独立写出来说明这一篇的内容已经真正变成你自己的技能了。遇到报错不用紧张打印变量、确认类型、再决定如何转换三步走基本能解决绝大多数问题。
返回列表