找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1531

积分

0

好友

225

主题
发表于 3 天前 | 查看: 10| 回复: 0

如何评估自己的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. 使用maxkey参数查找“最大对象”

常见需求是从对象列表中根据某个属性找到最值项,例如找到分数最高的学生。

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

同样的模式也适用于minsorted函数。关键在于习惯使用key参数来指定排序或比较的依据,而不是在循环内部写一堆if判断。

7. 自定义排序:sortedlambda函数

例如,先按字符串长度排序,长度相同的再按字典序排序。

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. allany:提升条件判断的可读性

使用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)

allany的逻辑简单直接,但它们能有效减少嵌套的if语句,让条件判断的代码更像是“描述业务规则”,从而提升Python代码的可读性和可维护性。

建议你找一个完整的时间段,尝试独立编写这17段代码。不要查看答案,自己动手敲一遍。遇到问题先查阅官方文档或搜索,实在无法解决再回来对照。

如果你能轻松写出这些代码,并且知道如何进一步优化和封装,那么你的Python基本功足以应对大多数业务开发需求。反之,如果某些部分编写起来磕磕绊绊,则需针对性地加强练习,直到将这些“生疏感”完全消除。




上一篇:Python 算法实战:基于日志数据统计 N 日活跃用户与 DAU
下一篇:Nginx反向代理与上游服务SSL双向认证配置指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 17:17 , Processed in 0.227406 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表