如何评估自己的Python真实水平?很多开发者不想再练习“打印九九乘法表”这类过于基础的题目,而是希望检验更贴近实际工作的编程能力。
为此,我们整理了17段精炼的短代码。每一段都不复杂,却涵盖了日常项目开发中最常用的一圈核心“基本功”。你可以尝试逐段独立编写,再与下文提供的范例进行对比,查漏补缺。
1. 列表推导式:一行完成过滤与映射
常见需求:从一个数字列表中筛选出偶数,并计算它们的平方。
nums = [1, 2, 3, 4, 5, 6]
# 要求:保留偶数并平方
result = [x * x for x in nums if x % 2 == 0]
print(result) # [4, 16, 36]
能否流畅地写出这一行,反映了你对“条件过滤+数据映射”的熟悉程度。如果你的第一反应是使用循环:
result = []
for x in nums:
if x % 2 == 0:
result.append(x * x)
虽然功能正确,但代码的“Python风格”会稍显不足。在日常的Python数据处理中,应多尝试使用列表推导式来简化代码。
2. 字符统计:dict.get()的巧妙用法
统计一段文本中每个字符出现的次数。
text = "hello world"
counter = {}
for ch in text:
counter[ch] = counter.get(ch, 0) + 1
print(counter)
# 输出:{'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}
关键在于dict.get(key, default)方法,它避免了先判断键是否存在的if语句,直接在一行内完成计数逻辑。这种模式在日志分析、接口QPS统计等场景中极为常见。更进阶的写法是使用collections.Counter,下文会单独介绍。
3. 去重并保留原始顺序
直接使用list(set(lst))会丢失列表的原始顺序,这在某些业务场景下可能导致问题。例如,对一个用户列表去重,同时需要保留首次出现的顺序。
users = ["tom", "jack", "tom", "lucy", "jack"]
seen = set()
result = []
for u in users:
if u not in seen:
seen.add(u)
result.append(u)
print(result) # ['tom', 'jack', 'lucy']
这段代码考察了三个知识点:利用集合(set)实现O(1)复杂度的成员检查、列表(list)的顺序保持特性,以及基本的流程控制能力。
4. 字符串切片:反转与子串截取
切片是Python中语法细节容易遗忘的部分之一。
s = "abcdefg"
print(s[::-1]) # gfedcba 反转字符串
print(s[1:4]) # bcd 左闭右开区间
print(s[:3]) # abc 省略起始索引
print(s[3:]) # defg 省略结束索引
最容易出错的两点是:切片区间为[start, end),即不包含结束索引;步长(step)为负数时可以从后向前切片。熟练掌握切片,在处理日志字符串、URL解析等任务时会更加得心应手。
5. 使用zip合并列表为字典
将一组键(keys)和一组值(values)合并成一个字典。
keys = ["name", "age", "city"]
values = ["Tom", 18, "Beijing"]
data = dict(zip(keys, values))
print(data) # {'name': 'Tom', 'age': 18, 'city': 'Beijing'}
如果你的写法仍是手动循环索引,代码就显得不够简洁:
data = {}
for i in range(len(keys)):
data[keys[i]] = values[i]
熟练使用zip可以大幅提升代码的简洁性和可读性。
6. 使用max的key参数查找“最大对象”
常见需求是从对象列表中根据某个属性找到最值项,例如找到分数最高的学生。
students = [
{"name": "Tom", "score": 88},
{"name": "Jack", "score": 92},
{"name": "Lucy", "score": 90},
]
top = max(students, key=lambda s: s["score"])
print(top) # {'name': 'Jack', 'score': 92}
print(top["name"]) # Jack
同样的模式也适用于min和sorted函数。关键在于习惯使用key参数来指定排序或比较的依据,而不是在循环内部写一堆if判断。
7. 自定义排序:sorted与lambda函数
例如,先按字符串长度排序,长度相同的再按字典序排序。
words = ["python", "go", "java", "rust", "c"]
result = sorted(words, key=lambda w: (len(w), w))
print(result) # ['c', 'go', 'java', 'rust', 'python']
两个要点:sorted返回一个新列表,原列表不变;key函数可以返回一个元组,实现多级排序,这类似于SQL中的ORDER BY length(name), name。
8. 文件读取:with open与基础统计
统计一个文本文件的总行数和单词数。
from pathlib import Path
path = Path("sample.txt")
line_count = 0
word_count = 0
with path.open("r", encoding="utf-8") as f:
for line in f:
line_count += 1
word_count += len(line.split())
print("行数:", line_count)
print("单词数:", word_count)
这里考察几个良好习惯:是否使用with语句进行上下文管理以自动关闭文件;是否记得指定正确的文件编码(如utf-8);是否采用逐行迭代的方式处理大文件,避免一次性将全部内容读入内存。
9. try/except:优雅处理异常
将字符串转换为整数,转换失败时返回一个默认值。
def to_int(s: str, default: int = 0) -> int:
try:
return int(s)
except ValueError as e:
# 在实际开发中,这里应记录日志
print(f"转换失败: {s}, 原因: {e}")
return default
print(to_int("123")) # 123
print(to_int("abc", -1)) # -1
考察点:了解ValueError异常类型;避免使用裸的except:(会捕获所有异常,包括KeyboardInterrupt等);在异常处理中记录相关信息,而不是静默忽略错误。
10. 函数参数:*args与**kwargs
编写一个简单的日志函数,支持指定日志级别、任意数量的消息内容以及额外的键值对信息。
from datetime import datetime
def log(*args, level="INFO", **kwargs):
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
msg = " ".join(str(a) for a in args)
extra = " ".join(f"{k}={v}" for k, v in kwargs.items())
line = f"[{ts}] [{level}] {msg}"
if extra:
line += " | " + extra
print(line)
log("用户登录", user_id=123)
log("支付失败", "余额不足", level="WARN", order_id=456)
如果能独立写出这个函数,说明你对Python函数参数的几种形式已经掌握得比较熟练。
11. 编写生成器:按需生成数据
例如,生成一个无限的斐波那契数列,并按需获取前N个数。
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
g = fib()
for _ in range(10):
print(next(g), end=" ")
# 输出: 0 1 1 2 3 5 8 13 21 34
关键点:理解yield的语义;生成器是惰性求值的,只在需要时计算下一个值;能在适合使用列表和适合使用生成器的场景之间做出选择。这在处理日志流、大文件或网络数据流时非常有用。
12. 列表“拍平”:处理嵌套结构
将一个嵌套列表(仅一层嵌套)展开成一个扁平列表。
nested = [[1, 2], [3, 4, 5], [], [6]]
flat = [x for sub in nested for x in sub]
print(flat) # [1, 2, 3, 4, 5, 6]
这个嵌套的列表推导式初看可能有些绕,将其展开为普通循环就清晰了:
flat = []
for sub in nested:
for x in sub:
flat.append(x)
能正确写出嵌套列表推导式,表明你对循环的嵌套顺序理解到位。
13. enumerate:同时获取索引与值
在遍历列表时同时获取元素索引(例如打印带编号的列表)。
names = ["Tom", "Jack", "Lucy"]
for idx, name in enumerate(names, start=1):
print(f"{idx}. {name}")
考察点:不再需要手动维护一个计数器变量i;了解enumerate(iterable, start=1)中start参数的用法,可以指定索引的起始值。
14. 基础类定义:__init__与__repr__
定义一个简单的用户类,并实现友好的打印输出。
class User:
def __init__(self, name: str, age: int, active: bool = True):
self.name = name
self.age = age
self.active = active
def __repr__(self) -> str:
return f"User(name={self.name!r}, age={self.age}, active={self.active})"
u = User("Tom", 18)
print(u) # User(name='Tom', age=18, active=True)
这里主要培养三个意识:构造函数__init__应保持简洁,主要职责是初始化属性;实现__repr__方法对调试极为友好;属性命名应清晰明确,避免过度缩写。
15. 简易装饰器:为函数添加计时功能
为函数执行添加计时逻辑,这是一个非常实用的装饰器。
import time
from functools import wraps
def timeit(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return func(*args, **kwargs)
finally:
end = time.perf_counter()
print(f"{func.__name__} 耗时 {end - start:.4f} 秒")
return wrapper
@timeit
def slow_add(a, b):
time.sleep(0.5)
return a + b
print(slow_add(3, 4))
考察点:装饰器的基本结构;使用functools.wraps来保留原函数的元信息(如名称、文档字符串);将计时逻辑放在finally块中,确保即使函数抛出异常,耗时也会被打印。
16. collections.Counter:简化的词频统计
回顾第2点中手写的计数器,使用标准库的Counter可以更简洁。
from collections import Counter
text = "today is a good day and today we learn python"
words = text.split()
counter = Counter(words)
print(counter)
print(counter.most_common(3)) # 出现频率最高的3个单词
关键点:Counter(iterable)可直接对可迭代对象进行计数;most_common(n)方法能方便地获取出现频率最高的前n个元素。这在快速进行日志分析或生成简单报表时非常高效。
17. all与any:提升条件判断的可读性
使用all检查密码是否满足所有规则,比一连串的and操作更清晰。
pwd = "Abc12345"
rules = [
lambda s: len(s) >= 8,
lambda s: any(c.isdigit() for c in s),
lambda s: any(c.islower() for c in s),
lambda s: any(c.isupper() for c in s),
]
ok = all(rule(pwd) for rule in rules)
print("密码合法:", ok)
使用any检查列表中是否存在负数。
nums = [1, 2, 3, -1]
has_negative = any(x < 0 for x in nums)
print("有负数吗:", has_negative)
all和any的逻辑简单直接,但它们能有效减少嵌套的if语句,让条件判断的代码更像是“描述业务规则”,从而提升Python代码的可读性和可维护性。
建议你找一个完整的时间段,尝试独立编写这17段代码。不要查看答案,自己动手敲一遍。遇到问题先查阅官方文档或搜索,实在无法解决再回来对照。
如果你能轻松写出这些代码,并且知道如何进一步优化和封装,那么你的Python基本功足以应对大多数业务开发需求。反之,如果某些部分编写起来磕磕绊绊,则需针对性地加强练习,直到将这些“生疏感”完全消除。