1. 项目概述:为什么你需要这份Python练手题集?

如果你正在学习Python,或者已经入门但感觉基础不牢、写代码时思路卡壳,那你来对地方了。这份精心整理的25道Python练手题,就是为你准备的“编程健身房”。它不是那种罗列一堆语法知识点的教科书,而是直接把你拉到“实战场”上,通过解决一个个具体、有趣的问题,来真正内化Python的核心语法、数据结构与编程思想。

我见过太多初学者,看教程时觉得都懂,一打开编辑器就大脑一片空白。问题出在哪里?缺乏有效的、循序渐进的刻意练习。编程是一门实践的手艺,光看不练永远学不会。这份题集的设计初衷,就是填补“知道”和“会做”之间的鸿沟。题目覆盖了从基础数据类型操作、流程控制,到函数、列表推导式、文件处理等关键知识点,难度呈阶梯式上升。更重要的是,每一道题我都附上了详细的解题思路和代码答案,并会重点讲解其中的“坑点”和“优雅写法”。你可以先自己尝试,卡住了再看答案和解析,这个过程本身就是最高效的学习方式。

无论你是准备面试、巩固基础,还是想找点有趣的编程挑战,这份题集都能让你在动手实践中,感受到Python的简洁与强大,逐步建立起解决实际问题的信心和能力。收藏起来,每天刷几道,一个月后你再看自己的代码,会有脱胎换骨的感觉。

2. 题目设计与解题思路全解析

2.1 题目难度阶梯与知识点映射

一份好的练习题集,绝不是题目的简单堆砌。我这25道题是经过精心设计的,遵循了“温故知新、循序渐进”的原则。我将它们大致分为四个阶段:

第一阶段:基础热身(第1-6题) 这个阶段的题目目标是唤醒你对Python最基本语法的肌肉记忆。重点考察变量、基本数据类型(字符串、整数)、运算符和最简单的条件判断。例如,交换两个变量的值、判断闰年、数字求和等。这些题目看似简单,但正是构建复杂程序的基石。我特别设计了一些“陷阱”,比如闰年的判断规则(能被4整除但不能被100整除,或者能被400整除),就是为了让你严谨地理解 and or 的逻辑。

第二阶段:核心数据结构操作(第7-14题) 列表、字典、元组和集合是Python的“四大金刚”。这个阶段的题目将深度操练这些数据结构。你会遇到列表排序(区分 sort() sorted() )、列表去重(利用集合的特性)、字典的合并与统计等经典问题。例如,“统计字符串中每个字符出现的次数”一题,就是字典的绝佳应用场景。通过这类练习,你会深刻理解为什么说“选择合适的数据结构,问题就解决了一半”。

第三阶段:函数与算法初探(第15-20题) 当你能熟练操作数据后,就需要学习如何用函数来组织代码,并接触一些基础的算法思想。这个阶段会涉及递归(如斐波那契数列)、匿名函数 lambda map/filter/reduce 等高阶函数,以及简单的查找和排序算法思想(如冒泡排序)。编写一个判断素数的函数,能让你练习函数的定义、参数传递和返回值,同时理解算法效率(只需遍历到平方根即可)。

第四阶段:综合应用与文件处理(第21-25题) 最后几道题模拟了更真实的微型项目场景。你会综合运用之前所学,来处理文件读写、数据解析等任务。比如,从文本文件中读取数据并计算统计信息,或者实现一个简单的通讯录管理功能。这能让你体会到如何将零散的知识点串联起来,解决一个完整的小问题,为日后开发更复杂的程序打下基础。

2.2 通用解题方法论与避坑指南

在动手写代码之前,掌握正确的解题思路比盲目敲键盘更重要。这里我分享一套通用的解题流程,并指出新手最容易踩的“坑”。

第一步:彻底理解问题 不要急于编码。仔细阅读题目描述,用自己的话复述一遍需求。明确输入是什么(格式、类型、范围),输出又是什么。例如,题目说“输入一个字符串”,你要立刻想到:字符串里会不会有空格?会不会是空字符串?这些边界情况决定了你代码的健壮性。

第二步:设计算法与数据结构 在脑子里或纸上画一画解决问题的步骤。先用人能理解的逻辑描述出来。思考用什么数据结构来存储中间结果最方便?是列表、字典还是集合?这个过程能帮你厘清思路,避免边写边改,代码越写越乱。

