
1. 为什么“77个函数”这个数字值得认真对待很多人看到“Python常用函数总结”这类标题第一反应是收藏第二反应是再也没打开过。我太理解这种心态了——网上随便一搜动辄“100个Python技巧”“200个内置函数大全”收藏夹里躺了几百篇真正记住的没几个。但“77个”这个数字有点意思它不像100那么整也不像50那么少恰好是一个有经验的开发者凭手感能列出来的、日常写代码真正高频用到的函数数量级。我自己的经历是写了六七年Python之后回头统计发现日常代码里反复出现的函数确实就在七八十个左右。剩下的那些要么是特定领域的比如memoryview、classmethod要么是可以用其他方式替代的。所以这篇内容我打算按“实际使用频率”和“解决什么问题”来组织而不是按字母顺序或者官方文档的分类来堆砌。你读完应该能做到两件事第一知道每个函数在什么场景下该用第二知道哪些函数容易踩坑、哪些参数有隐藏行为。这篇文章适合已经能写Python脚本但想提升代码质量的人也适合刚学完基础语法、想看看“真实项目里到底用哪些函数”的初学者。我不会只列函数签名和一句解释而是会结合具体场景说明为什么选它、不选别的方案以及我实际用的时候遇到过什么问题。2. 字符串处理最容易被低估的高频操作区2.1strip家族不只是去空格strip()、lstrip()、rstrip()这三个函数看起来简单到不需要讲但我见过太多人在处理用户输入或者文件读取时栽跟头。strip()默认去掉的是首尾的空白字符包括空格、制表符、换行符、回车符。但很多人不知道它可以接受参数指定要去掉的字符集合。text ###hello### print(text.strip(#)) # 输出: hello这里有个容易踩的坑strip(#)去掉的是首尾所有属于#的字符而不是去掉“###”这个子串。如果你写strip(###)效果和strip(#)完全一样因为参数被当作字符集合处理。我当初做日志解析的时候想去掉行首的[INFO]标记写了line.strip([INFO])结果发现[、I、N、F、O、]这些字符都被去掉了连正常内容里的字母I都被误伤。正确做法是用removeprefix和removesuffixPython 3.9或者用正则。提示处理CSV或日志时优先用removeprefix/removesuffix语义清晰且不会误伤内容字符。2.2split和join一对反向操作split()不传参数时按任意空白字符分割并且会自动忽略连续空白和首尾空白。这个行为在处理不规则文本时特别有用。但传了分隔符之后行为就变了——连续分隔符会产生空字符串。print(a,,b.split(,)) # [a, , b] print(a b.split()) # [a, b]join()是split()的逆操作但要注意它只能连接字符串序列。如果你有一个整数列表得先转成字符串,.join(map(str, nums))。我见过有人写,.join(nums)然后报TypeError还以为是join坏了。实际项目中split配合maxsplit参数能解决很多问题。比如解析配置文件的一行key value用line.split(, 1)可以保证值里面即使有等号也不会被切碎。2.3replace的第三个参数限制替换次数replace(old, new, count)的第三个参数很多人不知道。当你只想替换前几个匹配时这个参数能省掉一次正则或者循环。比如处理HTML转义时只替换第一个为amp;就可以用text.replace(, amp;, 1)。但要注意replace不支持正则表达式。如果需要模式匹配替换得用re.sub。我一般的原则是纯字符串替换用replace涉及模式、边界、分组就用re。replace的性能比re.sub好不少在循环里处理大量文本时差距明显。2.4startswith和endswith元组参数是隐藏利器这两个函数最容易被忽略的特性是参数可以是元组。这意味着你可以一次检查多个前缀或后缀。filename report.pdf if filename.endswith((.pdf, .docx, .txt)): print(可处理的文档)这比写or连接多个endswith调用要干净得多而且短路求值性能也更好。我在做文件分类工具时这个特性帮我省掉了大量if-elif。2.5format和f-string什么时候用哪个f-stringPython 3.6现在是格式化字符串的首选可读性最好性能也最优。但format()方法在需要动态指定格式模板时仍然有用。比如你从配置文件读取一个模板字符串然后往里填值这时候只能用format()或%。template 用户{name}的余额是{balance:.2f} print(template.format(name张三, balance1234.5))f-string的调试语法Python 3.8也值得记住f{x}会输出x具体值调试时非常方便不用再写print(x, x)。3. 列表与字典数据操作的核心战场3.1enumerate告别手动索引新手最常见的写法是for i in range(len(lst))然后lst[i]取值。这种写法不仅啰嗦而且在遍历过程中修改列表时容易出错。enumerate直接给你索引和值for i, item in enumerate(lst, start1): print(f第{i}项: {item})start参数可以指定起始编号做报表或者用户界面时特别有用。我见过有人在循环里手动维护一个计数器变量其实一个enumerate就解决了。3.2zip并行遍历的利器zip把多个可迭代对象“拉链式”配对。最常用的场景是同时遍历两个列表names [张三, 李四] scores [85, 92] for name, score in zip(names, scores): print(name, score)但要注意zip在Python 3中返回的是迭代器只能消费一次。如果你需要多次遍历得先转成list。另外当两个列表长度不一致时zip会以短的为准多余的元素被忽略。如果你希望以长的为准用itertools.zip_longest。zip还有一个经典用法是配合*解压list(zip(*matrix))可以实现矩阵转置。这个技巧在处理二维数据时很实用比嵌套循环简洁得多。3.3sorted的key参数比你想的更强大sorted和list.sort的key参数接受一个函数这个函数对每个元素调用一次返回值用来排序。很多人只知道用keylen按长度排序但key可以返回元组实现多级排序data [(张三, 85), (李四, 92), (王五, 85)] sorted_data sorted(data, keylambda x: (-x[1], x[0]))这个例子先按分数降序分数相同按名字升序。-x[1]利用了数字取负后大小关系反转的特性。如果是字符串想降序可以用reverseTrue但那样会同时反转所有排序级别所以多级排序时用取负或者自定义比较函数更灵活。注意key函数只调用一次所以即使函数体复杂一点性能也比在比较函数里反复计算要好。3.4dict.get和setdefault避免KeyError的两种策略get(key, default)在键不存在时返回默认值不会抛异常。这是读取配置或者统计计数时的标配。setdefault(key, default)则是在键不存在时插入默认值并返回。它和get的区别在于会修改字典。典型用法是构建多值字典groups {} for name, dept in employees: groups.setdefault(dept, []).append(name)但要注意setdefault的第二个参数无论键是否存在都会被求值。如果默认值的计算代价很高应该用if key not in d先判断。我见过有人写d.setdefault(k, expensive_function())结果每次调用都执行了expensive_function白白浪费性能。3.5collections.Counter统计计数的正确姿势虽然Counter不是内置函数但它是标准库中最常用的工具之一。统计词频、投票结果、日志级别分布用Counter一行搞定from collections import Counter words text.split() freq Counter(words) print(freq.most_common(10))most_common(n)返回前n个高频元素。Counter还支持加减运算合并两个计数结果时很方便。我处理日志时经常用Counter统计状态码分布比手动循环加字典快得多代码也短。3.6list comprehensionvsmap/filter列表推导式现在基本取代了map和filter的地位可读性更好。但在某些场景下map和filter仍然有优势当你已经有一个现成的函数不需要额外写lambda时。# 列表推导式 squares [x**2 for x in range(10)] # map squares list(map(lambda x: x**2, range(10)))列表推导式更直观而且支持条件过滤[x**2 for x in range(10) if x % 2 0]。我个人的原则是能用列表推导式就用除非逻辑复杂到一行写不下那就老老实实写循环。4. 文件与路径跨平台开发的必修课4.1open的encoding参数不写会出事open()不指定encoding时使用的是系统默认编码。在Windows上通常是GBK在Linux/macOS上是UTF-8。这意味着同一段代码在不同系统上可能一个正常一个乱码。我早期写的一个脚本在本地跑得好好的部署到服务器上读中文文件全是乱码排查了半天才发现是编码问题。with open(data.txt, r, encodingutf-8) as f: content f.read()养成习惯只要涉及文本文件永远显式指定encodingutf-8。如果是二进制文件用rb模式不需要encoding。4.2pathlib.Path比os.path更现代的选择pathlib从Python 3.4引入现在已经是处理路径的推荐方式。它把路径当作对象用/运算符拼接比os.path.join直观得多。from pathlib import Path base Path(/home/user) file base / data / input.csv print(file.suffix) # .csv print(file.stem) # input print(file.parent) # /home/user/dataPath对象还有exists()、is_file()、is_dir()、mkdir()等方法比os.path的函数式接口更符合直觉。读取文件也直接支持file.read_text(encodingutf-8)一行搞定。4.3glob和rglob批量查找文件Path.glob(*.txt)返回当前目录下所有txt文件rglob递归查找子目录。这比os.walk加手动过滤简洁太多。for p in Path(.).rglob(*.log): print(p)但要注意glob返回的是生成器文件特别多时不会一次性占用大量内存。如果需要排序得先转成列表再sorted。4.4shutil文件操作的瑞士军刀shutil.copy、shutil.move、shutil.rmtree是文件管理脚本的常客。copy2会保留元数据修改时间等copy不会。rmtree删除整个目录树用的时候要小心我一般会先打印路径确认再执行。import shutil shutil.copy2(source.txt, backup/source.txt) shutil.rmtree(temp_dir)注意rmtree没有回收站删了就没了。生产环境建议先shutil.move到临时目录确认无误再删。5. 异常处理与调试让代码更健壮5.1try/except/else/finally的完整结构很多人只用try/except忽略了else和finally。else在try块没有异常时执行finally无论是否异常都执行。这个结构在资源清理时特别有用try: f open(data.txt, encodingutf-8) content f.read() except FileNotFoundError: print(文件不存在) else: print(f读取了{len(content)}个字符) finally: f.close()不过实际写文件操作时with语句已经覆盖了finally的清理功能所以finally更多用在锁的释放、连接关闭等场景。5.2raise ... from ...保留异常链在捕获一个异常后抛出另一个异常时用from可以保留原始异常信息try: config json.loads(text) except json.JSONDecodeError as e: raise ValueError(配置文件格式错误) from e这样traceback会同时显示两个异常方便定位根因。不加from的话原始异常会被隐藏调试时容易迷失方向。5.3assert调试利器生产环境慎用assert在调试时很有用但要注意Python的-O优化模式会移除所有assert语句。所以assert只应该用于内部检查不能用于验证用户输入或执行业务逻辑。def calculate_discount(price, rate): assert 0 rate 1, 折扣率必须在0到1之间 return price * rate如果这个函数会被外部调用应该用if not 0 rate 1: raise ValueError(...)代替assert。5.4logging模块比print更专业的调试方式print调试在小型脚本里没问题但项目一大满屏的print输出就没法看了。logging可以分级DEBUG/INFO/WARNING/ERROR、输出到文件、带时间戳和模块名。import logging logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s ) logging.info(程序启动) logging.error(发生错误)我一般在开发时用logging.debug输出详细过程生产环境把级别调到INFO或WARNING这样不用改代码就能控制输出量。6. 函数式编程工具让代码更简洁6.1functools.partial固定部分参数partial可以把一个函数的某些参数固定住生成一个新函数。这在回调场景中特别有用from functools import partial def power(base, exponent): return base ** exponent square partial(power, exponent2) print(square(5)) # 25我处理数据管道时经常用partial把配置参数固定住然后传给map或filter避免写一堆lambda。6.2functools.lru_cache一行代码实现缓存对于计算密集型的纯函数加一个lru_cache装饰器就能缓存结果后续相同参数的调用直接返回缓存值from functools import lru_cache lru_cache(maxsize128) def fib(n): if n 2: return n return fib(n-1) fib(n-2)maxsize控制缓存条目数设为None表示无限制。但要注意被缓存的函数参数必须是可哈希的列表、字典不能直接作为参数。6.3itertools三剑客chain、groupby、isliceitertools.chain把多个可迭代对象串联成一个处理多个来源的数据时很方便from itertools import chain all_items list(chain(list1, list2, list3))groupby按key分组但要求输入已经按key排序否则同一组会被拆成多段。这个坑我踩过后来每次用groupby之前都先sorted。islice对迭代器做切片避免把整个迭代器转成列表。处理大文件时islice可以只取前几行预览不用加载全部内容。6.4operator模块替代lambda的利器operator.itemgetter和operator.attrgetter在排序和取值时比lambda更快也更简洁from operator import itemgetter data [(张三, 85), (李四, 92)] sorted(data, keyitemgetter(1))itemgetter(1)等价于lambda x: x[1]但因为是C实现的性能更好。多级排序可以传多个索引itemgetter(1, 0)。7. 那些容易混淆的函数对比7.1isvsis比较的是对象身份内存地址比较的是值。对于小整数和短字符串Python有缓存机制is可能返回True但这不保证。永远用比较值用is只和None比较。a [1, 2, 3] b [1, 2, 3] print(a b) # True print(a is b) # False7.2copyvsdeepcopycopy.copy创建浅拷贝嵌套的可变对象仍然是共享的。copy.deepcopy递归复制所有层级。处理嵌套字典或列表时如果修改副本会影响原对象那就是浅拷贝的锅。import copy original [[1, 2], [3, 4]] shallow copy.copy(original) deep copy.deepcopy(original) shallow[0][0] 99 print(original[0][0]) # 99被影响了7.3rangevsarangerange是Python内置的返回惰性序列支持整数步长。numpy.arange返回数组支持浮点步长但因为是浮点计算可能出现精度问题导致元素个数不符合预期。需要浮点序列时我一般用numpy.linspace代替arange指定元素个数而不是步长更可控。7.4sortvssortedlist.sort()原地排序返回None。sorted()返回新列表原列表不变。这个区别看似简单但我在代码审查中见过有人写result lst.sort()然后对None做操作报错。记住原地操作的方法通常返回None这是Python的惯例。8. 实际项目中的函数组合技巧8.1 用zip和dict快速构建映射从两个列表构建字典最简洁的方式是dict(zip(keys, values))。这比循环赋值快代码也短。如果values有重复后面的会覆盖前面的。keys [a, b, c] values [1, 2, 3] mapping dict(zip(keys, values))8.2 用any和all做条件判断any在任意元素为真时返回Trueall在所有元素为真时返回True。配合生成器表达式可以写出很简洁的条件判断numbers [2, 4, 6, 8] if all(n % 2 0 for n in numbers): print(全是偶数)这比写循环加标志变量要清晰得多而且短路求值遇到第一个不满足条件的就停止。8.3 用enumerate和zip同时遍历多个列表需要同时遍历索引和多个列表时可以组合使用for i, (name, score) in enumerate(zip(names, scores), 1): print(f{i}. {name}: {score})这种写法在生成报表时特别高效一行代码搞定编号、姓名、分数的对齐输出。8.4 用defaultdict简化分组统计collections.defaultdict在访问不存在的键时自动创建默认值省去了setdefault或if key not in的判断from collections import defaultdict groups defaultdict(list) for name, dept in employees: groups[dept].append(name)defaultdict(list)用于分组defaultdict(int)用于计数defaultdict(set)用于去重分组。这三个模式覆盖了大部分日常需求。9. 我踩过的那些函数坑9.1 可变默认参数这是Python最经典的坑之一def add_item(item, lst[]): lst.append(item) return lst print(add_item(1)) # [1] print(add_item(2)) # [1, 2]不是[2]默认参数在函数定义时求值一次之后所有调用共享同一个列表。正确做法是用None作为默认值在函数体内创建新列表def add_item(item, lstNone): if lst is None: lst [] lst.append(item) return lst9.2 闭包中的变量捕获在循环中创建lambda或函数时它们捕获的是变量本身而不是变量的值funcs [] for i in range(3): funcs.append(lambda: i) print([f() for f in funcs]) # [2, 2, 2]不是[0, 1, 2]解决办法是用默认参数固定当前值lambda ii: i。9.3strip的参数是字符集合前面提过但值得再强调一次。strip(abc)去掉的是首尾所有属于{a, b, c}的字符不是去掉子串abc。这个行为在去除标点时容易误伤。9.4sorted的稳定性Python的sorted是稳定排序相同key的元素保持原有顺序。这意味着你可以多次排序来实现多级排序先按次要key排再按主要key排。但更推荐直接用元组key一次搞定。9.5dict的键顺序Python 3.7中字典保持插入顺序但不要依赖这一点做逻辑判断。如果需要有序字典用collections.OrderedDict虽然现在普通dict也有序了但OrderedDict有额外的方法如move_to_end。10. 如何真正记住这77个函数列清单容易记住难。我自己的方法是按“场景”而不是“函数名”来记忆。比如“我要统计词频”脑子里应该跳出Counter“我要同时遍历两个列表”应该想到zip“我要安全地取字典值”应该用get。把函数和具体问题绑定比死记硬背签名有效得多。另一个方法是读标准库源码。collections、itertools、functools这几个模块的源码都不长读一遍能理解很多函数的设计意图。比如看了Counter的实现就明白为什么它统计快——底层就是字典加get。最后不要试图一次记住所有函数。先掌握最高频的20个写代码时遇到问题再查文档。用着用着剩下的自然就记住了。我到现在也不敢说77个全记得但常用的那三四十个已经成了肌肉记忆这就够了。