总有人私信问我:Python 应该从哪开始学?我的回答永远是同一句——先把数据类型玩明白。这句话不是说语法不重要,而是因为在 Python 里,无论你写变量、做判断、调函数、处理文件,还是后面接触 pandas、numpy 做数据分析,本质上都在跟不同的数据类型打交道。前两天一个读者拿了段爬虫代码问我为什么报错,我一看,是字符串和列表混着拼接,连最基本的type()都没看。这种问题几乎每个零基础的人都会踩一遍。所以这篇教程,我想把 Python 数据类型这条主线彻底讲透:从int、float、str、bool、None这几个基本类型,到list、tuple、dict、set四个容器类型,再到类型转换的边界情况和常见坑,最后延伸到 pandas 这类数据分析库里的dtype概念。不管你是完全零基础的小白,还是已经写了点代码但老在类型上翻车的人,这篇都值得慢慢看。
1. 先搞清楚:Python 数据类型到底是什么
1.1 变量是标签,不是“盒子”
很多零基础的人对变量有个错误印象:变量名是一个盒子,赋值就是把值放进去。这个模型在 Python 里会让你产生大量误解。更准确的理解是:变量名是便利贴,数据对象才是真正存在于内存里的东西,赋值就是在对象上贴一张名字标签。
举个例子:
a = [1, 2, 3] b = a a.append(4) print(b) # [1, 2, 3, 4]如果变量是“盒子”,那b = a应该是把a里的[1, 2, 3]复制一份给b,结果b不应该跟着变。但实际结果b变了,因为a和b两个名字贴在了同一个列表对象上。这个区别是后面学习深浅拷贝、列表陷阱的前提。带着“标签”模型去看 Python 类型,很多现象就自然了。比如a = 1; b = a; a = 2,最后b仍然是1,因为整数是不可变对象,给a重新贴2的标签不会影响b指向的整数对象1。
1.2 Python 有哪些内置数据类型
Python 官方文档把所有内置类型分成很多类,但对零基础来说,先记住这张表就够用:
| 类型 | 名称 | 是否可变 | 典型场景 |
|---|---|---|---|
int | 整数 | 不可变 | 计数、编号、索引 |
float | 浮点数 | 不可变 | 价格、身高、测量值 |
str | 字符串 | 不可变 | 文本、日志、路径 |
bool | 布尔值 | 不可变 | 条件判断、开关状态 |
NoneType | 空值 | 不可变 | 表示“什么都没有” |
list | 列表 | 可变 | 有序的一组数据 |
tuple | 元组 | 不可变 | 固定结构的数据 |
dict | 字典 | 可变 | 键值对映射关系 |
set | 集合 | 可变 | 去重、集合运算 |
你不需要一次全背下来,但要有“家族感”:数字家族、文本家族、容器家族。后面学函数、类、文件操作,都是这几大家族在来回组合。想确认一个对象到底是什么类型,最简单的方式就是type():
print(type(1)) # <class 'int'> print(type("hello")) # <class 'str'> print(type([1, 2])) # <class 'list'>用type()打印类型,是我调试代码最常用的动作。一旦报错出现TypeError,第一步永远是看看出错的那个变量到底是什么类型,而不是盯着错误信息发呆。
1.3 动态类型不等于“不用管类型”
Python 是动态类型语言,意思是变量本身没有固定的类型,同一个变量今天指整数,明天指字符串,语法上完全允许:
x = 1 x = "hello" x = [1, 2]这种写法能运行,但不代表你可以忽略类型。动态类型解决的是“写起来灵活”的问题,代价是“运行到某一行才暴露类型错误”。比如:
age = input("请输入年龄: ") print(age + 1)这段代码一运行就报错,因为input()返回的是字符串,字符串不能和整数相加。所以真正的项目里,动态类型反而要求你有更强的“类型意识”。我见过太多新手把变量名全叫data、tmp,写了几十行都不知道里面是什么类型,调试起来非常痛苦。建议从第一天起,养成看到变量就多想一步“它现在是什么类型”的习惯。后面我会详细讲type()和isinstance()怎么配合使用。
2. 基本类型逐个拆解:int、float、str、bool、None
2.1 整数和浮点数:精度问题提前说
Python 3 里的int没有位数上限,可以很大:
print(10 ** 100) # 一个 101 位的数字这在写算法题、处理大数时很省心。float是实数的近似表示,遵循 IEEE 754 双精度标准,所以它有一个经典问题:0.1 + 0.2不等于0.3。
print(0.1 + 0.2) # 0.30000000000000004原因很简单:二进制无法精确表示 0.1 和 0.2,就像十进制无法精确表示 1/3 一样。这种误差在科学计算里通常可以接受,但在金额计算里不能忍。处理金钱最好用decimal.Decimal,或者先把单位转成“分”再用整数计算。
数值运算中有几个容易记混的符号:
print(7 // 2) # 3,整除,向下取整 print(7 % 2) # 1,取余数 print(7 ** 2) # 49,幂运算 print(-7 // 2) # -4,注意负数整除也是向下取整如果你要写一个“判断奇数还是偶数”的功能,记得用% 2。如果要做整数除法并保留结果,用//得到整数,用/得到浮点数。很多人第一次看到7 / 2 = 3.5而7 // 2 = 3会困惑,其实这就是 Python 刻意区分“精确除法”和“整除”的设计,比很多语言里int / int = int的规则更符合直觉。
2.2 字符串:引号、切片和格式化
字符串是字符的序列,用单引号、双引号、三引号括起来都行。单双引号没有本质区别,主要为了让字符串内部能包含另一种引号。三引号适合多行文本、文档字符串。
s1 = 'hello' s2 = "I'm a teacher" s3 = """第一行 第二行"""提到路径时要小心反斜杠转义。Windows 路径写成普通字符串会出问题:
path = "C:\Users\new" # \n 变成了换行 raw_path = r"C:\Users\new" # 原始字符串,推荐索引和切片是字符串最常用的操作。正索引从 0 开始,负索引从 -1 从右往左数:
s = "python" print(s[0]) # p print(s[-1]) # n print(s[0:2]) # py,左闭右开,取不到索引 2 print(s[::-1]) # nohtyp,倒序字符串是不可变的,所以s[0] = "P"会报错。想改字符串只能靠切片、替换等方法生成新字符串。常用方法里我几乎天天用split、join、strip、replace:
text = " hello,world " print(text.strip()) # 去掉首尾空格 print(text.split(",")) # ['hello', 'world'] print("-".join(["a", "b", "c"])) # a-b-c print(text.replace("world", "Python"))格式化字符串一定要用 f-string,直观、高效、可读性好:
name = "张三" score = 90.5 print(f"{name} 的分数是 {score:.1f}") # 张三 的分数是 90.5字符串还有一个底层概念叫编码。计算机存的是字节,你看到的中文和英文都要编码成字节。Python 里str是 Unicode 字符,bytes是字节序列,转换靠encode和decode:
s = "中文" b = s.encode("utf-8") print(b) # b'\xe4\xb8\xad\xe6\x96\x87' print(b.decode("utf-8")) # 中文如果编码和解码用的不是同一个字符集,就会出现乱码。日常处理文件时,最稳妥的方式就是统一用 UTF-8。
2.3 布尔值、None 和真假判断
bool只有两个值:True和False。它们通常来自比较表达式和逻辑运算,也常被用来表示开关状态:
is_adult = age >= 18 is_ok = (score > 60) and (attendance > 0.8)有个反直觉的事实:bool是int的子类。True == 1为真,False == 0为真。正因为这个继承关系,用type(x) == int判断整数会漏掉布尔值,后面讲isinstance时会专门说。
Python 里每个对象都有“真假值”,这叫真值测试。以下值一律被视为假:0、0.0、""、[]、()、{}、set()、None。其余都是真。这意味着你可以直接写:
if user_list: print("列表非空") else: print("列表为空")而不是写if len(user_list) != 0。这是 Python 风格里很常见的一招,变量是否存在内容一眼可见。
None代表“空”,是NoneType的唯一对象。函数没有return时默认返回None。判断变量是否为None,要用is None,不要用== None:
result = some_function() if result is None: print("函数没返回结果")is比较的是对象身份,==比较的是值。对于None这种单例对象,is None是规范写法。
2.4 类型转换的正确姿势
Python 提供了几个内置函数做类型转换:int(x)、float(x)、str(x)、bool(x)、list(x)、tuple(x)、dict(x)、set(x)。这是初学者必须练熟的一组操作。
比较典型的用法:
print(int("12")) # 12 print(int("12", 16)) # 18,按十六进制解析 print(float("3.14")) # 3.14 print(str(123)) # '123' print(bool("")) # False print(list("abc")) # ['a', 'b', 'c']注意,int("3.14")会报错,因为字符串里的小数点不是整数格式。必须先转成float,再转成int:
text = "3.14" value = float(text) print(int(value)) # 3转换失败会抛异常,常见两种:ValueError表示内容格式不对,比如int("abc");TypeError表示类型本身不对,比如int(None)。写代码时如果转换的数据来自用户输入、文件读取、接口返回,一定要加异常处理,否则程序会直接崩掉。后面我会给一个安全的转换函数模板。
3. 容器类型:list、tuple、dict、set 的区别与选择
3.1 列表:最常用的可变序列
列表用方括号[]创建,可以装任意类型的对象,并且自身可变。它是 Python 里最百搭的容器。
nums = [1, 2, 3] nums.append(4) # 尾部追加 nums.extend([5, 6]) # 一次追加多个 nums.insert(0, 0) # 指定位置插入 print(nums) # [0, 1, 2, 3, 4, 5, 6] nums.pop() # 移除并返回最后一个元素 nums.remove(1) # 按值移除第一个匹配项列表支持切片,和字符串的规则一致。列表元素也可以是列表,形成嵌套结构,比如矩阵、二维表格:
matrix = [[1, 2], [3, 4]] print(matrix[1][0]) # 3列表推导式是一行生成列表的利器:
squares = [x ** 2 for x in range(6)] even = [x for x in range(20) if x % 2 == 0]这个语法一开始有点晕,但用多了就回不去了。它的本质是“从某个序列里取元素,做处理,再放进新列表”。
列表的一个经典坑是在循环中删除元素。因为删除会改变索引,导致跳过一个元素:
arr = [1, 2, 3, 4, 5] for x in arr: if x % 2 == 0: arr.remove(x) print(arr) # 可能会得到 [1, 3, 5],但不是所有情况都如你预期如果一定要在循环里删,建议倒序遍历,或者更简单地构建一个新列表:
arr = [1, 2, 3, 4, 5] new_arr = [x for x in arr if x % 2 != 0]3.2 元组:我就是要一个“不能改的列表”
元组用圆括号()创建,和列表最大的区别是不可变。有点像把一组数据“锁死”,保证别人不会中途改掉。
point = (10, 20) year_month = (2024, 7)有一个特别容易看走眼的地方:创建只有一个元素的元组时,必须加逗号:
t1 = (1) # 这是整数 1,不是元组 t2 = (1,) # 这才是只有一个元素的元组 print(type(t1)) # <class 'int'> print(type(t2)) # <class 'tuple'>元组常配合解包使用:
a, b = (1, 2) print(a, b) # 1 2解包的本质是把元组里的元素按位置赋值给变量,这个能力在写循环、函数返回值时非常香。比如for index, item in enumerate(items)就是解包(index, item)。
元组不可变,但如果你往元组里塞了一个列表,列表本身还是能变的:
t = ([1, 2], 3) t[0].append(99) print(t) # ([1, 2, 99], 3)这是因为元组锁住的是“元素的引用”,不是引用指向的对象。理解这一点,你就不会对“元组不可变”产生误判。
3.3 字典:键值对的查找神器
字典用花括号{}创建,存放的是键值对。它的核心优势是查找速度极快,尤其在数据量大的时候。列表查找要挨个比,字典查找直接算位置,复杂度差异非常明显。
user = { "name": "小明", "age": 18, "city": "Shanghai" } print(user["name"]) # 小明 print(user.get("age")) # 18 print(user.get("score", 0)) # 0,键不存在时返回默认值用user["score"]如果键不存在会抛KeyError,用get则不会。所以从外部数据构建的字典,读取时优先用get。
字典的键必须是不可变类型。str、int、tuple都可以作为键,list、dict、set不行,因为它们可以变,会导致哈希值不稳定。这是 Python 的一个硬性约束,犯错时会看到TypeError: unhashable type: 'list'。
遍历字典有三种姿势:
for key in user: print(key) # 默认遍历键 for value in user.values(): print(value) # 遍历值 for key, value in user.items(): print(key, value) # 同时遍历键和值Python 3.7 以后,字典保留了插入顺序,所以“字典是乱序”的旧印象可以改一改了。它只是不能像列表那样按下标取元素,而是按关键字取。
3.4 集合:去重、交集、并集
集合用花括号{}创建,但要注意:{}创建的是空字典,空集合必须用set():
s = {1, 2, 3} empty = set()集合的核心特性是元素不重复、无序。最常见的需求就是去重:
words = ["apple", "banana", "apple"] unique_words = set(words) print(unique_words) # {'apple', 'banana'}集合运算很直观:
a = {1, 2, 3} b = {2, 3, 4} print(a & b) # {2, 3},交集 print(a | b) # {1, 2, 3, 4},并集 print(a - b) # {1},差集 print(a ^ b) # {1, 4},对称差集集合元素也必须是可哈希的,所以列表不能放进去。如果你想用列表的列表去重,只能转成元组再进集合。
需要注意,set没有顺序,直接list(set(words))去重后顺序可能变。需要保持原顺序时,用dict.fromkeys:
words = ["apple", "banana", "apple", "cherry"] result = list(dict.fromkeys(words)) print(result) # ['apple', 'banana', 'cherry']原理也很简单:字典的键不重复,且从 Python 3.7 起保持插入顺序。这个技巧在需要“去重且保持原序”时很实用。
3.5 四个容器怎么互相转换
容器转换是写代码时绕不开的操作。四个容器之间大部分都能互转:
print(list((1, 2, 3))) # 元组转列表 print(tuple([1, 2, 3])) # 列表转元组 print(set([1, 2, 2, 3])) # 列表转集合,顺带去重 print(list({"a": 1, "b": 2})) # 字典转列表,得到键的列表 print(dict([("a", 1), ("b", 2)])) # 两元素列表转字典还有一种常用场景,把两个列表凑成字典:
keys = ["name", "age"] values = ["张三", 18] info = dict(zip(keys, values)) print(info) # {'name': '张三', 'age': 18}zip把两个序列按位置一对一对“拉链”起来,配上dict()就是现成的字典。写爬虫、处理表格时,这种写法经常遇到。
3.6 可变与不可变:90% 新手掉过的坑
可变对象和不可变对象的行为差异,是 Python 教程讲得最多、新手掉坑也最多的地方。我总结几个典型场景。
第一个是函数默认参数用可变对象:
def add_item(item, items=[]): items.append(item) return items print(add_item(1)) # [1] print(add_item(2)) # [1, 2],第二次调用时默认列表已经变了这个问题的根源在于,默认参数[]在函数定义时只创建一次,后续所有调用共享同一个列表。正确写法是默认参数用None,函数内部再创建新列表。
第二个是局部修改外部容器:
def clean(nums): nums.clear() data = [1, 2, 3] clean(data) print(data) # []列表是可变对象,作为参数传入函数后,函数内部的原地修改会影响到外部变量。如果你不想影响外部,就传副本:clean(data.copy())。
第三个是深浅拷贝。list.copy()和切片copy()都是浅拷贝,只复制外层列表,内层嵌套的列表仍然共享:
origin = [[1, 2], [3, 4]] shallow = origin.copy() shallow[0][0] = 99 print(origin) # [[99, 2], [3, 4]]要彻底独立,用copy.deepcopy:
import copy deep = copy.deepcopy(origin) deep[0][0] = 0 print(origin) # [[99, 2], [3, 4]]遇到包含嵌套容器的数据,先想清楚“我是要共享,还是要独立副本”。这是列表、字典操作里最实用的一课。
4. 类型判断和类型转换的实战细节
4.1 type() 和 isinstance() 怎么选
判断类型有两个常用函数:type()和isinstance()。很多人随手用type(x) == int,但这里有个坑。
print(type(1) == int) # True print(type(True) == int) # False print(isinstance(True, int)) # True因为bool是int的子类,type(True)返回的是bool,不等于int;但isinstance会考虑继承关系,所以True被认为是整数的一种。绝大多数情况下,判断类型应该用isinstance,尤其是你不想把布尔值排除在整数之外的时候。
isinstance还支持元组类型:
from numbers import Integral, Real print(isinstance(3, (int, float))) # True print(isinstance(True, (int, float))) # True如果你写的是通用函数,只关心对象是不是“数”,可以用numbers模块里的抽象类型,而不是死磕int和float。这样代码对 numpy 数值类型也能兼容,不会被原生类型限制死。
4.2 隐藏在 ==、is、in 和布尔运算里的类型规则
前面说过==比较值,is比较内存地址。新手最容易踩的坑是“小整数缓存”:Python 为了性能,会缓存-5到256之间的整数,导致某些情况下a is b为真:
a = 256 b = 256 print(a is b) # True,但这是实现细节 c = 257 d = 257 print(c is d) # False,每次创建新对象不要依赖这种缓存行为。判断数字相等用==,只有判断None、单例对象时才用is。这个原则在大多数语言里都适用。
in运算符也有类型差异。x in list是线性扫描,x in set或x in dict是哈希查找,后者的查找速度几乎不受容器大小影响。所以当你在一个大列表里反复判断成员关系,应该先转成集合:
big_list = list(range(100000)) big_set = set(big_list) print(99999 in big_set) # 快很多and和or的返回值也容易让人迷惑。它们返回的并不一定是布尔值,而是决定整个表达式结果的最后一个对象:
print(0 or "default") # default print("hi" and "bye") # bye这个特性有时候很有用,比如设置默认值:name = user_name or "匿名"。
4.3 强制转换的边界和异常处理
类型转换不只是书上那几行int("12"),实战中大量数据来自网络、文件、用户输入,什么鬼格式都有。我建议封装一个“安全转换函数”:
def safe_int(value, default=None): try: return int(value) except (ValueError, TypeError): return defaultValueError覆盖“字符串内容不是数字”的情况,TypeError覆盖“对象根本没有转换成整数的接口”的情况。用这个函数,你就不用到处写 try except,代码干净得多。
浮点数还有一个特殊值:无穷大和 NaN。float("inf")表示正无穷,float("nan")表示不是一个数。NaN 有一点非常反直觉,它不等于它自己:
import math nan_value = float("nan") print(nan_value == nan_value) # False print(math.isnan(nan_value)) # True因此判断一个值是否为 NaN,要用math.isnan,不要用==。在数据分析中,NaN 经常出现在缺失值里,这个坑也是高频。
4.4 格式化、文件读写和 JSON 里的类型问题
数字格式化很简单,f-string 里:.2f保留两位小数,:,添加千分位:
price = 1234567.891 print(f"{price:,.2f}") # 1,234,567.89文件读写时会遇到类型问题。open()读出来的文本永远是字符串,想当数字处理就得先转换:
with open("data.txt", encoding="utf-8") as f: lines = f.readlines() nums = [int(line.strip()) for line in lines]写入时反过来,数值要str()转成字符串才能写。很多新手在这两类代码里卡住,因为没意识到“文件是字节和文本的边界,不是类型自动转换器”。
JSON 是前后端、爬虫、接口对接最常见的格式。json.dumps只能序列化基础类型,如果你的字典键是整数,序列化时会被强制转成字符串:
import json data = {1: "a", 2: "b"} print(json.dumps(data)) # {"1": "a", "2": "b"}如果对象里有set、datetime、自定义类,直接dumps会报TypeError: Object of type set is not JSON serializable。解决办法是先转换成列表、字符串等基础类型,或者写一个自定义的默认转换函数。处理 JSON 数据时,我建议每次都打印一下type()和len(),先确认拿回来的是列表还是字典,再动手遍历。
5. 扩展到 pandas、Redis 和 JavaScript 的类型体系
5.1 pandas 的 dtype:数据分析必懂
学 Python 数据分析和数据科学,绕不开 pandas。pandas 里的Series和DataFrame也有“类型”的概念,但不叫type,叫dtype。常见的有int64、float64、object、bool、datetime64、category等。
你可以用dtypes查看每个列的类型:
import pandas as pd df = pd.DataFrame({ "age": ["20", "25", "30"], "score": ["80.5", "90.2", "88.0"] }) print(df.dtypes) # age object # score object看到object就要警惕:这一列其实存的是 Python 对象,通常是字符串。想让列能算平均值、做排序,就得转换类型:
df["age"] = pd.to_numeric(df["age"]) df["score"] = df["score"].astype(float) print(df.dtypes) # age int64 # score float64pd.to_numeric和astype是两种常用方式。astype适合明确转换,pd.to_numeric更适合处理混合格式,还可以加errors="coerce"把转不过去的值变成NaN。为什么 pandas 这么在意数据类型?因为int64的列在内存和运算速度上比object列好太多,大数据量下差距会非常明显。做数据分析的第一课,就是先看dtypes,再决定要不要转。
5.2 不同语言里的数据类型对比
很多人搜过“redis 数据类型”“javascript 判断数据类型”“java 数据类型”,其实学习 Python 类型后,再看其他语言会有种“熟悉的陌生感”。
Redis 的string、hash、list、set、zset和 Python 的字符串、字典、列表、集合概念上有相似,但底层完全不同。比如 Redis 的list是链表,支持头尾操作;Python 的list是动态数组,按下标访问更快。Redis 的set是无序集合,和 Python 的set行为上很像,但它们本质是不同软件里的数据结构,谈不上完全等价。
JavaScript 的类型判断体系是最容易闹笑话的:typeof []返回"object",所以判断数组要用Array.isArray([])或者Object.prototype.toString.call([])。Java 是静态强类型,声明变量必须写类型,比如int a = 1,编译期就锁死了。Python 用动态类型加类型注解,介于两者之间,既灵活又可以通过工具做检查。
这些对比能帮你建立一个更通用的认知:数据类型的本质是“对象能支持哪些操作”,不同语言用不同规则描述同一件事。理解了 Python 的可变不可变、容器类型、哈希规则,学习任何一门新语言的数据结构都会快很多。
5.3 零基础到底要不要一上来就学 pandas
我的建议很直接:不要急着上 pandas。先花一两周把 Python 原生数据类型、条件分支、循环、函数搞熟练,再碰 pandas。原因很简单,pandas 里的dtype、索引、缺失值处理、apply函数,全是建立在 Python 基础类型之上的。如果连str和int的区别都不清楚,看到一个object类型的列只会更懵。
正确的路线是:原生的int/float/str/bool用熟以后,学list/dict,然后学函数和文件读写,最后再进入 pandas。到那时你会发现 pandas 的很多操作其实就是“把原生类型搬进表格结构”。很多人学到 pandas 转换类型时半天转不过弯,不是因为 pandas 难,而是前面原生类型的底子没打牢。数据结构的基本功,值得你在开头多花点时间。
6. 常见坑、练习清单和类型注解
6.1 我见过的十大典型错误
根据我带过学员的经验,我把最常见的十个和数据类型相关的错误列成速查表。建议你把它存下来,写代码前扫一眼。
| 错误写法 | 会看到什么 | 正确思路 |
|---|---|---|
| 用 list 做 dict 的键 | TypeError: unhashable type: 'list' | 改用 tuple 作为键 |
| 修改字符串中的字符 | TypeError: 'str' object does not support item assignment | 字符串不可变,用切片/替换生成新字符串 |
| 字符串和整数直接拼接 | TypeError: can only concatenate str (not "int") to str | 先str(数字)或改用 f-string |
用== None判断 | 大多数情况能跑,但语义不对 | 用is None |
| 集合去重后顺序变了 | 结果无规律 | 用list(dict.fromkeys(...))保序 |
| 循环时删除列表元素 | 元素被跳过,结果诡异 | 构建新列表,或倒序循环 |
函数默认参数写[] | 多次调用数据累积 | 默认参数用None,函数内再创建 |
| 用 float 算金额 | 出现0.30000000000000004一类的误差 | 用 Decimal 或整数分 |
用type(x) == int判断整数 | 对True判断失败 | 用isinstance(x, int) |
| 复制嵌套列表后改内容 | 原列表也跟着变 | 用copy.deepcopy |
这些坑不是靠背记住的,而是靠踩过之后形成条件反射。看到TypeError的时候,第一反应不是“哪里语法错了”,而是“哪个变量的类型和我以为的不一样”。
6.2 建议的练习路线和小任务
数据类型是“看十遍不如写一遍”的内容。下面几个小任务,是我给零基础学员设计的练习路线,难度逐步上升,每个任务都会用到不同数据类型。
第一个任务:写一个 BMI 计算器。输入身高和体重,计算 BMI 并打印结果。这个任务会让你练input()返回字符串、float()转换、f-string 格式化输出,把str/float串一遍。
height = float(input("请输入身高(米): ")) weight = float(input("请输入体重(千克): ")) bmi = weight / height ** 2 print(f"你的 BMI 是 {bmi:.2f}")第二个任务:输入一段英文文本,统计每个单词出现的次数。这是str、list、dict的组合练习,也是词频统计的雏形。
text = "the quick brown fox jumps over the lazy dog the" words = text.split() counts = {} for word in words: counts[word] = counts.get(word, 0) + 1 print(counts)第三个任务:把一个列表里的重复元素去掉,同时保持原顺序。用dict.fromkeys实现,体会字典键不重复、保持插入顺序的特性。
items = ["apple", "banana", "apple", "cherry", "banana"] unique = list(dict.fromkeys(items)) print(unique) # ['apple', 'banana', 'cherry']第四个任务:定义两个集合,输出交集、并集、差集。这个任务不仅练set,也练 Python 运算符的直观风格。
a = {"python", "java", "c"} b = {"python", "javascript", "go"} print(a & b) # {'python'} print(a | b) # {'python', 'java', 'c', 'javascript', 'go'}每个任务做完,都建议在关键位置加一行print(type(x)),观察同一段流程里变量类型是怎么变化的。我自己学的时候,就是这样在“类型”旁反复打标记,后来才有了一眼看出类型问题的能力。
6.3 从类型注解开始让自己的代码更稳
Python 是动态类型,但你不是必须把所有类型信息都丢掉。从 3.5 开始,Python 支持类型注解,写函数时能标出参数类型和返回值类型:
from typing import Dict, List def count_words(words: List[str]) -> Dict[str, int]: result: Dict[str, int] = {} for word in words: result[word] = result.get(word, 0) + 1 return result这里的List[str]表示“字符串组成的列表”,Dict[str, int]表示“字符串到整数字典”。运行时 Python 不会强制执行,但mypy、VSCode 的 Pylance 会在静态分析阶段帮你抓出类型错误。这等于把动态类型的灵活和静态类型的检查结合了起来,是现在主流 Python 工程的标准做法。
类型注解对零基础最大的价值,不是“学一门新语法”,而是逼你想清楚每个变量“到底是什么”。当你写def process(data: List[str])时,你就不会随手传入一个str。当函数报错时,IDE 会直接提醒类型不匹配。这个习惯越早养成,后面写 pandas、写机器学习代码就越稳。
还有个小经验:在 VSCode 里打开 Python 文件时,把鼠标悬停在变量上,它会直接显示推断出来的类型。写一行代码,看一眼类型提示,这比任何教程都管用。
最后分享一个我自己的习惯:每段代码写完,我都会随手打印一下关键变量的type(),尤其在调试 JSON、处理文件读取数据的时候。类型这种东西,你踩过的坑越多,就会越有感觉。零基础阶段别急着追求“看懂高级代码”,先把int、str、list、dict这几个老朋友搞清楚,后面学 pandas、写自动化脚本、处理数据,都会顺很多。祝你在数据类型这条路上少踩坑,早点体会到“类型对了,代码自然就稳了”的乐趣。