第三步:编写代码与模块化 将你的算法步骤转化为代码。遵循“先搭骨架,再填血肉”的原则。先写出主干逻辑,比如循环和判断结构,然后再补充细节。一个重要的技巧是:及时将一段功能独立的代码封装成函数。即使题目没要求,这也是一种极好的练习,能让你的代码更清晰、更易复用。

第四步:测试与调试 用不同的输入测试你的代码,特别是边界情况。比如,对数字进行操作的题目,要测试正数、负数、零;处理列表的题目,要测试空列表、只有一个元素的列表。Python的交互式环境(REPL)和 print() 函数是你最好的调试工具,善用它们来查看程序运行过程中变量的值。

避坑指南1:可变对象的陷阱 这是Python新手最容易出错的地方之一。列表和字典是可变对象。当你写 new_list = old_list 时,你并没有创建一个新的列表,而是创建了一个指向同一块内存地址的新引用。修改 new_list 会导致 old_list 也一起被修改。正确的复制方法是使用 list() 构造函数、切片 [:] copy 模块的 deepcopy

避坑指南2:迭代时修改容器 在遍历一个列表或字典的同时,又去删除或增加它的元素,这会导致不可预知的行为(比如跳过了某些元素或引发运行时错误)。安全的做法是,先创建一个你要修改元素的副本,或者记录下需要删除的索引或键,在迭代完成后再进行统一操作。

3. 精选题目详解与代码实现

下面,我将挑选其中最具代表性的10道题目,进行超详细的拆解。不仅给出答案,更会剖析多种解法及其优劣,并穿插大量实战中积累的技巧。

3.1 第2题:判断闰年

题目 :输入一个年份,判断它是否是闰年。

详细解析 : 闰年的规则是:能被4整除但不能被100整除,或者能被400整除。很多新手会写成一系列嵌套的 if-else ,其实用逻辑运算符一行就能优雅解决。

代码实现

def is_leap_year(year):
    """
    判断是否为闰年
    :param year: 整数年份
    :return: True 如果是闰年,否则 False
    """
    # 核心逻辑:使用逻辑运算符组合条件
    return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)

# 测试用例
test_years = [1900, 2000, 2024, 2025, 2100]
for y in test_years:
    print(f"{y}: {is_leap_year(y)}")

输出

1900: False
2000: True
2024: True
2025: False
2100: False

关键点

  1. 运算符优先级 and 的优先级高于 or ,所以 (year % 4 == 0 and year % 100 != 0) 作为一个整体先计算。为了绝对清晰,加上括号是个好习惯。
  2. 函数化 :即使题目没要求,也养成将功能封装成函数的习惯。这提高了代码的可读性和可测试性。
  3. 测试 :一定要用多个边界值测试,特别是像1900(能被100整除但不能被400整除,不是闰年)和2000(能被400整除,是闰年)这样的关键案例。

3.2 第8题:列表去重并保持原顺序

题目 :给定一个可能包含重复元素的列表,去除重复元素,并尽可能保持剩余元素的原始顺序。

详细解析 : 这是一个非常经典的面试题。最简单的方法是使用 set ,但集合是无序的,会打乱原顺序。我们的目标是既要去重,又要保序。

方法一:使用字典(Python 3.7+) 从Python 3.7开始,字典会保持键的插入顺序。我们可以利用这一点。

def remove_duplicates_ordered(lst):
    """
    使用字典保持顺序去重
    """
    # 使用 fromkeys,字典的键是唯一的,且按首次出现顺序排列
    return list(dict.fromkeys(lst))

# 示例
original_list = [3, 2, 1, 2, 4, 3, 5]
result = remove_duplicates_ordered(original_list)
print(f"原列表: {original_list}")
print(f"去重后: {result}")

方法二:使用循环判断(通用方法) 如果考虑兼容更早的Python版本,或者想明确展示算法逻辑,可以使用一个辅助列表。

def remove_duplicates_ordered_v2(lst):
    """
    使用循环和辅助列表去重
    """
    seen = [] # 用于存放已出现的元素
    for item in lst:
        if item not in seen: # 如果元素还没出现过
            seen.append(item) # 添加到已见列表
    return seen

方法三:使用列表推导式与集合(简洁但需注意)

