1. 为什么我要花时间整理这77个Python函数
刚接触Python那会儿,我最怕的就是写代码时突然卡住——明明逻辑想清楚了,就是不知道用哪个函数来实现。翻官方文档吧,全英文不说,一个函数能给你列出七八个参数,看完更晕。后来带新人,发现大家卡的地方惊人地相似:字符串怎么拼接、列表怎么去重、字典怎么遍历、文件怎么读写,这些最基础的操作反而最容易让人翻车。
这份总结里的77个函数,是我从实际项目里一个个筛出来的。它们覆盖了字符串处理、列表操作、字典管理、文件读写、类型转换、数学计算、日期时间、异常处理这八个最常用的场景。每一个函数我都配了可直接运行的代码示例,标注了容易踩的坑,还补充了参数选择的依据。不管你是刚装好Python环境的新手,还是写了几年代码想查漏补缺的老手,这份清单都能当工具手册用——遇到问题直接翻到对应章节,复制代码改改就能跑。
提示:文中所有代码基于Python 3.10版本测试通过,部分函数在Python 2中行为不同,建议直接使用Python 3.8以上版本。
2. 字符串处理函数:文本操作的18般兵器
字符串操作是Python里最高频的需求,没有之一。我统计过自己写过的脚本,超过40%的代码行都在处理文本。这18个函数吃透了,日常文本处理基本不用再查文档。
2.1 大小写转换与判断类函数
str.upper()、str.lower()、str.title()、str.capitalize()这四个是入门必会。但很多人不知道str.title()会把"hello world"变成"Hello World",而str.capitalize()只把首字母大写变成"Hello world"。做数据清洗时选错函数,输出结果能让你排查半天。
str.isupper()、str.islower()、str.istitle()这三个判断函数经常被忽略。我处理用户输入时习惯先做一轮格式校验,比如要求用户名必须全小写,用if not username.islower()比写正则快得多。注意str.isalpha()判断的是"是否全是字母",空格和数字都会返回False,这个坑我踩过——当时想校验姓名,结果"张 三"因为中间有空格被判定为非法。
text = "python function summary" print(text.upper()) # PYTHON FUNCTION SUMMARY print(text.title()) # Python Function Summary print(text.capitalize()) # Python function summary print("ABC".isupper()) # True print("abc123".islower()) # True(数字不影响判断) print("abc".isalpha()) # True print("abc 123".isalpha()) # False(含空格和数字)2.2 查找替换与分割合并类函数
str.find()和str.index()的区别值得单独说:找不到时find()返回-1,index()直接抛异常。我早期写爬虫解析HTML时用index(),遇到一个页面结构变化就整个脚本崩溃,后来全换成find()配合判断,稳定性立马上来了。
str.replace(old, new, count)的第三个参数count指定替换次数,不传则全部替换。做日志脱敏时,我只想替换前3个出现的手机号,传count=3就搞定,不用写循环。
str.split(sep, maxsplit)和str.join(iterable)是一对反操作。split()不传参数时按任意空白字符分割,传了参数就按指定字符分割。join()的参数必须是可迭代对象,而且里面的元素必须都是字符串——如果列表里有数字,得先map(str, list)转换,这个报错信息"TypeError: sequence item 0: expected str instance, int found"我见过太多次了。
log = "2024-01-15 10:23:45 ERROR User login failed" parts = log.split(" ", 2) # 只分割前两个空格 print(parts) # ['2024-01-15', '10:23:45', 'ERROR User login failed'] # 数字列表转字符串拼接 nums = [1, 2, 3, 4, 5] result = "-".join(map(str, nums)) print(result) # 1-2-3-4-52.3 去除空白与对齐类函数
str.strip()、str.lstrip()、str.rstrip()这三个去除空白的函数,默认去除空格、制表符、换行符。但你可以传入参数指定要去除的字符集,比如str.strip("0")会去掉首尾所有的"0"。注意它去除的是"字符集"而不是"子串"——"hello".strip("hel")的结果是"o",因为它会去掉首尾所有属于{h,e,l}的字符。
str.center(width, fillchar)、str.ljust()、str.rjust()在做命令行输出对齐时特别有用。我写过一个批量处理文件的脚本,用ljust(30)让文件名左对齐,输出日志看起来清爽多了。str.zfill(width)专门用于数字补零,生成订单号时"123".zfill(8)得到"00000123",比手动拼字符串优雅。
filename = "report.pdf" print(filename.ljust(20, ".")) # report.pdf.......... print("42".zfill(6)) # 000042 print(" hello ".strip()) # hello print("www.example.com".strip("wcom.")) # example(去掉首尾属于该集合的字符)2.4 格式化与编码类函数
str.format()和f-string是两种主流格式化方式。f-string从Python 3.6引入后我基本只用它了,写起来直观:f"{name} is {age} years old"。但str.format()在需要动态指定格式模板时仍有优势,比如从配置文件读取模板字符串再填充。
str.encode()和bytes.decode()处理编码问题。读文件时如果遇到"UnicodeDecodeError",八成是编码没指定对。我处理中文文本的习惯是统一用encoding='utf-8',遇到GBK编码的老文件再单独处理。str.encode('utf-8')把字符串转成字节串,网络传输和写二进制文件时必用。
str.startswith()和str.endswith()支持传入元组做多条件判断,这个技巧很少人知道。filename.endswith(('.jpg', '.png', '.gif'))一行代码判断多种图片格式,比写or连接清爽得多。
# 多后缀判断 files = ["a.jpg", "b.png", "c.txt", "d.gif"] images = [f for f in files if f.endswith(('.jpg', '.png', '.gif'))] print(images) # ['a.jpg', 'b.png', 'd.gif'] # 编码转换 text = "中文内容" encoded = text.encode('utf-8') print(encoded) # b'\xe4\xb8\xad\xe6\x96\x87\xe5\x86\x85\xe5\xae\xb9' print(encoded.decode('utf-8')) # 中文内容3. 列表与元组操作函数:数据容器的核心玩法
列表是Python里最灵活的数据结构,这15个函数覆盖了增删改查、排序、统计、推导式等核心操作。元组作为不可变列表,相关操作也放在这一节对比说明。
3.1 增删改查基础操作
list.append(x)在末尾追加单个元素,list.extend(iterable)把可迭代对象里的元素逐个追加,list.insert(i, x)在指定位置插入。这三个的区别用好了能省不少事:合并两个列表用extend()比循环append()快,因为extend()是C语言层面实现的。
删除操作有list.remove(x)、list.pop(i)、del list[i]、list.clear()四种。remove()按值删除,找不到会报ValueError;pop()按索引删除并返回被删元素,不传索引默认删最后一个;del是语句不是函数,可以删除切片;clear()清空整个列表。我处理任务队列时常用pop(0)模拟FIFO,但数据量大时性能差,后来改用collections.deque。
list.index(x)查找元素索引,找不到报ValueError。list.count(x)统计出现次数。这两个函数在数据预处理阶段用得很多,比如统计某个标签出现的频率。
tasks = ["task1", "task2", "task3"] tasks.append("task4") # ['task1', 'task2', 'task3', 'task4'] tasks.extend(["task5", "task6"]) # 追加多个 tasks.insert(0, "urgent") # 头部插入 print(tasks.pop()) # task6(删除并返回) print(tasks.count("task1")) # 13.2 排序与反转的进阶技巧
list.sort(key=None, reverse=False)原地排序,sorted(iterable, key=None, reverse=False)返回新列表。关键在key参数——传入一个函数,排序时按函数返回值比较。我处理用户列表时常用key=lambda u: u['age']按年龄排序,或者key=lambda u: (u['age'], u['name'])多级排序。
list.reverse()原地反转,reversed(seq)返回反转迭代器。注意reversed()返回的不是列表,需要list(reversed(seq))转换。切片seq[::-1]也能反转,但会创建新对象,内存敏感场景慎用。
list.sort()是稳定排序,相同key的元素保持原有顺序。这个特性在做多级排序时很有用:先按次要条件排一次,再按主要条件排一次,最终结果就是主要条件优先、次要条件次之。
users = [ {"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}, {"name": "Charlie", "age": 30} ] # 按年龄升序,年龄相同按姓名降序 users.sort(key=lambda u: (u['age'], u['name']), reverse=False) # 更精细的多级排序 users.sort(key=lambda u: u['name'], reverse=True) users.sort(key=lambda u: u['age']) print(users)3.3 列表推导式与生成器表达式
列表推导式[expr for item in iterable if condition]是Python最优雅的特性之一。我见过有人写十行循环做的事,推导式一行搞定。但要注意可读性——嵌套超过两层就该拆开了。
生成器表达式(expr for item in iterable)和列表推导式语法几乎一样,区别是它返回生成器对象,惰性求值,不占内存。处理大文件时我必用生成器:sum(len(line) for line in open('bigfile.txt')),内存占用几乎为零。
zip(*iterables)把多个可迭代对象"拉链"式配对,enumerate(iterable, start=0)同时获取索引和值。这两个函数在遍历时特别有用,for i, (a, b) in enumerate(zip(list1, list2))能同时拿到索引和两个列表的对应元素。
# 列表推导式:筛选偶数并平方 squares = [x**2 for x in range(10) if x % 2 == 0] print(squares) # [0, 4, 16, 36, 64] # 生成器表达式:内存友好 total = sum(len(line) for line in ["hello", "world", "python"]) print(total) # 16 # zip和enumerate配合 names = ["Alice", "Bob"] scores = [85, 92] for i, (name, score) in enumerate(zip(names, scores), 1): print(f"{i}. {name}: {score}")3.4 元组的不可变特性与解包
元组用圆括号定义,一旦创建不能修改。这个"不可变"特性让它适合做字典的键、函数的返回值、配置常量。tuple(iterable)把其他序列转成元组。
元组解包是Python里极其实用的语法:a, b = (1, 2),first, *rest = [1, 2, 3, 4],*init, last = [1, 2, 3, 4]。函数返回多个值时实际上返回的就是元组,调用方直接解包接收。交换变量a, b = b, a也是元组解包的经典应用。
namedtuple从collections模块导入,创建带字段名的元组子类。访问元素可以用属性名而不是索引,代码可读性大幅提升。我定义配置项时常用Config = namedtuple('Config', ['host', 'port', 'timeout']),比字典更轻量,比普通元组更清晰。
from collections import namedtuple Point = namedtuple('Point', ['x', 'y']) p = Point(10, 20) print(p.x, p.y) # 10 20 print(p[0], p[1]) # 10 20(仍支持索引) # 解包技巧 data = [1, 2, 3, 4, 5] first, *middle, last = data print(first, middle, last) # 1 [2, 3, 4] 54. 字典与集合操作函数:键值对与去重利器
字典和集合底层都是哈希表,查找时间复杂度O(1)。这12个函数覆盖了字典的增删改查、遍历、合并,以及集合的交并差运算。
4.1 字典的创建与访问方式
dict.get(key, default)是访问字典最安全的方式,键不存在时返回默认值而不是报KeyError。我处理API返回的JSON数据时,字段可能缺失,全用get()加默认值,代码健壮性提升一个档次。
dict.setdefault(key, default)在键不存在时设置默认值并返回,存在时直接返回现有值。统计词频时counts[word] = counts.setdefault(word, 0) + 1,或者更简洁地用collections.defaultdict(int)。
dict.update(other)合并字典,相同键会被覆盖。Python 3.9引入了|运算符做字典合并,d1 | d2返回新字典,d1 |= d2原地更新。dict.fromkeys(keys, value)批量创建键值对,初始化配置字典时很方便。
config = {"host": "localhost", "port": 8080} print(config.get("timeout", 30)) # 30(键不存在返回默认值) # 词频统计 words = ["apple", "banana", "apple", "cherry", "banana", "apple"] counts = {} for w in words: counts[w] = counts.get(w, 0) + 1 print(counts) # {'apple': 3, 'banana': 2, 'cherry': 1} # 字典合并 defaults = {"timeout": 30, "retries": 3} user_config = {"timeout": 60} merged = defaults | user_config print(merged) # {'timeout': 60, 'retries': 3}4.2 字典的遍历与视图对象
dict.keys()、dict.values()、dict.items()返回视图对象,不是列表。视图对象是动态的,字典变了它也跟着变。需要列表时用list(d.keys())转换。
遍历字典时for key in d默认遍历键,for key, value in d.items()同时拿键值。我见过有人写for key in d: value = d[key],多一次哈希查找,用items()更高效。
dict.pop(key, default)删除并返回指定键的值,dict.popitem()删除并返回最后一个键值对(Python 3.7+按插入顺序)。dict.clear()清空字典。key in dict判断键是否存在,比dict.has_key()(Python 2遗留)快得多。
scores = {"Alice": 85, "Bob": 92, "Charlie": 78} # 遍历 for name, score in scores.items(): print(f"{name}: {score}") # 安全删除 removed = scores.pop("Bob", None) print(removed) # 92 print(scores) # {'Alice': 85, 'Charlie': 78} # 判断键存在 if "Alice" in scores: print("Alice exists")4.3 集合运算与去重实战
set(iterable)创建集合,自动去重。set.add(x)添加元素,set.remove(x)删除元素(不存在报错),set.discard(x)删除元素(不存在不报错)。set.pop()随机删除并返回一个元素。
集合运算:a | b并集,a & b交集,a - b差集,a ^ b对称差集。对应方法union()、intersection()、difference()、symmetric_difference()。判断子集用a.issubset(b)或a <= b,超集用a.issuperset(b)或a >= b。
去重是集合最常见的用途。list(set(my_list))一行去重,但会打乱顺序。需要保持顺序时用dict.fromkeys(my_list).keys(),Python 3.7+字典有序,这个方法既去重又保序。
# 去重保序 nums = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3] unique = list(dict.fromkeys(nums)) print(unique) # [3, 1, 4, 5, 9, 2, 6] # 集合运算 a = {1, 2, 3, 4} b = {3, 4, 5, 6} print(a & b) # {3, 4} print(a | b) # {1, 2, 3, 4, 5, 6} print(a - b) # {1, 2} print(a ^ b) # {1, 2, 5, 6}5. 文件与异常处理函数:让脚本稳定运行
文件读写和异常处理是脚本从"能跑"到"稳定"的关键。这10个函数覆盖了文件打开、读写、路径操作、异常捕获、上下文管理。
5.1 文件打开模式与读写操作
open(file, mode, encoding)是文件操作的入口。mode参数:'r'读,'w'写(覆盖),'a'追加,'x'独占创建,'b'二进制,'+'读写。组合使用如'rb'二进制读,'w+'读写。
file.read(size)读指定字节数,不传读全部。file.readline()读一行,file.readlines()读所有行返回列表。大文件不要用read()或readlines(),内存扛不住,用for line in file逐行迭代。
file.write(string)写入字符串,file.writelines(lines)写入字符串列表。注意write()不会自动加换行符,需要手动加\n。写文件后必须file.close(),否则数据可能没刷到磁盘。
# 安全写法:with自动关闭 with open("data.txt", "w", encoding="utf-8") as f: f.write("第一行\n") f.writelines(["第二行\n", "第三行\n"]) # 逐行读取大文件 with open("data.txt", "r", encoding="utf-8") as f: for line in f: print(line.strip())5.2 路径操作与文件检测
os.path.exists(path)判断路径是否存在,os.path.isfile()判断是否文件,os.path.isdir()判断是否目录。os.path.join(a, b)拼接路径,自动处理不同操作系统的分隔符,比手动拼/或\可靠。
os.path.getsize(path)获取文件大小(字节),os.path.getmtime(path)获取修改时间戳。os.path.splitext(path)分离文件名和扩展名,返回元组。os.path.basename()和os.path.dirname()分别取文件名和目录名。
pathlib模块是Python 3.4+推荐的路径操作方式,面向对象,比os.path更直观。Path("data") / "file.txt"用/运算符拼接路径,p.exists()、p.is_file()、p.suffix等属性和方法用起来很顺手。
from pathlib import Path p = Path("data") / "file.txt" print(p) # data/file.txt print(p.suffix) # .txt print(p.stem) # file print(p.parent) # data # 创建目录 p.parent.mkdir(parents=True, exist_ok=True)5.3 异常捕获与自定义异常
try/except/else/finally是异常处理的标准结构。try块放可能出错的代码,except捕获指定异常,else在无异常时执行,finally无论是否异常都执行。我清理资源时必用finally,比如关闭数据库连接。
except Exception as e捕获所有异常,但生产环境不建议裸捕,会掩盖真正的bug。应该按具体异常类型分别处理:FileNotFoundError、ValueError、KeyError、TypeError等。
raise主动抛出异常,raise ValueError("invalid input")。自定义异常继承Exception类,class MyError(Exception): pass。assert condition, message断言,条件为假时抛AssertionError,用于调试和参数校验。
def read_config(path): try: with open(path, "r", encoding="utf-8") as f: return f.read() except FileNotFoundError: print(f"配置文件 {path} 不存在,使用默认配置") return "" except PermissionError: print(f"没有权限读取 {path}") return "" finally: print("读取操作完成") # 自定义异常 class ConfigError(Exception): pass def validate(config): if "host" not in config: raise ConfigError("缺少host配置")6. 类型转换与数学计算函数:数据处理的基石
类型转换和数学计算是每个脚本都会用到的。这12个函数覆盖了基础类型转换、数值判断、数学运算、随机数生成。
6.1 基础类型转换函数
int(x)转整数,float(x)转浮点,str(x)转字符串,bool(x)转布尔。int("123")得到123,int("12.5")会报ValueError,需要先float()再int()。int(3.9)截断小数部分得到3,不是四舍五入。
list(iterable)、tuple(iterable)、set(iterable)、dict(iterable)把可迭代对象转成对应容器。dict()要求可迭代对象里每个元素是键值对,比如dict([("a", 1), ("b", 2)])。
chr(i)和ord(c)互转ASCII码。chr(65)得到"A",ord("A")得到65。hex()、oct()、bin()分别转十六进制、八进制、二进制字符串。round(number, ndigits)四舍五入,注意Python的银行家舍入规则:round(2.5)得到2不是3。
print(int("123")) # 123 print(float("3.14")) # 3.14 print(str(42)) # "42" print(bool(0)) # False print(bool("")) # False print(bool([])) # False print(bool("False")) # True(非空字符串都是True) print(chr(65)) # A print(ord("A")) # 65 print(hex(255)) # 0xff print(round(2.5)) # 2(银行家舍入) print(round(3.5)) # 46.2 数值判断与数学运算
abs(x)绝对值,max(iterable)最大值,min(iterable)最小值,sum(iterable)求和。max()和min()支持key参数,max(words, key=len)返回最长的单词。
pow(base, exp)幂运算,等价于base ** exp。divmod(a, b)同时返回商和余数,divmod(17, 5)得到(3, 2)。math模块提供更多数学函数:math.sqrt()平方根,math.ceil()向上取整,math.floor()向下取整,math.pi圆周率,math.e自然常数。
math.isclose(a, b)判断两个浮点数是否接近,避免直接用==比较浮点数。math.gcd(a, b)最大公约数,math.factorial(n)阶乘。random模块的random()、randint()、choice()、shuffle()、sample()覆盖了随机数需求。
import math import random print(abs(-5)) # 5 print(max([3, 1, 4, 1, 5])) # 5 print(sum([1, 2, 3, 4])) # 10 print(pow(2, 10)) # 1024 print(divmod(17, 5)) # (3, 2) print(math.sqrt(16)) # 4.0 print(math.ceil(3.2)) # 4 print(math.floor(3.8)) # 3 print(math.gcd(12, 18)) # 6 print(random.randint(1, 10)) # 1到10随机整数 print(random.choice(["a", "b"])) # 随机选一个 items = [1, 2, 3, 4, 5] random.shuffle(items) # 原地打乱 print(random.sample(items, 3)) # 随机选3个不重复6.3 日期时间处理函数
datetime模块是日期时间处理的核心。datetime.now()当前时间,datetime.strptime(string, format)字符串转日期,datetime.strftime(format)日期转字符串。格式化符号:%Y四位年,%m月,%d日,%H时,%M分,%S秒。
timedelta表示时间间隔,datetime + timedelta(days=1)得到明天。计算两个日期差:date1 - date2返回timedelta对象,.days属性拿天数。
time.time()返回Unix时间戳(秒),time.sleep(seconds)暂停执行。time.perf_counter()高精度计时,测代码性能时用这个而不是time.time()。
from datetime import datetime, timedelta now = datetime.now() print(now.strftime("%Y-%m-%d %H:%M:%S")) # 2024-01-15 10:30:00 # 字符串转日期 dt = datetime.strptime("2024-01-15", "%Y-%m-%d") print(dt) # 2024-01-15 00:00:00 # 日期计算 tomorrow = now + timedelta(days=1) diff = tomorrow - now print(diff.days) # 1 # 性能计时 import time start = time.perf_counter() # ... 执行代码 ... end = time.perf_counter() print(f"耗时: {end - start:.4f}秒")7. 高阶函数与迭代工具:写出更Pythonic的代码
高阶函数和itertools模块能让代码更简洁高效。这10个函数覆盖了map、filter、reduce、lambda、itertools常用工具。
7.1 map、filter、reduce三剑客
map(function, iterable)对每个元素应用函数,返回迭代器。list(map(str, [1, 2, 3]))得到['1', '2', '3']。filter(function, iterable)过滤元素,函数返回True的保留。reduce(function, iterable)累积计算,需要从functools导入。
这三个函数配合lambda使用很常见,但列表推导式往往更易读。[str(x) for x in [1,2,3]]比map(str, [1,2,3])直观。我现在的习惯是:简单转换用推导式,复杂逻辑用map/filter配合具名函数。
from functools import reduce nums = [1, 2, 3, 4, 5] # map:每个元素平方 squares = list(map(lambda x: x**2, nums)) print(squares) # [1, 4, 9, 16, 25] # filter:筛选偶数 evens = list(filter(lambda x: x % 2 == 0, nums)) print(evens) # [2, 4] # reduce:累加 total = reduce(lambda a, b: a + b, nums) print(total) # 157.2 itertools高效迭代工具
itertools.count(start, step)无限计数器,itertools.cycle(iterable)无限循环,itertools.repeat(elem, n)重复元素。这三个配合zip或islice使用。
itertools.chain(*iterables)串联多个可迭代对象,itertools.islice(iterable, start, stop)切片迭代器。itertools.groupby(iterable, key)分组,要求先按key排序。itertools.product(*iterables)笛卡尔积,itertools.permutations()排列,itertools.combinations()组合。
import itertools # 串联 combined = list(itertools.chain([1, 2], [3, 4], [5])) print(combined) # [1, 2, 3, 4, 5] # 笛卡尔积 colors = ["red", "green"] sizes = ["S", "M"] for c, s in itertools.product(colors, sizes): print(c, s) # red S, red M, green S, green M # 组合 print(list(itertools.combinations([1, 2, 3], 2))) # [(1, 2), (1, 3), (2, 3)]7.3 lambda与闭包实战
lambda arguments: expression创建匿名函数,适合简单逻辑。sorted(data, key=lambda x: x[1])按第二个元素排序。lambda只能写一个表达式,复杂逻辑还是用def。
闭包是函数内部定义函数并返回,内部函数引用外部变量。def outer(x): def inner(y): return x + y; return inner,add5 = outer(5),add5(3)得到8。闭包在装饰器和回调函数里用得很多。
functools.partial(func, *args)固定部分参数,返回新函数。partial(int, base=2)创建一个二进制转十进制的函数,binary_to_int("1010")得到10。
from functools import partial # lambda排序 data = [("apple", 3), ("banana", 1), ("cherry", 2)] data.sort(key=lambda x: x[1]) print(data) # [('banana', 1), ('cherry', 2), ('apple', 3)] # 闭包 def multiplier(n): def multiply(x): return x * n return multiply double = multiplier(2) triple = multiplier(3) print(double(5)) # 10 print(triple(5)) # 15 # partial binary_to_int = partial(int, base=2) print(binary_to_int("1010")) # 108. 常见问题与排查技巧实录
写了这么多年Python,有些错误反复出现。我把最典型的几个整理成速查表,遇到问题直接对照排查。
8.1 函数使用高频错误速查表
| 错误信息 | 常见原因 | 解决方法 |
|---|---|---|
TypeError: 'int' object is not iterable | 对整数用了for循环或list() | 检查变量类型,用range()包裹 |
KeyError: 'xxx' | 字典访问不存在的键 | 用dict.get(key, default) |
IndexError: list index out of range | 列表索引越界 | 检查len(),用切片或try/except |
ValueError: invalid literal for int() | 字符串含非数字字符 | 先strip()再判断isdigit() |
UnicodeDecodeError | 文件编码不匹配 | 指定encoding='utf-8'或'gbk' |
AttributeError: 'NoneType' object has no attribute | 函数返回None被当对象用 | 检查函数返回值,加判空 |
IndentationError | 缩进不一致 | 统一用4个空格,别混用Tab |
8.2 参数传递的坑与经验
Python的参数传递是"传对象引用"。可变对象(列表、字典)传进函数后修改会影响原对象,不可变对象(数字、字符串、元组)不会。这个特性导致过很多bug:函数里list.append()了元素,调用方发现原列表也变了。
默认参数只在函数定义时求值一次,所以不要用可变对象做默认参数。def add(item, lst=[])是经典错误,多次调用会共享同一个列表。正确写法是def add(item, lst=None): if lst is None: lst = []。
# 错误示范 def add_item(item, lst=[]): lst.append(item) return lst print(add_item(1)) # [1] print(add_item(2)) # [1, 2] 不是[2]! # 正确写法 def add_item_safe(item, lst=None): if lst is None: lst = [] lst.append(item) return lst print(add_item_safe(1)) # [1] print(add_item_safe(2)) # [2]8.3 性能优化的几个实用技巧
字符串拼接用join()而不是+。+每次创建新字符串,循环里拼接是O(n²),join()是O(n)。我处理十万行日志时,用join()比+快了近百倍。
列表推导式比for循环append快,因为推导式在C层面优化过。但推导式里不要做复杂计算,可读性优先。
用set做成员判断比list快得多。if x in big_list是O(n),if x in big_set是O(1)。数据量大时先转set。
collections模块的Counter、defaultdict、deque比手动实现高效。Counter统计词频一行搞定,deque做队列比list.pop(0)快。
from collections import Counter, deque # 词频统计 words = ["a", "b", "a", "c", "b", "a"] counter = Counter(words) print(counter.most_common(2)) # [('a', 3), ('b', 2)] # 高效队列 queue = deque([1, 2, 3]) queue.append(4) # 右端添加 queue.appendleft(0) # 左端添加 print(queue.popleft()) # 0(左端弹出)8.4 调试与排查的实战心得
print()大法虽然土但有效。我习惯在关键分支加print(f"[DEBUG] variable={variable}"),用f-string带上变量名,排查完再删。
assert断言用于开发阶段校验假设。assert len(data) > 0, "数据为空",条件不满足时抛AssertionError并显示消息。生产环境可以用-O参数禁用断言。
logging模块比print更适合生产环境。logging.basicConfig(level=logging.INFO)配置日志级别,logging.info()、logging.error()分级输出。日志可以写文件、带时间戳、按级别过滤。
pdb调试器:import pdb; pdb.set_trace()在代码里打断点,运行时进入交互调试。命令:n下一行,s进入函数,c继续,p variable打印变量,q退出。VS Code和PyCharm都有图形化调试器,打断点、看变量、单步执行更直观。
import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) def process(data): logging.info(f"开始处理,数据量: {len(data)}") try: result = [x * 2 for x in data] logging.info(f"处理完成,结果量: {len(result)}") return result except Exception as e: logging.error(f"处理失败: {e}", exc_info=True) raise8.5 虚拟环境与依赖管理
python -m venv myenv创建虚拟环境,source myenv/bin/activate(Linux/Mac)或myenv\Scripts\activate(Windows)激活。虚拟环境隔离项目依赖,避免版本冲突。
pip install package安装包,pip freeze > requirements.txt导出依赖列表,pip install -r requirements.txt批量安装。国内下载慢可以指定镜像源,但注意选择合规稳定的源。
pip list查看已安装包,pip show package查看包详情,pip uninstall package卸载。pip install --upgrade package升级包。
注意:虚拟环境目录不要提交到Git,在
.gitignore里加上venv/或myenv/。依赖列表requirements.txt要提交,方便别人复现环境。
9. 我个人在实际操作中的体会
这77个函数我用了五年多,最大的感受是:基础函数用熟了,写代码的速度和代码质量会有质的飞跃。刚开始我也追求各种高级特性,后来发现80%的问题用这77个函数就能解决,剩下的20%再查文档也来得及。
给新手的建议是:不要死记硬背,把这篇文章收藏起来,写代码时遇到相关场景就翻出来看看。用着用着就形成肌肉记忆了。我当初把str、list、dict的常用方法各抄了十遍,现在闭着眼都能写出来。
最后分享一个我自己的习惯:每学一个新函数,就在REPL里试三种情况——正常输入、边界输入、错误输入。比如学split(),试"a,b,c".split(",")、"".split(",")、"a,b".split()。这样能快速摸清函数的脾气,比看文档印象深得多。