用游戏背包理解Python列表:从增删改查到排序查找实战

发布时间:2026/8/31 13:09:20
用游戏背包理解Python列表:从增删改查到排序查找实战 我一度以为算法和数据结构是只在面试题和竞赛代码里出现的东西直到我认真把一个小游戏玩明白才猛然发现那些看起来高深莫测的“算法”其实早就藏在日常操作里了。尤其当游戏里的背包、任务列表、排行榜出现时你根本逃不开一个最基础也最重要的数据结构——列表。这篇文章是“编程农场”系列的第 02 篇主题非常聚焦列表。我会用游戏里最常见的场景把列表的底层机制、常用操作、以及和列表强相关的排序、查找、遍历、切片这些“听起来像算法”的概念全部拆开揉碎讲清楚。适合刚学 Python、C、Java 但总觉得数据结构枯燥的同学也适合那些想用游戏化场景重新理解列表的开发者。学完这一篇你会发现列表不是一个需要死记硬背的考点而是你每天打开游戏、管理背包、处理任务队列时已经在用的逻辑。1. 列表到底是什么先从游戏背包说起1.1 一个最直观的比喻背包格子很多新手学数组和列表时第一反应都是“这不就是一堆数据排在一起吗”。这个理解方向是对的但不够深刻。我们换个场景想你的游戏角色有一个背包背包里有 20 个格子每个格子可以放一件物品。你往第 0 个格子放了一把剑往第 1 个格子放了一瓶药水往第 2 个格子放了一张地图。这个“背包”就是一个列表。格子的编号就是索引index格子里放的东西就是元素。你用“背包[0]”拿到剑用“背包[1]”拿到药水——这和 Python 里写backpack[0]、backpack[1]是完全一样的逻辑。列表解决的第一个问题就是把多个同类型或不同类型的数据组织在一起并且允许通过编号快速访问。在编程农场这个系列里如果我们把农场里收获的每颗蔬菜都看成游戏背包里的物品那“今天收获的蔬菜列表”就天然是一个列表。1.2 列表、数组、链表的区别很多初学者会把“列表”和“数组”混为一谈这不能算错但在理解底层时要注意区分数组Array一段连续的内存空间每个元素大小固定通过索引访问速度极快。链表Linked List由节点组成每个节点存数据和下一个节点的地址插入删除快随机访问慢。列表List在 Python 中列表更像“动态数组”的封装既能按下标快速访问又支持随时追加元素内存不够时自动扩容。这里不需要死记硬背。只需要记住Python 的列表是一种“用起来很舒服的数组”它解决了传统数组长度固定、增删麻烦的问题。C 语言里的数组长度写死了就不能改Python 里my_list.append(10)就可以随手加一个元素底层自动帮你申请新的内存空间。1.3 列表能解决什么问题在游戏和日常开发中列表主要解决这几类问题批量管理同类数据比如一局游戏里所有怪物的血量、一次任务里所有奖励道具的名称。保持数据的先后顺序比如任务队列、出招顺序、消息记录。配合循环进行批量处理没有列表时你要处理 100 个怪物得写 100 行代码有列表后一个 for 循环搞定。为排序、查找等算法提供基础载体几乎所有经典算法冒泡排序、二分查找、快速排序都是基于列表或数组展开的。所以你可以把列表理解成“算法的游乐场”。几乎所有算法题核心操作对象都是列表。这也是为什么算法学习永远绕不开列表。2. 环境准备与版本说明在学习列表时我们主要使用 Python 3 作为演示语言因为它的语法最接近直觉适合新手把注意力放在“列表本身的逻辑”而不是“内存地址怎么算”。但文中也会提到 C/Java 里数组的对应写法方便有语言迁移需求的读者。操作系统Windows / macOS / Linux 均可Python 版本3.8 及以上推荐 3.10开发工具IDLE、VS Code、PyCharm 都可以额外依赖无全部使用 Python 标准库版本不一定非要和我一致。如果你本机装的是 Python 3.8下面所有示例也都支持如果你用的是 Anaconda、MiniConda 或系统自带 Python也都能直接运行。重点是理解列表的思维方式而不是纠结某个版本才有的语法糖。检查 Python 版本可以在命令行执行python --version如果输出类似Python 3.10.12就说明环境没问题。3. 列表的核心知识点拆解在进入游戏实战之前我们先系统过一遍列表的核心知识点。这些内容不是零散的语法罗列而是后续游戏代码的基础。3.1 创建列表的多种方式Python 里创建一个列表最简单的当然是方括号直接写# 创建一个空列表 backpack [] # 创建带初始元素的列表 items [剑, 药水, 地图] # 使用 list() 函数创建 numbers list([1, 2, 3]) # 通过 range() 生成数值列表 levels list(range(1, 6)) print(levels) # [1, 2, 3, 4, 5]这里尤其要注意range()的用法。range(1, 6)生成从 1 到 5 的整数序列结尾是开区间也就是不包含 6。很多新手在写range(1, 6)时以为包含 6结果循环少了一次这是非常经典的坑。3.2 索引与切片游戏里最常用的操作索引就是“第几个格子”的编号。Python 列表的索引从 0 开始也就是第一个元素的索引是 0第二个是 1依此类推。backpack [剑, 药水, 地图, 面包, 金币] print(backpack[0]) # 剑 print(backpack[2]) # 地图 print(backpack[-1]) # 金币负索引表示从后往前数 print(backpack[-2]) # 面包负索引是一个很方便的特性-1表示最后一个元素-2表示倒数第二个。这在处理“排行榜最后一名”“最近一条消息”等场景时非常实用。切片则是从一个列表里“切”出一个子列表语法是list[start:end:step]。同样end 是开区间不包含。items [剑, 药水, 地图, 面包, 金币] # 从索引1开始到索引3之前也就是拿第1个和第2个 print(items[1:3]) # [药水, 地图] # 拿前三个 print(items[:3]) # [剑, 药水, 地图] # 隔一个取一个 print(items[::2]) # [剑, 地图, 金币] # 反转列表 print(items[::-1]) # [金币, 面包, 地图, 药水, 剑]切片在游戏里的典型应用是“分页显示背包”“显示最近 5 条战斗日志”。理解了切片你就能写出非常简洁的代码而不需要写一堆 for 循环。3.3 常用方法增删改查列表作为“动态数组”最常用的方法可以分为四类增加、删除、修改、查询。先看增加backpack [] # 在末尾追加 backpack.append(剑) backpack.append(药水) # 在指定位置插入 backpack.insert(1, 盾牌) # 批量追加 backpack.extend([面包, 地图]) print(backpack) # [剑, 盾牌, 药水, 面包, 地图]再看删除# 删除指定元素(只删第一个匹配项) backpack.remove(药水) # 按索引弹出并返回最后一个元素 last backpack.pop() # 按索引弹出指定位置的元素 first backpack.pop(0) # 清空列表 backpack.clear()修改很简单直接对索引赋值backpack[0] 神器查询类操作items [剑, 药水, 地图] # 查找元素位置 index items.index(药水) # 1 # 统计元素出现次数 count items.count(剑) # 1 # 判断是否存在 if 地图 in items: print(背包里有地图)这里有一个新手容易忽略的问题append是原地修改是生成新列表。很多人在函数里用my_list my_list [1]和my_list.append(1)看起来结果一样但前者会创建新对象在循环里频繁做运算会产生大量临时对象性能会差很多。3.4 遍历列表for 循环与枚举遍历是列表操作里最高频的动作。最简单的遍历就是 for 循环items [剑, 药水, 地图] for item in items: print(item)如果你同时需要索引和元素用enumeratefor index, item in enumerate(items): print(index, item)在游戏场景里遍历列表最常见的需求是“给背包里所有物品增加效果”比如所有武器攻击力 5所有药水回复量翻倍。这种批量处理本质就是一个遍历加修改。3.5 列表与基础算法排序、查找、反转列表之所以和算法强绑定是因为算法经常需要处理“一组数据”。最基础的三类算法就是查找、排序、遍历处理。线性查找逐个判断元素是否满足条件。def find_item(items, target): for index, item in enumerate(items): if item target: return index return -1冒泡排序通过相邻元素两两比较并交换把最大值“冒”到最后面。这个算法是很多新手的第一个排序算法虽然效率不高但特别适合理解排序的本质。def bubble_sort(nums): n len(nums) for i in range(n - 1): for j in range(n - 1 - i): if nums[j] nums[j 1]: nums[j], nums[j 1] nums[j 1], nums[j] return nums print(bubble_sort([5, 2, 9, 1])) # [1, 2, 5, 9]二分查找针对有序列表每次和中间元素比较把搜索范围缩小一半。它的前提是列表必须有序。def binary_search(nums, target): left, right 0, len(nums) - 1 while left right: mid (left right) // 2 if nums[mid] target: return mid elif nums[mid] target: left mid 1 else: right mid - 1 return -1 print(binary_search([1, 2, 5, 9, 20], 9)) # 3Python 内置的sort()方法和sorted()函数底层是 Timsort 算法时间复杂度约为 O(n log n)比手写冒泡排序更快。但在学习阶段手写一遍冒泡排序和二分查找能帮你把“列表的索引、比较、交换、循环边界”这些基础功打扎实。4. 实战案例用“编程农场”游戏理解列表与算法下面我们进入最核心的实战环节。我会带你做一个极简的“编程农场”游戏控制台程序它不依赖任何第三方库只有一个main.py文件却能完整体现列表的增删改查、遍历、排序、查找等核心操作。游戏设定是这样的农场里有一块地玩家可以种菜、收菜。收割下来的蔬菜会放进背包列表。背包可以查看、排序、查找。收获一定数量蔬菜后可以完成任务并领取奖励。4.1 创建项目结构先在本地新建一个目录coding_farm然后在里面创建main.py。整个项目只有一个文件用命令行运行。coding_farm/ └── main.py4.2 设计数据结构与游戏功能在这个游戏里我们需要两个列表harvested_crops已收获的蔬菜列表元素是字符串比如萝卜、白菜。task_list任务列表元素是字典包含任务名称、目标和当前进度。用字典作为列表元素可以模拟现实开发中“列表里存对象”的常见情况。4.3 编写核心代码下面是完整代码可以直接复制运行# 文件路径coding_farm/main.py import random # 蔬菜种子池 SEED_POOL [萝卜, 白菜, 土豆, 西红柿, 南瓜] # 背包列表存放已经收获的蔬菜 harvested_crops [] # 任务列表每个任务是字典 task_list [ {name: 收获任意蔬菜 3 次, target: 3, progress: 0}, {name: 收获 2 个南瓜, target: 2, progress: 0}, ] def plant_and_harvest(): 模拟种菜并收获从种子池随机出现一种蔬菜放进背包 crop random.choice(SEED_POOL) harvested_crops.append(crop) print(f你收获了一个 {crop}) update_task_progress(crop) return crop def show_backpack(): 展示背包列表 if not harvested_crops: print(背包是空的。) return print(\n 背包列表 ) for index, crop in enumerate(harvested_crops): print(f第 {index} 格{crop}) print(f总共 {len(harvested_crops)} 个物品\n) def update_task_progress(crop): 根据收获的蔬菜更新任务进度 for task in task_list: if 任意蔬菜 in task[name]: task[progress] 1 elif crop 南瓜 and 南瓜 in task[name]: task[progress] 1 def show_tasks(): 展示任务列表 print(\n 任务列表 ) for task in task_list: status 完成 if task[progress] task[target] else f进度 {task[progress]}/{task[target]} print(f{task[name]}{status}) def sort_backpack(): 对背包里的蔬菜排序使用内置 sort if not harvested_crops: print(没有可以排序的物品。) return harvested_crops.sort() print(背包已按名称排序) show_backpack() def find_crop(crop_name): 在线性查找的基础上返回蔬菜在背包中的所有位置 positions [] for index, crop in enumerate(harvested_crops): if crop crop_name: positions.append(index) if positions: print(f找到了 {crop_name}位置在{positions}) else: print(f背包里没有 {crop_name}。) def hand_write_bubble_sort(nums): 手写一个冒泡排序用于理解排序算法 n len(nums) for i in range(n - 1): for j in range(n - 1 - i): if nums[j] nums[j 1]: nums[j], nums[j 1] nums[j 1], nums[j] return nums def demo_bubble_sort(): 演示冒泡排序 demo_list [5, 3, 8, 1, 9, 2] print(排序前, demo_list) sorted_list hand_write_bubble_sort(demo_list) print(冒泡排序后, sorted_list) def main(): print( 欢迎来到编程农场 ) while True: print(\n请选择操作) print(1. 种菜并收获) print(2. 查看背包) print(3. 查看任务) print(4. 背包排序) print(5. 查找蔬菜) print(6. 演示冒泡排序) print(0. 退出) choice input(请输入数字).strip() if choice 1: plant_and_harvest() elif choice 2: show_backpack() elif choice 3: show_tasks() elif choice 4: sort_backpack() elif choice 5: name input(请输入要查找的蔬菜名称).strip() find_crop(name) elif choice 6: demo_bubble_sort() elif choice 0: print(游戏结束再见) break else: print(无效输入请重新选择。) if __name__ __main__: main()4.4 代码讲解每个函数和列表有什么关系这段代码看着不长但几乎用到了列表的所有核心操作。我们逐个拆解SEED_POOL是一个全局列表它起到了“配置项”的作用。在真实开发中这种常量列表可以让代码更清晰。harvested_crops是核心背包列表。每次种菜收获用append()在末尾追加一个元素模拟“把蔬菜放进背包”。task_list是任务列表列表里每个元素是字典。这模拟了现实开发中“对象列表”的场景。你可以把字典理解成一个“小对象”通过task[name]、task[progress]访问属性。show_backpack()用了for index, crop in enumerate(harvested_crops)遍历列表同时拿到索引和元素。update_task_progress()遍历task_list修改字典里的progress字段这是典型的“列表修改”操作。sort_backpack()直接调用list.sort()方法对列表进行原地排序。因为字符串排序默认按字母顺序所以“白菜”会排在“萝卜”前面。find_crop()是一个线性查找遍历列表把所有匹配的位置收集到一个新列表positions中。注意这里返回的是一个列表也是“列表套列表”思维的体现。demo_bubble_sort()用一个小列表演示手写冒泡排序帮助你理解比较和交换过程。4.5 运行与验证在命令行或终端进入coding_farm目录运行python main.py交互过程大概是这样的 欢迎来到编程农场 请选择操作 1. 种菜并收获 2. 查看背包 3. 查看任务 4. 背包排序 5. 查找蔬菜 6. 演示冒泡排序 0. 退出 请输入数字1 你收获了一个 土豆 请选择操作 1. 种菜并收获 2. 查看背包 3. 查看任务 4. 背包排序 5. 查找蔬菜 6. 演示冒泡排序 0. 退出 请输入数字2 背包列表 第 0 格土豆 总共 1 个物品因为蔬菜是从SEED_POOL里随机选择的所以每次运行的输出不一定完全一样但流程和原理是确定的。你可以在plant_and_harvest()函数里添加打印信息观察每次append之后背包的变化。这里有个小建议当你运行几次后可以试试输入5查找一个并不存在的蔬菜再对比任务进度变化。这些练习能把“在列表中查找”“修改列表内容”变成肌肉记忆。5. 常见问题与排查思路列表和算法虽然基础但新手在实际练习中会遇到不少问题。这些问题有相当一部分不是语法问题而是“对列表的边界和生命周期理解不到位”。下面整理几个经典场景。问题现象常见原因解决思路IndexError: list index out of range访问了不存在的索引比如列表只有 3 个元素却访问了list[5]先打印len(list)确认长度循环时注意边界使用-1访问最后一个元素ValueError: xxx is not in listremove()或index()查找一个不存在的元素调用前先判断if target in my_list或使用find_crop()时先遍历一遍确认循环中一边遍历一边删除元素结果漏删或报错删除元素后列表长度和索引发生变化可以遍历原列表的副本for item in my_list[:]或先收集要删除的元素再统一删除使用拼接列表导致每次循环都生成新列表性能变差误把当作原地追加方法在循环中追加用append()扩展多个元素用extend()只有需要新列表时才用sort()和sorted()混淆没分清“原地排序”和“返回新列表”list.sort()原地修改原列表返回Nonesorted(list)返回新列表两个列表变量“互相影响”直接赋值list2 list1两个变量指向同一个列表对象需要独立副本时使用list2 list1.copy()或list2 list1[:]冒泡排序结果不对内层循环范围写错多排或者漏排了最后一个元素使用for j in range(n - 1 - i)确保已排好的部分不再参与比较其中最值得单独强调的是“直接赋值”的问题。看下面这段代码a [1, 2, 3] b a b.append(4) print(a) # [1, 2, 3, 4]a 也被改了很多新手以为b a是复制一份其实b和a是同一个列表对象的两个名字。这在游戏里可能造成“一个角色的背包被另一个角色共享”的 bug。解决办法是显式拷贝b a.copy() # 或 b a[:]这种问题在 Python 里尤其常见因为 Python 的变量本质上是“对象的引用”。理解这一点对后续学习函数参数、深浅拷贝都很有帮助。6. 最佳实践与工程建议6.1 优先使用列表推导式简化代码列表推导式是 Python 里非常优雅的列表生成方式。它能用一行代码完成“创建新列表并过滤、转换”的操作。# 传统写法 squares [] for i in range(10): squares.append(i * i) # 列表推导式 squares [i * i for i in range(10)]在游戏场景中比如你想“给所有收获的蔬菜名称加上前缀“新鲜的”可以直接写fresh_crops [f新鲜的{crop} for crop in harvested_crops]列表推导式不是炫技它能显著提高代码可读性。但也要注意如果逻辑复杂到难以一眼看懂就老老实实写循环可读性优先于简洁。6.2 切片是处理子集的好工具切片在很多场景下比循环更清晰。# 取最近 5 条战斗日志 recent_logs combat_logs[-5:] # 分页显示每页 10 条 page 2 page_size 10 start (page - 1) * page_size end page * page_size current_page_items all_items[start:end]这种写法特别适合游戏背包分页、排行榜分页、日志滚动等场景。理解start:end的半开区间语义能避免很多边界 bug。6.3 用enumerate替代手动索引计数很多初学者喜欢这样写index 0 for item in items: print(index, item) index 1这种写法容易出错而且不够 Pyhtonic。更推荐直接用enumeratefor index, item in enumerate(items): print(index, item)如果起始序号不是 0还可以传入第二个参数for index, item in enumerate(items, start1): print(index, item)6.4 修改列表时注意深拷贝与浅拷贝在真实项目中列表往往不是简单的字符串数组而是字典、对象甚至嵌套列表。这时拷贝就要格外小心。假设你的游戏背包列表里每个元素是字典backpack [ {name: 剑, attack: 10}, {name: 盾牌, defense: 5}, ]如果只做浅拷贝new_backpack backpack.copy() new_backpack[0][attack] 99你会发现原背包也被改了因为字典在复制时引用没有独立。这种场景需要深拷贝import copy new_backpack copy.deepcopy(backpack)在项目实战中备份存档、角色切换、队伍配置复制都需要根据列表元素类型决定用浅拷贝还是深拷贝。6.5 排序时注意稳定性与 key 参数Python 的sort()是稳定排序也就是说相同元素的相对顺序不会改变。这在多关键字排序里很有用。比如先按蔬菜名称排序再按收获数量排序可以用key参数传入函数crops_with_count [ {name: 萝卜, count: 5}, {name: 白菜, count: 2}, {name: 南瓜, count: 9}, ] crops_with_count.sort(keylambda x: x[count], reverseTrue) print(crops_with_count)lambda在这里相当于一个“匿名函数”表示“取这个字典里的 count 字段作为排序依据”。reverseTrue表示从大到小排序。这套组合在排行榜、任务列表排序中非常常用。6.6 生产环境中的数据安全提醒如果将来你把类似逻辑写成 Web 服务或游戏后端要特别注意不要轻易删除列表元素尤其是根据用户输入的下标去删除。删除前需要校验下标是否越界否则很容易产生IndexError。对包含用户数据的列表做批量修改前建议先备份一份原列表防止误操作后无法恢复。在数据库场景中对应的原则是更新和删除前必须加条件、先查询确认、再执行生产环境操作前要做好备份或使用事务回滚机制。这些虽然是后端开发的话题但从列表练习开始就养成“修改前确认、删除前备份”的意识会受用很久。7. 从列表到更广阔的算法世界如果你已经能独立理解上面所有代码那你其实已经掌握了编程中最基础也最核心的抽象能力把一组数据放进容器然后对这组数据进行遍历、查找、排序、筛选。这个能力无论你以后写 Python、Java、Go还是 C都是相通的。列表只是数据结构的第一站。顺着这条线继续往前走你会接触到栈后进先出适合处理游戏里的撤销操作、函数调用。队列先进先出适合处理消息队列、签到顺序。字典/哈希表键值对存储适合做游戏道具 ID 到道具信息的映射。树层级结构适合处理技能树、装备强化路径。图网络关系适合处理地图寻路、社交关系。这些数据结构都与列表有千丝万缕的联系。比如树的每一层可以看成一个列表图的邻接表就是一个以列表为元素的列表——也就是“列表套列表”。当你把列表彻底吃透再学其他数据结构会轻松很多。所谓算法并不是一个个孤立的高深名词而是“如何高效地解决某类问题”的套路。而列表就是承载这些套路最基础也最重要的容器。你不需要在一开始就背下所有排序算法的模板只需要在每次使用append、sort、index、切片时多问一句“底层到底发生了什么”“为什么这样设计”。在编程农场这个系列里我们接下来还会继续用游戏场景去理解二叉树、图、动态规划等更复杂的概念。但无论学到多深请记住所有高级结构都是从列表这样的基础容器生长出来的。现在把列表的每一个操作、每一个边界条件都亲手跑一遍就是为后续所有算法学习打下的最扎实的地基。你可以现在就把上文中的main.py复制到本地运行一遍然后试着改一两个功能比如把背包上限设为 10 格超过后提示“背包已满”或者给蔬菜增加“品质”字段按品质排序。这些动手实践比看十篇文章都更有用。祝你玩得开心也学得开心。