def remove_duplicates_ordered_v3(lst):
    """
    使用集合和列表推导式(Python 3.6+ 可保序,但原理依赖字典)
    """
    seen = set()
    # 利用集合的in操作是O(1)的特性提高效率
    return [x for x in lst if not (x in seen or seen.add(x))]

这个方法比较“炫技”。 seen.add(x) 总是返回 None ,所以 not (x in seen or seen.add(x)) 这个逻辑等价于:如果x不在seen中,就执行 seen.add(x) 并将其加入结果列表。它高效且简洁,但可读性稍差。

性能与选择

  • 对于中小型列表,三种方法差异不大。 dict.fromkeys() 方法最Pythonic,也最推荐。
  • 如果列表非常大,方法三(使用集合检查)在平均情况下时间复杂度更优(O(n)),因为集合的成员检查是O(1),而列表的 in 操作是O(n)。
  • 核心技巧 :在面试或实际编码中,优先选择 list(dict.fromkeys(lst)) ,因为它清晰、高效,且能明确表达“利用键唯一性及顺序”的意图。

3.3 第12题:统计字符串字符频率

题目 :输入一个字符串,统计其中每个字符出现的次数。

详细解析 : 这是字典的招牌应用场景。字符作为键,出现的次数作为值。遍历字符串,如果字符不在字典中,就添加键并设值为1;如果已在,就将其值加1。

基础实现

def count_chars(text):
    """
    统计字符串中字符频率
    """
    char_count = {}
    for char in text:
        # 方法1:使用 if-else
        # if char in char_count:
        #     char_count[char] += 1
        # else:
        #     char_count[char] = 1

        # 方法2:使用 get 方法,更简洁
        char_count[char] = char_count.get(char, 0) + 1
    return char_count

# 示例
sample_text = "hello world"
result = count_chars(sample_text)
print(result)

使用 collections.Counter (进阶/生产环境推荐) : Python标准库中的 collections 模块提供了 Counter 这个利器,它就是为计数而生的。

from collections import Counter

def count_chars_counter(text):
    """
    使用 Counter 统计字符频率
    """
    return dict(Counter(text)) # 将Counter对象转为普通字典输出

result_counter = count_chars_counter("hello world")
print(result_counter)

Counter 不仅代码极其简洁,还提供了 most_common(n) 等方法直接获取出现频率最高的前n个元素,功能强大。

扩展思考

  • 如果不想统计空格怎么办?可以在遍历前用 text.replace(‘ ‘, ‘’) 或者遍历时加一个 if char != ‘ ‘: 的判断。
  • 如果想忽略大小写怎么办?可以在遍历时使用 char.lower()
  • 注意事项 :字典在Python 3.7之前是无序的,如果你需要按字符出现顺序或频率排序输出,可能需要使用 collections.OrderedDict 或对结果进行排序操作。

3.4 第15题:斐波那契数列(递归与迭代)

题目 :生成斐波那契数列的第n项。斐波那契数列:F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2) (n>=2)。

详细解析 : 这道题是讲解递归和迭代思想,以及算法效率的绝佳例子。

方法一:递归(直观但低效)

def fibonacci_recursive(n):
    """
    递归实现斐波那契数列
    警告:n较大时性能极差
    """
    if n <= 1:
        return n
    return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)

为什么低效? 以计算 F(5) 为例,递归树如下:

                F(5)
              /      \
          F(4)        F(3)
         /    \       /   \
      F(3)   F(2)   F(2)  F(1)
      /  \   /  \   /  \
   F(2)F(1)F(1)F(0)F(1)F(0)
   /  \
F(1) F(0)

F(3) 被计算了2次, F(2) 被计算了3次。随着n增大,重复计算呈指数级增长,时间复杂度约为O(2^n),完全不可接受。

方法二:迭代(动态规划思想,高效)

def fibonacci_iterative(n):
    """
    迭代实现斐波那契数列,时间复杂度 O(n)
    """
    if n <= 1:
        return n
    a, b = 0, 1 # 分别代表 F(0) 和 F(1)
    for _ in range(2, n + 1):
        a, b = b, a + b # 同时更新,b 变为新的 F(i),a 变为旧的 b (即 F(i-1))
    return b

这种方法只遍历一次,用两个变量 a b 滚动更新,存储前两项的值。时间复杂度是O(n),空间复杂度是O(1),非常高效。

