
关于“过滤偶数并计算平方”这组操作很多Python教程里都有但大多只是扔给你一段[x**2 for x in range(10) if x % 2 0]就算完事。今天我想换个角度把这个看似简单的小例子掰开揉碎讲透它背后涉及的条件过滤、列表推导式、内存模型、异常处理甚至如何把它从“玩具代码”变成能应对真实数据量的实用脚本。无论你是刚装好Python还没写过几行代码的新手还是想巩固基础的老手这篇都能给你点不一样的东西。网上搜Python安装教程、vscode配置Python环境的人特别多说明很多朋友卡在了“环境能跑”到“写出第一段有效代码”之间。看完这篇你会理解Python处理数据流的典型思路也能顺手掌握几个之后写爬虫、做数据分析、写量化策略代码时一定会用到的技巧。1. 先搞懂题目到底在问什么1.1 过滤和计算背后的两个基本动作“过滤偶数并计算平方”拆开看就是两步第一步把一批数字里的偶数挑出来第二步对挑出来的每个偶数做平方运算。听起来简单但这两个动作在编程世界里对应着两种最核心的能力——条件筛选和映射变换。条件筛选解决的是“哪些数据需要保留”的问题。比如你有一堆用户ID只想保留活跃用户或者你有一批日志只想保留错误级别。过滤的本质就是给每个数据一个“是/否”的判断判断通过的留下不通过的丢掉。在这个例子里判断条件就是“是否能被2整除”。映射变换解决的则是“保留的数据如何转换”的问题。平方、取绝对值、字符串拼接、类型转换这类操作不会改变数据的数量只是把每一个输入变成对应的输出。筛选和变换组合起来就构成了绝大多数数据处理任务的底层逻辑这也是为什么很多大数据框架比如Spark把核心操作抽象成filter和map两个算子。1.2 这个例子适合什么样的学习阶段如果你正在背Python基础语法想通过小例子把for循环、条件判断、列表这些概念串起来这个题目非常合适。因为它不需要额外装任何库用Python自带的语法就能写完。同时它又能引出列表推导式、生成器这些进阶特性让你在同一个例子里感受到“代码从啰嗦到简洁再到高效”的进化过程。如果你已经在用Python做爬虫或者数据分析也别觉得这个例子幼稚。爬虫里清洗数据时你会反复用到“过滤”和“变换”量化策略里处理行情数据更是离不开对序列做条件筛选和数学计算。这个例子就是这些东西的微缩版理解了它你后面看pandas的时候会轻松很多。2. 从最笨的方法写起再逐步进化2.1 普通循环写法理清逻辑的第一步不管会不会列表推导式我都建议你先把普通写法亲手打一遍numbers range(1, 11) even_numbers [] for n in numbers: if n % 2 0: result n ** 2 even_numbers.append(result) print(even_numbers)这段代码完全直译了题目遍历1到10的数字用%运算符判断奇偶偶数的就计算平方然后塞进结果列表。这里有个新手很容易犯的错把result n ** 2写在if外面。那样的话所有数字都会计算平方过滤就失效了。缩进在Python里是语法的一部分if下面再缩进一层代表“只在这个条件成立时执行”千万别小看这个缩进报IndentationError的大多就是在这里栽的。range(1, 11)生成的是从1到10的整数序列。注意range是“左闭右开”的结尾写11才能包含10。如果你写range(10)则生成0到9那样偶数就变成了0、2、4、6、8平方结果里会多个0根据你的具体需求选择起始值。2.2 用列表推导式一行搞定过滤和计算当你掌握了循环写法后就可以接触Python中最有辨识度的特性之一列表推导式。它的好处不只是省几行代码而是把“表达式”“循环”“条件”三个部分清晰地压缩在一个句法结构里代码即逻辑。squares_of_evens [n ** 2 for n in range(1, 11) if n % 2 0] print(squares_of_evens)读法很简单从for n in range(1, 11)循环if n % 2 0过滤对留下的每个n计算n ** 2收集结果。这里有个有意思的细节列表推导式里的顺序是“结果在前循环其次条件最后”。很多人第一次写会写成[if n % 2 0 for n in range(1, 11) n ** 2]这种奇怪顺序然后报语法错误。记住Python的列表推导式语法就是这样设计的表达式永远在最前面。2.3 为什么推荐先写循环再“进化”到推导式我见过不少教程直接教推导式结果学生连%取余都还没玩明白就开始背语法。我的习惯是遇到新需求先用最直白的循环写通逻辑确认结果正确后再考虑能不能改成推导式。这样既能保证思考过程不被打断又能体现代码优化的价值。而且循环版本中你可以在里面加print(n)来观察每一步的执行过程这对调试特别友好。推导式版本想打印中间过程就得额外写函数或者改用循环不利于新手理解。所谓“优化”的前提是先有正确的、能跑的代码而不是一上来就追求最短写法。3. 核心细节逐项拆解取余、幂运算与序列范围3.1%取余运算符的边界情况判断偶数用n % 2 0这里的%是取余运算符得到的是整除后的余数。偶数除以2余数为0奇数余数为1。这个逻辑看起来无比简单但有三个边界情况值得你注意。第一负数。-4 % 2在Python里结果是0所以负偶数也能正确识别。-3 % 2的结果是1不是 -1因为Python取余运算的结果符号跟除数保持一致也就是“模运算”的数学定义。很多从C语言转过来的朋友会在这里产生认知冲突因为在C语言里-3 % 2可能得到 -1。所以如果你的数据里有负数直接用n % 2 0判断偶数没有任何问题放心用。第二浮点数。4.0 % 2的结果是0.0也能和0比较通过。但是浮点数计算可能引入精度问题比如某些数在计算机里表达不精确导致%结果出现极小误差。筛选用判断要有风险意识。如果数据是整数建议先转成int再操作。第三None或非数字类型。如果列表里混入字符串或None%会直接抛出TypeError。真实场景中数据往往是脏的所以后面我会专门讲异常处理与数据清洗。3.2 平方的三种写法你该选哪个题目里的“计算平方”Python中有至少三种常见写法写法示例特点幂运算符n ** 2语义清晰适合任意指数推荐乘法n * n速度最快但只适合平方这种特殊计算pow()函数pow(n, 2)函数式写法适合需要传参调用的场景从可读性来说n ** 2最直观从性能来说n * n在大量数据下稍微快一点因为幂运算符需要处理指数计算的通用逻辑。不过如果你只是处理几千几万个数字这点性能差异完全不是瓶颈。我给的建议是写代码优先考虑可读性用n ** 2。但如果你在做高性能计算例如每秒处理上亿次运算可以考虑把平方换成乘法。3.3range的起始值、步长与可迭代对象很多人对range的理解停留在“生成0到某个数的序列”其实它有三个参数range(start, stop, step)。默认起始是0默认步长是1。题目中如果要“过滤偶数”你可以不写循环里的%判断直接用步长参数生成偶数# 从2开始步长为2生成10以内的偶数 even_numbers list(range(2, 11, 2)) print(even_numbers) # [2, 4, 6, 8, 10]这算是一个巧妙的替代方案但它和“先全量遍历再过滤”的思路不同。前者是一条断言“我就是要这些偶数”后者是一种通用过滤能力——当你的筛选条件不是简单的“步长”时比如“能被3整除且大于50”你就需要条件表达式了。还有一个很多人忽略的点range在Python 3里是惰性求值的它不一次性生成所有数字而是每次迭代才产生下一个值。所以哪怕你写range(10**9)内存也不会爆。但如果你把它转成list(range(10**9))那内存瞬间爆炸。这个特性在后面的生成器部分还会用到。3.4 为什么结果是列表而不是别的类型result [n ** 2 for n in range(1, 11) if n % 2 0]列表推导式产出的自然是一个list。列表的特点是可变、有序、可重复适合大多数数据处理场景。但如果你只需要迭代一次或者数据量极大列表会白白占用内存。这时候可以改成生成器推导式把方括号换成圆括号result_gen (n ** 2 for n in range(1, 11) if n % 2 0)这个result_gen是一个生成器对象它不会一次性计算出所有结果而是在你每次迭代时才生成下一个值。举个例子如果你要处理一亿个数字列表推导式可能直接把你内存耗尽而生成器推导式消耗的内存几乎可以忽略。但生成器只能遍历一次遍历完就空了不能索引不能重复使用这是个取舍。3.5 从列表到更高级的容器集合与元组如果把方括号改成花括号得到的是集合推导式result_set {n ** 2 for n in range(1, 11) if n % 2 0}集合会自动去重而且无序。对于平方结果来说因为每个偶数平方都唯一所以看不出区别。但如果你的处理逻辑会产生重复结果而你恰恰想要去重后的结果那么集合推导式就是很自然的选择。你在数据分析中经常用到的“去重”操作底层就是这样一种集合思维。如果要想得到不可修改的结果可以用tuple包一层result_tuple tuple(n ** 2 for n in range(1, 11) if n % 2 0)这种写法把生成器对象直接转成元组适合当作常量数据使用。4. 异常处理与脏数据真实场景无法回避的问题4.1 当数据列表里混入“奇怪的东西”研究pyth上述示例的原始数据都是range 生成的整数干净得像刚洗过的苹果。但现实中你拿到的数据往往来自API、Excel表格、爬虫抓取的页面一个不小心里面就会混入字符串、空值、浮点数甚至字典。这时直接套用列表推导式程序大概率报错中断。写一段不那么健壮的代码当反例data [1, 2, 3, 4, None, 5.0, 6] result [n ** 2 for n in data if n % 2 0]这段代码运行到3时会因为字符串和整数做取余操作而抛出TypeError: unsupported operand type(s) for %: str and int。None也一样。结果就是整个程序崩溃没有任何输出。真实项目里数据清洗是重头戏甚至能占掉整个开发时间的一半以上。所以当你看到别人漂亮的列表推导式时别忘了背后可能还有一串清洗逻辑。4.2 在推导式里做类型判断和异常捕获最简单的做法是在条件部分加入类型检查data [1, 2, 3, 4, None, 5, 6] result [n ** 2 for n in data if isinstance(n, int) and n % 2 0] print(result)isinstance(n, int)确保n是整数后才继续判断是否偶数。这样字符串和None会被直接过滤掉不再引起异常。注意bool是int的子类所以True会被当成1False被当成0如果你不想把布尔值算进去还得额外排除isinstance(n, bool)的情况。有时候你希望把异常情况单独记录而不是静默丢弃此时列表推导式就不够用了需要写一个普通循环加try...exceptdata [1, 2, 3, 4, None, 5, 6] clean_result [] for n in data: try: if n % 2 0: clean_result.append(n ** 2) except TypeError: # 记录日志或者做一些其他处理 continue print(clean_result)把%和**都包在try里任何类型不匹配的问题都会被抓到。这种写法牺牲了一点推导式的优雅但换来了更强的健壮性。写得多了你会形成一种直觉处理自己构造的干净数据时怎么简洁怎么来处理第三方数据时第一时间想的应该是防崩溃。4.3 自定义过滤函数的场景如果判断逻辑不是“取余为0”这种简单条件而是需要多步计算或外部调用你可以定义一个函数再配合内置的filter函数def is_even_and_valid(n): if not isinstance(n, int): return False return n % 2 0 data [1, 2, 3, 4, None, 5, 6] filtered filter(is_even_and_valid, data) squares [n ** 2 for n in filtered] print(squares)filter的第一个参数是函数第二个参数是可迭代对象。它会依次把每个元素传给函数根据返回的布尔值决定是否保留。这种方式把“判断逻辑”抽出成为独立单元便于单元测试和复用代码结构也更清晰。我前阵子处理一批电商订单数据时需要过滤出“金额大于100且不是退款单”的记录就是写了个filter函数然后在函数里塞了十几行逻辑。相比把所有条件堆在一个列表推导式里这种方式不容易出错别人看代码时也更舒服。5. 进阶玩法让这个例子长出翅膀5.1 用map与filter组合实现彻底摆脱推导式列表推导式虽然Pythonic但并非唯一选择。filter负责过滤map负责映射两者组合起来同样能实现题目要求numbers range(1, 11) squares list(map(lambda n: n ** 2, filter(lambda n: n % 2 0, numbers))) print(squares)这里用了lambda表达式也就是临时定义的匿名函数。filter先筛出偶数map再对每个偶数求平方最后list()把结果转成列表。这种写法在某些函数式编程风格的项目里很常见。不过对新手来说lambda和两个嵌套函数调用组合起来阅读门槛比列表推导式高不少。我的看法是列表推导式是Python社区的默认偏好filtermap可以作为知识储备遇到项目里已有这种风格时能看懂就够了。5.2 把逻辑封装成可复用函数直接写一段脚本当然也行但如果你在多个地方都要过滤偶数并计算平方封装成函数是更好的做法def even_squares(numbers): return [n ** 2 for n in numbers if n % 2 0] print(even_squares(range(1, 11))) print(even_squares([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))函数封装的好处不言而喻提高复用性、减少重复代码、方便测试。你还可以加上类型注解让函数意图更清晰from typing import Iterable, List def even_squares(numbers: Iterable[int]) - List[int]: 过滤输入中的偶数并计算平方。 return [n ** 2 for n in numbers if n % 2 0]类型注解是Python 3的一个好特性它不会影响运行但能让IDE给出更智能的提示也能让其他阅读代码的人快速理解参数和返回值的类型。现在很多公司的Python代码规范里都要求公开函数必须写类型注解。5.3 生成器函数处理巨大数据量的正确姿势如果数据量极大比如你要过滤一个日志文件中几千万个数字列表推导式会一次性把所有平方结果放进内存很可能把内存占满。这时可以用生成器函数def even_squares_gen(numbers): for n in numbers: if n % 2 0: yield n ** 2 # 使用时逐个取而不需要加载全部结果 gen even_squares_gen(range(1000000)) print(next(gen)) # 第一个偶数平方 print(next(gen)) # 第二个函数里只要出现yield它就不再是普通函数而是一个生成器函数。调用它不会执行函数体而是返回一个生成器对象。每次next()才真正执行到下一个yield把结果“吐”出来。这就是惰性求值的思想与之前提到的range和生成器推导式一脉相承。我用这个方式处理过一个大几GB的文本文件里面有海量的传感器读数需要筛选出满足条件的值并做平方计算。用列表推导式的话程序中途就会被杀掉改用生成器函数后内存占用一直保持平稳处理完全没有压力。所以如果你觉得自己可能要处理上百万级数据建议尽早把生成器练熟。5.4 用numpy改写体验向量化计算提到数值计算就不能不提numpy。它是Python数据科学生态的基石几乎所有的数据分析、量化策略、机器学习代码都依赖它。用numpy改写这个题目会完全换一种风格import numpy as np arr np.arange(1, 11) # 生成数组 even arr[arr % 2 0] # 布尔索引过滤 squares even ** 2 # 直接对数组求平方 print(squares)arr[arr % 2 0]利用布尔数组对原数组进行索引一步完成过滤。然后even ** 2会对每个元素同时计算平方这就是“向量化”操作。整个过程没有显式循环运行速度可能比纯Python列表推导式快几十倍尤其在数据量大的时候优势明显。为什么快因为numpy的底层是C语言实现的操作的是连续内存上的同类型数据避免了Python对象的逐项解释开销。如果你要做数据分析或者量化策略numpy是绕不开的坎这个例子可以作为你入门向量化思维的第一步。6. 实操过程全记录从环境配置到运行结果6.1 本机Python环境准备开始实操前要确保你的电脑已经装好Python。没装好的先去 python官网 下载对应系统的安装包。这里我挑几个容易踩坑的点说一下。Windows用户安装时务必勾选“Add Python to PATH”选项不勾选的话后面在命令行里敲python会提示找不到命令。macOS用户如果通过Homebrew安装Python装完建议再执行一下检查。Linux用户则可以直接看系统是否自带或者通过软件包管理器安装。安装完成后在命令行或终端里输入python --version能看到版本号就说明环境就绪。如果你还想用Visual Studio Code写代码需要额外安装Python插件然后在VS Code底部选择Python解释器才能正常代码补全和运行。6.2 在VS Code中编写并运行完整示例我平时写Python用的就是VS Code轻量、功能够用、配置起来不算折腾。在编辑器里新建一个even_squares_demo.py文件写入下面这段完整代码from typing import Iterable, List def even_squares(numbers: Iterable[int]) - List[int]: 过滤偶数并计算平方。 return [n ** 2 for n in numbers if n % 2 0] if __name__ __main__: data range(1, 21) result even_squares(data) print(1到20之间的偶数平方, result)if __name__ __main__:的作用是只有直接运行这个文件时下面的代码才会执行如果别人import这个模块就不会自动触发测试代码——这是良好的脚本结构习惯。在VS Code里你可以直接点击右上角的三角形运行按钮或者打开终端输入python even_squares_demo.py。运行结果会打印1到20之间的偶数平方 [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]注意结果从4开始到400结束因为1到20里的偶数是2、4、...、20。如果你期望的是从别的范围开始可以调整range参数。6.3 在Jupyter Notebook或命令行中交互式测试如果你想一边写一边看结果用Jupyter Notebook很方便。在命令行安装jupyter或者直接装Anaconda打开Notebook后一个格子就能写代码另一个格子写文字说明非常适合学习和调试。命令行交互式PythonREPL适合快速验证小段代码逻辑。直接输入python进入交互模式然后逐行敲代码每敲一行立刻看到输出这种即时反馈对理解Python的求值过程很有帮助。比如输入 [n ** 2 for n in range(1, 11) if n % 2 0] [4, 16, 36, 64, 100]回车马上出结果非常适合验证想法。6.4 从示例到脚本把结果写入文件如果你的目标用户不是一个终端而是需要把结果保存成文件那么可以加上文件写入操作。这里用纯Python自带的功能不需要第三方库with open(even_squares.txt, w, encodingutf-8) as f: f.write(str(result))with语句会自动管理文件关闭避免资源泄漏。encodingutf-8是为了避免中文字符在Windows默认编码下写入报错。如果你想把结果保存成Excel光靠基础函数是不够的需要安装openpyxl或者pandas。之前看到一个热词是“python写入excel”说明很多人在学这个需求后面我会稍微展开Excel相关的写法。6.5 用pandas扩展从列表到数据表的思维转换当数据规模变大、字段变多之后列表甚至numpy数组都不够用了你需要像表格一样处理数据。这时候该用pandasimport pandas as pd df pd.DataFrame({number: range(1, 11)}) df[even] df[number] % 2 0 even_df df[df[even]] even_df[square] even_df[number] ** 2 print(even_df)这里创建了一个 DataFrame先用df[number] % 2 0计算出一个布尔列再用布尔列过滤行最后计算平方并添加为新列。这个过程和最早的if n % 2 0逻辑一模一样但表达方式变成了“列运算”和“按行筛选”。我个人建议把pandas的这种思维当作后续进阶目标刚开始学Python用列表推导式没问题但当你发现自己频繁写for循环处理二维数据时就该思考是不是可以用pandas把这些操作拍平成一行。量化策略代码里的大量数据处理都是这种模式学会它很多“看起来高级”的框架也就没那么神秘了。7. 常见问题与排查技巧实录7.1 为什么我的列表推导式报语法错误很多初学者会把列表推导式的顺序记错写成类似下面这样[if n % 2 0 for n in range(10) n ** 2] # 错误正确的顺序是[表达式 for 变量 in 可迭代对象 if 条件]。if必须在循环之后表达式必须在最前面。如果报错信息里有SyntaxError: invalid syntax基本就是顺序问题。建议拆成普通循环先写一遍再逐字对照改成推导式。7.2 结果里出现了多余的0如果你用range(10)生成的是0到9那么偶数包括0平方自然是0。如果你的预期是只处理1到10那就应该写成range(1, 11)。这个属于范围设置问题不是逻辑错误。判断数据边界时一定要想清楚“左闭右开”的规则。7.3 为什么直接打印生成器对象看不到结果生成器表达式和生成器函数都不会立即执行所以直接打印会看到generator object genexpr at 0x...。你看到的其实是对象的内存地址不是数据本身。要拿到结果可以直接用list()把它转换为列表result_gen (n ** 2 for n in range(1, 11) if n % 2 0) print(list(result_gen))或者用循环逐个取。理解惰性求值是进阶的一道坎它占用内存小但要注意只能迭代一次第二次迭代就没有数据了。7.4 数据量很大时程序内存占用太高如果你用列表推导式处理上亿个元素肯定会内存爆炸。排查时先看是不是用了list(range(...))或者列表推导式一次性产生海量结果。解决办法是改用生成器推导式或者直接处理可迭代对象而不转成列表。比如只想求和根本不需要列表total sum(n ** 2 for n in range(1, 100000001) if n % 2 0) print(total)这里sum接收一个生成器表达式边迭代边累加内存占用极其稳定。7.5 过滤时把字符串“3”识别成了数字这个问题来自类型不匹配前面反复提到过。如果你明确知道列表里可能有数字字符串可以先做类型转换再配合异常捕获data [1, 2, 3, 4, None] cleaned [] for item in data: try: num int(item) if num % 2 0: cleaned.append(num ** 2) except (TypeError, ValueError): passint(item)尝试把字符串3转成整数3如果转换失败就捕获异常。这里的continue与pass有细小差别但在这个场景下结果可以一样。用异常捕获时不要写空except:至少要指定具体的异常类型不然会给排查问题带来麻烦。7.6 想保留原有数据的顺序和分组信息怎么办如果原始数据不是简单的数字而是带字段的记录比如订单数据你需要同时过滤和保留订单ID用列表推导式处理二维结构会非常勉强。这个时候你应该转向pandas或者自定义类。举个例子orders [ {id: 1, amount: 23.5}, {id: 2, amount: 100.0}, {id: 3, amount: 88.8}, ] def is_even_amount(order): return int(order[amount]) % 2 0 filtered_orders list(filter(is_even_amount, orders))这里过滤逻辑不再针对单个数字而是针对字典。平方不再是核心操作但筛选的思想完全一致根据某个字段的计算结果决定是否保留整条记录。把例子从“数字列表”升级成“结构化数据”你就能体会到 Python 在真实业务中的强大处理能力。8. 结合热词与场景看看哪里还用得上这个例子8.1 爬虫数据处理中的过滤模板搜“python爬虫”的人特别多很多人在爬页面时遇到的问题是“拿到了列表但要从中筛选出有效内容”。比如从商品价格列表里过滤出大于100元的商品或者从评论内容里过滤出包含特定关键词的评论。这些操作的底层逻辑就是“条件过滤”只不过条件从“偶数”换成了“价格100”或者“关键词在评论中”。举个例子你爬回一个价格列表有的是字符串有的是数字有的还是None处理方式prices [39.9, 120, None, 199, 59.9] def clean_price(item): if item is None: return False return float(item) 100 valid_prices [float(p) for p in prices if clean_price(p)]这个和过滤偶数的思路一模一样只是判断条件和数值转换更复杂一点。所以别小看“过滤偶数”它就是你在爬虫里写清洗函数的雏形。8.2 数据分析与可视化中的筛选动作搜索“python数据分析与可视化”的人常常会问怎么选数据。用pandas筛选一行数据时你其实就是在做“过滤偶数”的升级版df[df[列名] 阈值]。可视化之前如果不想看见异常值第一步也是过滤。我自己的习惯是做任何图表之前先用一套过滤逻辑把不想要的点清理掉然后才交给绘图库。这个习惯就是从早期的“过滤偶数”里养成的先确定要什么再决定显示什么。8.3 量化交易策略代码里的条件过滤量化策略代码经常需要对行情数据做条件筛选比如“选出收盘价大于20日均线的股票”、再计算收益率。这就是“过滤”天然出现的场景。至于“计算平方”在量化里可能变成收益率平方或者波动率的平方数学处理形状不同但代码结构依然相似。曾经有留言问“python量化交易策略代码难不难学”我会说先从这类小例子理解“筛选 映射”的数据流后面接触回测框架时你会发现所有策略本质都是“对历史数据做条件判断 数学变换”。功底扎实了框架就只是一个工具而已。8.4 中秋节祝福代码和爱心代码本质是模式化输出再发散一下热词里还有“python爱心代码”“python中秋节祝福代码”。这些代码看起来花哨实际上也是把一些固定样式用循环和条件生成出来。比如爱心曲线遍历参数输出的是一串带颜色的字符祝福代码用循环打印几行祝词。它们和“过滤偶数并计算平方”一样都是“对序列做处理并输出”的套路。我鼓励你学完基础后立刻写这些小项目不要觉得没用。写它们能强化你对循环、条件、函数、字符串格式化的掌控力。先跑通再说跑通之后再去拆解内部原理进步会很快。9. 性能对比与选型建议什么场景用哪种写法9.1 不同写法的性能实测经验我用timeit简单测试过几种写法的执行时间供你参考。测试内容是对1000万个整数过滤偶数并计算平方。写法大致耗时内存占用普通 for 循环约1.2秒较高需要存结果列表列表推导式约0.8秒较高同样存结果列表生成器 手动迭代约1秒极低几乎不占额外内存filtermap 列表转换约0.9秒较高numpy向量化约0.05秒中等底层为连续数组这个测试结果可能因机器而异但趋势很明显列表推导式比普通循环快因为它在内部做了优化numpy快是因为底层C语言加SIMD指令集优化生成器不追求速度而是追求内存效率。9.2 选择建议别盲目追求最快速度不是唯一指标。如果你处理的数据只有几千条用普通循环都毫无压力但如果数据有上千万条内存占满导致程序崩溃就比慢一点更可怕。我的选型经验如下数据量小于10万条直接用列表推导式代码最简洁。数据量在百万级以上优先考虑生成器表达式或写生成器函数避免爆内存。需要做广播计算、矩阵运算或进一步扩展到数据分析直接用numpy/pandas。团队协作时优先看团队代码风格如果项目已经到处都是列表推导式就别为了炫技写一堆filtermap。9.3 配合递归或高阶函数时的一些反直觉点如果你把“过滤偶数并计算平方”写成递归理论上可行但没必要因为Python递归深度有限性能也差。高阶函数也不一定处处优于推导式。这是一个典型的需要区分“能不能”和“该不该”的场景。数据结构、可读性、团队习惯、运行环境这些都比“一行代码解决”重要得多。10. 最后再分享一点我的个人习惯实实在在地说这种小题目我每隔一段时间都会做一遍不是为了复习而是为了测试自己对Python的敏感度。比如你现在再看[n ** 2 for n in range(1, 11) if n % 2 0]脑海里能不能立刻浮现出它展开后循环的样子能不能立刻说出它和生成器表达式的内存差异如果不能建议回到最笨的循环版本多敲几遍。我在实际项目里处理过不少“过热”的数据任务吃过内存爆掉的亏以后我现在写这类过滤代码有个固定习惯先默认用生成器推导式如果确定结果列表本身就需要被反复索引和切片才改用列表推导式。这个习惯帮我减少了很多次程序运行到一半被系统杀掉的情况。还有一个不用钱的经验写这种基础小练习时随手在每行代码下面加上注释解释为什么要这么写而不是只抄一个结果。注释写给自己看也写给别人看。你将来回看这些笔记时能清楚地看到自己当时的思考是怎样一点点演变的那比任何教程都珍贵。