方法三:带缓存的递归(记忆化搜索) 如果你喜欢递归的简洁表达,又不想忍受其低效,可以使用“记忆化”技术。

from functools import lru_cache

@lru_cache(maxsize=None) # 使用LRU缓存装饰器,自动缓存计算结果
def fibonacci_memoization(n):
    """
    带缓存的递归实现
    """
    if n <= 1:
        return n
    return fibonacci_memoization(n-1) + fibonacci_memoization(n-2)

@lru_cache 是Python标准库提供的装饰器,它会自动存储函数调用的结果。当用相同的参数再次调用时,直接返回缓存的结果,避免了重复计算,将时间复杂度从O(2^n)降到了O(n)。

对比与选择

  • 学习递归概念 :使用方法一,但要知道它的缺陷。
  • 实际应用 无条件选择方法二(迭代) 。它效率最高,代码也清晰。
  • 理解优化技巧 :使用方法三,它是递归算法优化的经典案例,在很多动态规划问题中很有用。

3.5 第18题:使用 filter lambda 筛选数据

题目 :给定一个整数列表,使用 filter 函数和 lambda 表达式,筛选出其中的偶数。

详细解析 : 这道题旨在练习函数式编程的基本概念。 filter(func, iterable) 函数接受一个函数 func 和一个可迭代对象,返回一个迭代器,其中包含所有使 func 返回 True 的元素。 lambda 则用于快速定义简单的匿名函数。

代码实现

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 使用 filter + lambda
even_numbers_iterator = filter(lambda x: x % 2 == 0, numbers)

# filter返回的是一个迭代器,通常我们将其转换为列表
even_numbers_list = list(even_numbers_iterator)
print(f"原列表: {numbers}")
print(f"偶数列表: {even_numbers_list}")

与传统循环对比

# 等效的 for 循环实现
even_numbers_for = []
for num in numbers:
    if num % 2 == 0:
        even_numbers_for.append(num)

两种方式的比较

  • filter+lambda :代码更简洁,更具声明式编程风格(描述“要什么”,而不是“怎么做”)。在处理数据管道或链式调用时特别有用。但可读性对于不熟悉函数式编程的人来说可能稍差。
  • for循环 :更命令式,步骤清晰,对于复杂的判断逻辑更容易编写和调试。性能上两者在Python中差异不大,列表推导式通常更快。

更Pythonic的写法:列表推导式 其实,对于这个简单需求,列表推导式是更受Python社区推崇的写法:

even_numbers_comprehension = [x for x in numbers if x % 2 == 0]

列表推导式同样简洁,且通常比 filter+lambda 执行速度稍快,因为它是在解释器层面优化过的语法结构。

核心要点 : 这道题的目的是让你了解 filter lambda 的用法。但在实际编码中,如果只是简单的过滤, 优先考虑列表推导式 filter 在需要将过滤函数作为参数传递(例如,过滤条件动态变化)时更有优势。

3.6 第21题:冒泡排序算法实现

题目 :手动实现冒泡排序算法对一个数字列表进行升序排序。

详细解析 : 冒泡排序是排序算法的入门课,它重复地遍历要排序的列表,一次比较两个相邻元素,如果它们的顺序错误就把它们交换过来。遍历列表的工作重复进行,直到没有再需要交换的元素,这意味着该列表已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

基础实现

def bubble_sort_basic(arr):
    """
    基础冒泡排序
    """
    n = len(arr)
    # 遍历所有数组元素
    for i in range(n):
        # 最后 i 个元素已经就位,无需再比较
        for j in range(0, n - i - 1):
            # 如果当前元素大于下一个元素,则交换
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j] # Python 优雅的交换语法
    return arr

# 测试
unsorted_list = [64, 34, 25, 12, 22, 11, 90]
sorted_list = bubble_sort_basic(unsorted_list.copy()) # 使用副本,不改变原列表
print(f"排序前: {unsorted_list}")
print(f"排序后: {sorted_list}")

优化版本:提前终止 基础版本即使列表已经有序,也会完成所有轮次的遍历。我们可以加入一个标志位来优化。

def bubble_sort_optimized(arr):
    """
    优化的冒泡排序(提前终止)
    """
    n = len(arr)
    for i in range(n):
        swapped = False # 标志位,记录本轮是否发生交换
        for j in range(0, n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                swapped = True
        # 如果本轮没有发生任何交换,说明列表已完全有序,提前结束
        if not swapped:
            break
    return arr

这个优化对于近乎有序的列表能大幅提升效率。

算法分析

  • 时间复杂度
    • 最坏和平均情况:O(n²),当列表是逆序时。
    • 最好情况:O(n),当列表已经有序时(优化版本才能达到)。
  • 空间复杂度 :O(1),属于原地排序。
  • 稳定性 :是稳定排序算法(相等元素的相对位置不变)。

为什么还要学冒泡排序? 在实际开发中,你几乎永远不会自己写冒泡排序,Python内置的 list.sort() sorted() 函数使用的是高效的Timsort算法。学习冒泡排序的价值在于:

  1. 理解排序的基本思想 (比较和交换)。
  2. 掌握算法分析的初步概念 (时间/空间复杂度)。
  3. 练习嵌套循环和条件判断 。 把它当作一个思维体操,而不是一个实用工具。

3.7 第23题:列表推导式生成杨辉三角

题目 :使用列表推导式生成指定行数的杨辉三角。

详细解析 : 杨辉三角是一个经典的数学图形,每个数是它左上方和右上方的数的和。用编程生成它,能很好地练习列表推导式和列表的嵌套操作。关键在于发现每一行与上一行的关系。

思路分析

  1. 第一行是 [1]
  2. 第二行是 [1, 1]
  3. 对于第 i 行(i>=2),除了首尾是1,中间的第 j 个元素等于上一行的第 j-1 个元素加上第 j 个元素。即 row[j] = prev_row[j-1] + prev_row[j]

代码实现

def generate_pascal_triangle(num_rows):
    """
    生成杨辉三角
    """
    triangle = [] # 用于存储整个三角形
    for row_num in range(num_rows):
        # 每一行都是一个列表
        # 第一行是 [1]
        # 第二行是 [1, 1]
        # 后续行:首尾为1,中间元素由上一行相邻两元素相加得到
        row = [1] # 每一行以1开头
        if triangle: # 如果不是第一行
            prev_row = triangle[-1] # 获取上一行
            # 使用列表推导式生成中间部分
            # 对上一行进行滑动窗口求和:prev_row[j] + prev_row[j+1]
            row.extend([prev_row[j] + prev_row[j + 1] for j in range(len(prev_row) - 1)])
            row.append(1) # 每一行以1结尾
        triangle.append(row)
    return triangle

# 生成并打印前6行
rows = 6
pascal_triangle = generate_pascal_triangle(rows)
for row in pascal_triangle:
    # 格式化打印,使其居中显示更美观
    print(' '.join(map(str, row)).center(rows * 3))

纯列表推导式的“炫技”写法(理解即可)

def generate_pascal_oneliner(n):
    """
    使用列表推导式的一行写法(可读性较差,仅供学习)
    """
    return [[1 if j == 0 or j == i else (pascal[i-1][j-1] + pascal[i-1][j]) for j in range(i+1)] for i in range(n)]

这种写法非常紧凑,但难以理解和调试。 在实际项目中,强烈推荐使用第一种清晰易懂的写法 。编程的首要目标是清晰表达意图,而非追求极致的简短。

练习价值 : 这道题综合运用了循环、列表操作、条件判断和列表推导式。它教会你如何将复杂的数学规律转化为清晰的程序逻辑,并且展示了如何用代码构建一个结构化的数据(列表的列表)。

3.8 第25题:简单文件单词统计器

题目 :读取一个文本文件,统计文件中每个单词出现的次数,并将结果按出现频率从高到低输出。

详细解析 : 这是一个微型的综合项目,涉及文件I/O、字符串处理、数据统计和排序。它模拟了数据分析中的一个常见任务。

实现步骤拆解

  1. 读取文件 :使用 with open(...) as f 安全地打开文件,并读取全部内容。
  2. 文本预处理 :将内容转换为小写(使统计不区分大小写),去除标点符号。
  3. 分割单词 :使用字符串的 split() 方法按空白字符分割成单词列表。
  4. 统计频率 :使用 collections.Counter 进行计数。
  5. 排序输出 :使用 Counter most_common() 方法直接获取排序后的结果。

代码实现

import re
from collections import Counter

def count_words_in_file(filepath):
    """
    统计文本文件中单词频率
    :param filepath: 文本文件路径
    :return: 按频率降序排列的 (单词, 次数) 列表
    """
    try:
        with open(filepath, 'r', encoding='utf-8') as file:
            text = file.read()
    except FileNotFoundError:
        print(f"错误:文件 '{filepath}' 未找到。")
        return []
    except IOError as e:
        print(f"读取文件时发生错误: {e}")
        return []

    # 文本预处理:转小写,使用正则表达式移除非字母数字字符(保留连字符等可根据需求调整)
    # 这里\w匹配单词字符(字母、数字、下划线),^表示取反,即替换掉所有非单词字符为空格
    cleaned_text = re.sub(r'[^\w\s]', ' ', text.lower())

    # 分割单词
    words = cleaned_text.split()

    # 使用Counter统计
    word_counts = Counter(words)

    # 返回最常见的单词及其计数
    return word_counts.most_common()

# 假设有一个名为 ‘sample.txt’ 的文件
results = count_words_in_file('sample.txt')

# 打印前10个最常见的单词
print("单词统计结果(频率从高到低):")
for word, count in results[:10]:
    print(f"{word}: {count}")

# 如果你想输出所有单词
# for word, count in results:
#     print(f"{word}: {count}")

关键点与注意事项

  1. 文件编码 :始终指定文件编码(如 utf-8 ),这是避免中文等非ASCII字符乱码的关键。使用 with 语句可以确保文件被正确关闭。
  2. 异常处理 :文件操作可能失败(文件不存在、无权限等),添加 try-except 块使程序更健壮。
  3. 文本清洗 :正则表达式 re.sub(r'[^\w\s]’, ‘ ‘, text) 是核心。它匹配所有非单词字符、非空白字符(即标点符号),并将其替换为空格。这是一个简化的清洗,根据实际文本(如包含缩写 I'm 或带连字符的单词 well-known )可能需要更复杂的规则。
  4. 性能考虑 :对于大文件,一次性读取全部内容( file.read() )可能占用大量内存。可以改为逐行读取处理。
  5. Counter.most_common() :这个方法返回一个元素为 (item, count) 的列表,已经按计数从大到小排好序,非常方便。

这个小小的程序串联了多个知识点,是一个很好的练手项目。你可以在此基础上扩展,比如忽略常见无意义词(停用词)、绘制词云图等。

4. 常见问题与排查技巧实录

在练习这些题目时,你肯定会遇到各种错误和疑惑。下面我总结了一些最常见的问题及其解决方法,这可能是比题目答案更有价值的经验。

4.1 缩进错误(IndentationError)

这是Python新手的第一道坎。Python用缩进来定义代码块,混用空格和Tab键,或者缩进级别不一致,都会导致此错误。

错误示例

def my_func():
print("Hello") # 这里没有缩进

解决方案

  1. 使用统一的缩进方式 强烈建议只使用空格 ,并设置你的代码编辑器将Tab键自动转换为4个空格(这是PEP 8官方建议)。
  2. 检查编辑器设置 :在VS Code、PyCharm等编辑器中,查看右下角,确保显示的是“Spaces: 4”而不是“Tab Size: 4”。
  3. 利用编辑器的显示功能 :开启“显示空白字符”功能,这样空格和Tab会以不同的点或箭头显示出来,一目了然。

4.2 变量作用域混淆

在函数内部修改全局变量,或者不理解局部变量和全局变量的区别,会导致意想不到的结果。

错误示例

count = 0
def increment():
    count += 1 # UnboundLocalError: local variable 'count' referenced before assignment
increment()

在函数内部, count += 1 被解释为对局部变量 count 赋值,但此时局部变量 count 还未被定义。

解决方案

  1. 明确使用 global 关键字 (通常不推荐,因为它破坏了函数的封装性):
    def increment():
        global count
        count += 1
    
  2. 通过参数传递和返回值 (推荐做法):
    def increment(num):
        return num + 1
    count = increment(count)
    
  3. 使用可变对象 :如果要修改的是全局的列表或字典,直接操作其元素是可行的,因为你不是在给变量本身重新赋值。
    my_list = [1, 2, 3]
    def append_to_list():
        my_list.append(4) # 这是可以的,修改的是列表对象的内容
    

4.3 列表修改时的“幽灵”错误

在遍历列表的同时修改其长度(增删元素),是另一个经典错误源。

错误示例

words = ['hello', 'world', 'python', 'is', 'great']
for word in words:
    if len(word) < 4:
        words.remove(word) # 危险!在迭代中修改列表
print(words) # 结果可能不符合预期,比如 ‘is’ 可能没有被删除

当删除一个元素后,列表长度和索引都变了,但迭代器仍按原索引前进,可能导致某些元素被跳过。

解决方案

  1. 创建新列表 (最安全、最清晰):
    new_words = [word for word in words if len(word) >= 4]
    
  2. 遍历副本,修改原列表
    for word in words[:]: # 使用切片 words[:] 创建副本进行遍历
        if len(word) < 4:
            words.remove(word)
    
  3. 反向遍历 (适用于删除操作):
    for i in range(len(words)-1, -1, -1): # 从后往前遍历
        if len(words[i]) < 4:
            del words[i]
    
    反向遍历时,删除元素不会影响前面未遍历到的元素的索引。

4.4 字符串与数字类型转换错误

从输入(如 input() 函数)获取的数据默认是字符串类型,直接用于数学运算会导致 TypeError

错误示例

user_input = input("请输入一个数字: ")
result = user_input * 2 # 如果输入5,结果是‘55’,而不是10

解决方案

  1. 明确转换 :使用 int() , float() 进行转换。
    try:
        number = int(user_input)
        result = number * 2
    except ValueError:
        print("输入的不是有效整数!")
    
  2. 使用 try-except 捕获异常 :用户输入是不可靠的,必须做好异常处理,防止程序崩溃。

4.5 函数默认参数的可变陷阱

这是一个高级但常见的坑。如果函数的默认参数是一个可变对象(如列表、字典),那么这个默认参数在函数定义时就被创建了,并且在整个程序运行期间都是同一个对象。

错误示例

def add_item(item, my_list=[]):
    my_list.append(item)
    return my_list

print(add_item(1)) # 输出 [1]
print(add_item(2)) # 输出 [1, 2]!而不是预期的 [2]

解决方案 永远不要使用可变对象作为函数参数的默认值。 正确的做法是使用 None ,然后在函数内部进行判断和初始化。

def add_item_correct(item, my_list=None):
    if my_list is None:
        my_list = []
    my_list.append(item)
    return my_list

print(add_item_correct(1)) # 输出 [1]
print(add_item_correct(2)) # 输出 [2]

4.6 调试技巧: print 大法好与 pdb 入门

当程序行为不符合预期时,不要干瞪眼,要主动调试。

  1. print() 调试法 :在关键位置打印变量的值。这是最简单粗暴有效的方法。

    def complex_calculation(data):
        print(f"[DEBUG] 输入数据: {data}") # 打印输入
        intermediate = step1(data)
        print(f"[DEBUG] 第一步结果: {intermediate}") # 打印中间结果
        result = step2(intermediate)
        print(f"[DEBUG] 最终结果: {result}") # 打印最终结果
        return result
    

    使用 f-string (Python 3.6+)可以方便地格式化输出。调试完后,记得删除或注释掉这些打印语句。

  2. 使用断言 assert :在代码中插入检查点,确保某些条件为真。

    def divide(a, b):
        assert b != 0, "除数不能为零!"
        return a / b
    

    如果 b 为0,程序会抛出 AssertionError 并停止,帮助你快速定位问题。断言通常用于开发阶段,在生产环境中可能会被禁用。

  3. 使用调试器 pdb :对于复杂问题,交互式调试器是终极武器。在怀疑有问题的代码行前插入 import pdb; pdb.set_trace() ,运行程序时会在此处进入调试模式。你可以:

    • n (next): 执行下一行。
    • s (step): 进入函数内部。
    • c (continue): 继续执行直到下一个断点。
    • p <变量名> : 打印变量值。
    • l (list): 查看当前代码位置。
    • q (quit): 退出调试。 虽然一开始可能不习惯,但学会使用调试器能极大提升你解决复杂Bug的效率。

坚持把这25道题自己动手敲一遍,遇到错误就对照上面的排查技巧解决,你的Python实战能力一定会突飞猛进。编程没有捷径,就是不断地思考、敲代码、调试、再思考。这份题集和解析就是你的训练手册,现在,打开你的编辑器,开始练习吧。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