Python的函数定义中99%的人会遇到的一个坑

本文讲述了在Python编程中,如何在函数中使用列表作为参数并处理默认值,以及默认参数在多次调用时的行为。作者通过示例展示了如何避免因默认参数共享导致的问题,并提供了解决方案。

列表是一种经常使用的数据类型。在函数的定义中,常常会使用列表作为参数。

比如,要测试一个接口的数据,接口返回的数据格式如下:

{
  "code": "20000", 
  "data": ["孙悟空","李白","甄姬"], 
  "msg": "success", 
  "status": 0
}

要测试的内容是:返回的 data 数据是否跟需求符合。在测试之前,需要对数据进一步处理,比如要增加 “王昭君” 这个元素进去,需要写一个函数:

def add_element(data=["孙悟空","李白","甄姬"]):
    data.append('王昭君')
    return data

print(add_element())
print(add_element())
print(add_element())

在函数定义的时候经常会给参数设置默认值,在这个例子中,将 data 参数设置了默认值,函数定义以后,后面会被频繁的调用,期望值应该是打印如下:

["孙悟空","李白","甄姬","王昭君"]
["孙悟空","李白","甄姬","王昭君"]
["孙悟空","李白","甄姬","王昭君"]

实际结果为:

["孙悟空","李白","甄姬","王昭君"]
["孙悟空","李白","甄姬","王昭君","王昭君"]
["孙悟空","李白","甄姬","王昭君","王昭君","王昭君"]

原因

当定义函数时,会保存函数中默认参数 data 的值,也就是 ["孙悟空","李白","甄姬"],在每次调用的时候如果传递了新的实参,则使用传递的参数;没有传递,使用定义函数时保存的默认参数。

上面两次调用中,都没有传递新的实参,程序会调用定义函数时保存的默认参数,因为 append() , 在第一次调用以后,默认参数已经由 ["孙悟空","李白","甄姬"] 改变为 ["孙悟空","李白","甄姬","王昭君"],再次执行 append() 之后,就变成了 ["孙悟空","李白","甄姬","王昭君","王昭君"];同理,第三次又改变了。

可以使用 id() 函数来定位问题:

def add_element(data=["孙悟空","李白","甄姬"]):
    # id 来表示是不是同一个对象
    print(id(data))
    data.append('王昭君')
    return data

print(add_element())
print(add_element())
print(add_element())

打印出来的 id(data) 为同一个对象,也就是默认参数。如果我们改变 第二个 print(add_element())print(add_element(["孙悟空","李白","甄姬"])),那么第 2 个 id(data) 就会发生变化,因为它不在是默认值,而是新传进来的实参,实际结果也将变成:

2543416926792
['孙悟空', '李白', '甄姬', '王昭君']
2543418907848
["孙悟空","李白","甄姬", '王昭君']
2543416926792
['孙悟空', '李白', '甄姬', '王昭君', '王昭君']

改进方案

  • 如果参数中有列表,尽量不要用它做默认参数
  • 如果使用了列表作为默认参数,函数调用时传入实参,而不是省略
  • 可以在函数体中另外定义一个变量接收默认参数
def add_element(data=["孙悟空","李白","甄姬"]):
    if data == ["孙悟空","李白","甄姬"]:
        data = ["孙悟空","李白","甄姬"]
    data.append('王昭君')
    return data

这里给大家分享一份Python全套学习资料,包括学习路线、软件、源码、视频、面试题等等,都是我自己学习时整理的,希望可以对正在学习或者想要学习Python的朋友有帮助!

优快云大礼包:全网最全《全套Python学习资料》免费分享🎁

😝有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓

👉优快云大礼包🎁:全网最全《Python学习资料》免费分享(安全链接,放心点击)👈

style=“margin: auto” />

1️⃣零基础入门

① 学习路线

对于从来没有接触过Python的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
在这里插入图片描述

② 路线对应学习视频

还有很多适合0基础入门的学习视频,有了这些视频,轻轻松松上手Python~在这里插入图片描述

③练习题

每节视频课后,都有对应的练习题哦,可以检验学习成果哈哈!
在这里插入图片描述
因篇幅有限,仅展示部分资料

2️⃣国内外Python书籍、文档

① 文档和书籍资料

在这里插入图片描述

3️⃣Python工具包+项目源码合集

①Python工具包

学习Python常用的开发软件都在这里了!每个都有详细的安装教程,保证你可以安装成功哦!
在这里插入图片描述

②Python实战案例

光学理论是没用的,要学会跟着一起敲代码,动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。100+实战案例源码等你来拿!
在这里插入图片描述

③Python小游戏源码

如果觉得上面的实战案例有点枯燥,可以试试自己用Python编写小游戏,让你的学习过程中增添一点趣味!
在这里插入图片描述

4️⃣Python面试题

我们学会了Python之后,有了技能就可以出去找工作啦!下面这些面试题是都来自阿里、腾讯、字节等一线互联网大厂,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
在这里插入图片描述
在这里插入图片描述

5️⃣Python兼职渠道

而且学会Python以后,还可以在各大兼职平台接单赚钱,各种兼职渠道+兼职注意事项+如何和客户沟通,我都整理成文档了。
在这里插入图片描述
在这里插入图片描述
上述所有资料 ⚡️ ,朋友们如果有需要 📦《全套Python学习资料》的,可以扫描下方二维码免费领取 🆓
😝有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓

👉优快云大礼包🎁:全网最全《Python学习资料》免费分享(安全链接,放心点击)👈

<think>我们被要求使用Python编写一个生成99乘法表的函数。根据引用,有多种实现方式,包括循环(嵌套for循环、while循环)和递归等。 我们将展示几种不同的实现方式,并定义一个函数来封装它们。 注意:99乘法表通常是从1乘1到9乘9,所以我们的函数将生成一个9行9列的乘法表,但每行的列数递增(第1行1列,第2行2列,...,第9行9列)。 我们将定义函数,并展示几种不同的实现方式,每种方式都封装在一个函数中。 根据引用,我们有以下几种方式: 1. 使用两层for循环(引用[1]和引用[3]) 2. 使用递归(引用[2]) 3. 使用列表推导式和join(引用[3]) 此外,引用[4]还提到了while循环的方式。 我们将分别实现: 方式1:for-for循环 方式2:递归 方式3:一行代码(列表推导式) 方式4:while-while循环(或while-for循环) 但注意,题目要求定义一个函数,所以我们将每个方式都写成一个函数。 另外,输出格式可以调整,我们可以选择直接打印,也可以选择返回一个字符串(这样函数可以返回字符串,然后打印字符串)。 根据需求,我们可能希望函数返回字符串或者直接打印。这里我们让函数返回字符串,这样调用者可以灵活处理(打印或写入文件等)。 但是,注意递归的实现(引用[2])是直接打印的,我们可以修改为返回字符串,但递归构建字符串可能稍复杂。为了简单,我们也可以让函数直接打印,但为了统一,我们尽量让每个函数都返回字符串。 然而,递归版本如果返回字符串,需要递归地构建字符串。我们可以调整递归函数,使其返回每一行的字符串,然后组合。 但考虑到递归版本在引用[2]中是直接打印的,我们也可以提供两种版本,但题目要求是函数,所以我们将按照返回字符串的方式来实现。 我们先实现for循环版本(返回字符串): 思路:使用两层循环,外层循环控制行数(1到9),内层循环控制每行的列数(1到当前行数)。每行结束时换行。 例如: 第1行:1*1=1 第2行:1*2=2 2*2=4 ... 我们可以用一个列表来收集每一行的字符串,然后最后用换行符连接。 方式1:for_for_multiplication_table() 方式2:recursive_multiplication_table() 递归版本 方式3:one_line_multiplication_table() 一行代码版本(实际上也可以写成函数) 方式4:while_loop_multiplication_table() while循环版本 注意:递归版本中,我们可能需要一个参数来指定当前行,但为了函数接口一致,我们可以设置一个默认参数,但用户调用时不需要传递。不过,为了统一,我们可以让函数只接受一个可选参数(用于递归),然后对外提供一个无参数的函数。但这样会使得接口不一致。因此,我们可以写一个包装函数。 但是,题目要求是编写一个生成99乘法表的函数,所以我们每个函数都应该是无参数的(或者参数用于控制递归深度,但对外调用时不需要传递)。 我们这样设计: 递归版本:内部定义递归函数,然后调用它,并返回结果字符串。 由于递归需要从1到9,我们可以这样: 递归函数:_recursive_helper(n),当n=1时,返回第一行的字符串;否则,返回前n-1行的字符串加上第n行的字符串。 但是,递归到9为止,所以我们的主函数调用_recursive_helper(9)即可。 然而,递归深度过大时可能有问题,但这里只有9层,所以没问题。 开始实现: 注意:为了对齐,我们可以使用制表符\t或者固定宽度。引用[3]中使用了%2s来对齐。我们也可以采用类似方式。 我们将使用以下格式:每个乘法表达式用"{j}*{i}={result}",并且为了对齐,我们让每个表达式占固定宽度(比如8个字符)或者使用制表符。但是,当结果是一位数或两位数时,制表符可能不够整齐。所以我们可以固定每个表达式占7个字符,左对齐或右对齐。 例如,引用[3]中:"%2s x%2s = %2s" % (j, i, i*j) 这样每个数字占2个字符,等号前后有空格,整体宽度固定。 我们采用类似格式:每个表达式格式为"{j}×{i}={product}",其中j和i都是1到9,乘积最多两位,所以我们可以用:f"{j}×{i}={i*j:2}",但是这样乘积右对齐,我们可以调整。 但注意,乘法表是逐行输出的,我们希望每行内的表达式对齐。我们可以使用: f"{j}×{i}={i*j:>2}" 这样乘积右对齐占2位。或者统一用:f"{j}×{i}={i*j:2}" 但是,为了对齐,我们也可以让每个表达式都占7个字符,例如: 1×1=1 -> 用空格补足: "1×1=1 " 1×2=2 -> "1×2=2 " 但这样并不完美,因为当乘积是两位数时,占2位,所以表达式总长度不一致(1×1=1 是5个字符,1×9=9是5个字符,但2×9=18是6个字符)。所以我们可以固定每个表达式占6个字符?或者使用制表符。 参考引用[2]中使用了制表符(end="\t"),但这样在每行最后会多出一个制表符,而且不同环境制表符显示可能不一致。 我们这里采用每个表达式用固定宽度,比如每个表达式占8个字符,右对齐。这样: 1×1=1 -> " 1×1=1" 不够,我们希望是左对齐:表达式左对齐,占8个字符。 所以:f"{j}×{i}={i*j:<2}" 这样乘积左对齐占2位,但整个表达式长度还是不一致(因为j和i都是1位时表达式短,但j和i都是1位,所以整个表达式除了乘积部分都是3个字符,加上乘积2位,共5个字符)。所以我们可以固定整个表达式为:f"{j}×{i}={i*j}",然后补空格到8个字符?这样太麻烦。 另一种方法是:使用两个空格,然后每个表达式用:f"{j}×{i}={i*j:2} ",这样每个表达式后面加两个空格,但这样也不一定对齐。 我们参考引用[3]中的方法:使用"%d * %d = %d" % (j, i, i*j) 并固定每个数字占2位(%2d)?但是,在引用[3]的代码中,他们使用了"%2s x%2s = %2s",这样每个数字占2位,所以整个表达式长度固定。 我们采用:f"{j}×{i}={i*j:2}",然后每个表达式后面加两个空格?但这样表达式长度是:j和i都是1位,乘号1位,等号1位,乘积2位,共1+1+1+1+2=6,再加上两个空格共8个?但这样也不完全一致,因为当j和i都是1位时,表达式是6个字符(包括乘积的2位),然后我们加两个空格,凑成8个字符。这样每行打印多个表达式时,每个占8个字符,可以对齐。 但是,乘法表每行的表达式个数不同,所以每行打印完后换行即可。 我们这样:每个表达式用:f"{j}×{i}={i*j:2} ",注意乘积占2位(右对齐),然后后面加两个空格。这样每个表达式都是8个字符(因为j和i都是1位数字,所以整个表达式是:j(1位),乘号(1位),i(1位),等号(1位),乘积(2位)和两个空格,共1+1+1+1+2+2=8?不对,应该是:j(1),乘号(1),i(1),等号(1),乘积(2),两个空格(2)共8个字符?不对,j、乘号、i、等号共4个字符,乘积2个字符,两个空格2个字符,总共8个字符。所以每个表达式固定8个字符。 但是,当乘积是一位数时,乘积占2位(右对齐),所以会出现一个空格,例如:1×1= 1 (注意1前面有一个空格)加上两个空格,整个字符串是8个字符。而2×9=18,没有空格,加上两个空格也是8个字符。 这样,每行打印多个表达式,每个表达式占8个字符,就可以对齐。 但是,我们也可以不用两个空格,而用一个制表符,但制表符的宽度通常是4或8个字符,所以当表达式长度是4(如1×1=1)时,加上一个制表符(假设制表符宽度为4)则总共8个字符,而表达式长度是5(如1×9=9,这里9前面有一个空格,因为乘积是%2d,所以是" 9")则表达式总长度为5,加上制表符(跳到下一个制表位,即8的倍数)会跳到第8个字符,所以下一个表达式从第8个字符开始。这样也是对齐的。 但是,不同环境制表符显示可能不同,所以我们还是用固定空格。 我们决定:每个表达式使用 f"{j}×{i}={i*j:2} ",即乘积占2位(右对齐),然后后面加两个空格。这样每个表达式固定8个字符(包括最后的两个空格)。 但是,注意当i和j都是1位数,所以表达式除了乘积部分都是4个字符(如"1×1="),乘积部分2位,然后两个空格,共4+2+2=8。 现在,我们开始写代码。 注意:在函数中,我们收集每一行的字符串,每行由多个表达式组成,行内不换行,行尾换行。 方式1:for_for_multiplication_table 初始化一个空列表lines 外层循环i从1到9(包括9)表示行号(也是第二个乘数) 内层循环j从1到i(包括i)表示第一个乘数 构建表达式字符串:f"{j}×{i}={i*j:2} " 注意这里乘积占2位(右对齐),然后加两个空格 将这一行所有的表达式字符串拼接起来(注意内层循环每次加一个表达式),然后去掉行尾多余的空格?但我们不需要去掉,因为每个表达式后面都有两个空格,但行尾不需要两个空格,我们可以在内层循环结束后,将这一行的字符串去掉末尾空格,然后加上换行符。 或者,我们可以在内层循环中直接拼接,然后整个行字符串最后加上换行符。 但是,行尾的两个空格是多余的,我们可以不要最后一个表达式的两个空格。但是,为了简单,我们可以在内层循环结束后,将这一行的字符串去掉末尾的两个空格(或所有空格)?但这样可能不安全,因为表达式内部有空格。所以我们可以换一种方式:内层循环中,每个表达式都加,但最后我们去掉行尾的两个空格(因为我们知道每两个空格是分隔符,行尾不需要)。 所以: 对于每一行,初始化一个空字符串line 内层循环j从1到i: line += f"{j}×{i}={i*j:2} " 然后,我们去掉line末尾的两个空格(因为行尾不需要),然后加上换行符'\n' 将line加入lines列表 最后,将lines列表中的所有字符串连接起来,返回。 方式2:递归版本 我们定义一个递归函数,参数为n(当前行,从9开始递减,直到1),返回从第1行到第n行的乘法表字符串。 递归基:当n==1时,返回第一行的字符串,即"1×1= 1\n"(注意乘积是1,我们写成占2位,所以是" 1",然后去掉末尾两个空格?但第一行只有一个表达式,所以我们在构建第一行时,也是先构建一个表达式然后去掉末尾两个空格?但递归函数中,我们也可以直接构建行字符串,然后递归构建前面的行。 递归步骤:返回前n-1行的字符串(递归调用)加上第n行的字符串。 第n行的字符串构建:j从1到n,每个表达式f"{j}×{n}={n*j:2} ",然后拼接,然后去掉行尾两个空格,加上换行符。 但是,这样递归是倒序的?不,我们递归调用是前n-1行,所以先构建前n-1行,然后加上第n行,这样就是正序。 但是,递归深度是从n到1,但构建顺序是:先构建第一行,然后第二行,...,最后第n行。所以我们需要在递归基返回第一行,然后递归返回时逐层添加后面的行。 具体: if n == 1: return "1×1= 1\n" # 注意:乘积1占2位,所以是" 1" else: prev_lines = recursive_helper(n-1) # 前n-1行的字符串 current_line = "" for j in range(1, n+1): current_line += f"{j}×{n}={n*j:2} " current_line = current_line.rstrip() + '\n' # 去掉行尾空格,然后加换行符 return prev_lines + current_line 但是,这样第n行的表达式之间有两个空格,最后我们去掉所有末尾空格?但行内表达式之间的空格不需要去掉,我们只去掉行尾的两个空格(因为最后多加了两个空格)。所以可以用rstrip去掉行尾空格,但注意rstrip会去掉所有行尾空格,包括可能多个空格,但每行末尾我们只加了两个空格(最后一个表达式后面有两个空格,我们希望去掉这两个空格)。 所以:current_line = current_line[:-2] + '\n' # 直接去掉最后两个空格,但前提是最后两个一定是空格。但这样不安全,因为如果表达式没有空格呢?所以我们在构建时每个表达式后面都加两个空格,所以行尾一定有两个空格。 因此,我们可以用切片去掉最后两个字符(两个空格),然后加上换行符。 方式3:一行代码版本 参考引用[3]:print('\n'.join([' '.join(["%2s x%2s = %2s" % (j, i, i*j) for j in range(1, i+1)]) for i in range(1, 10)])) 我们稍作修改,使用f-string,并调整格式,然后返回字符串。 我们可以这样: table = '\n'.join([''.join([f"{j}×{i}={i*j:2} " for j in range(1, i+1)]).rstrip() for i in range(1,10)]) 但是,这样每行末尾会有两个空格,所以用rstrip去掉每行末尾空格(包括两个空格),然后换行连接。 所以函数可以写成: def one_line_multiplication_table(): return '\n'.join([''.join([f"{j}×{i}={i*j:2} " for j in range(1, i+1)]).rstrip() for i in range(1,10)]) 方式4:while循环版本 类似for循环,但使用while。 我们写两个while循环: i = 1 lines = [] while i <= 9: j = 1 line = "" while j <= i: line += f"{j}×{i}={i*j:2} " j += 1 line = line[:-2] # 去掉最后两个空格 lines.append(line) i += 1 return '\n'.join(lines) 但是,注意while循环版本中,我们也可以使用rstrip(),但这里我们明确知道最后两个字符是空格,所以直接切片去掉。 现在,我们编写这些函数,并选择其中一个作为主要实现。但题目要求是编写一个函数,我们可以提供多种实现,但用户要求的是一个函数,所以我们可以只写一个函数,但为了展示不同方法,我们提供多个函数,并说明。 但题目要求是“定义函数实现99乘法表的练习题”,所以我们提供多个函数定义,供用户参考。 注意:我们使用f-string,所以需要Python3.6以上。 下面我们写出完整的函数定义,并测试(在回答中,我们只展示代码,不运行,但确保语法正确)。 另外,我们也可以让函数一个可选参数,指定输出方式(比如对齐方式),但题目没有要求,所以简单实现。 开始编写代码: 我们将实现四个函数,每个函数都返回一个字符串,该字符串就是整个乘法表。 注意:在递归版本中,我们使用内部递归函数,所以主函数一个无参数的函数,内部调用递归函数(参数为9)。 另外,递归版本中,我们也可以不用内部函数,但需要处理递归参数。为了外部调用无参数,我们使用内部函数。 具体代码:</think>下面我将详细介绍如何使用Python定义函数实现99乘法表,并提供多种实现方式作为练习题参考。 ### 方法1:双层for循环函数 ```python def multiplication_table_for(): """使用双层for循环生成99乘法表""" for i in range(1, 10): # 外层控制行数 for j in range(1, i+1): # 内层控制每行表达式 print(f"{j}×{i}={i*j}", end="\t") # \t制表符对齐 print() # 换行 # 调用函数 multiplication_table_for() ``` ### 方法2:递归函数实现 ```python def multiplication_table_recursive(n=9): """递归方式生成99乘法表""" if n == 1: print("1×1=1") else: multiplication_table_recursive(n-1) # 递归调用 # 打印当前行 for j in range(1, n+1): print(f"{j}×{n}={j*n}", end="\t") print() # 调用函数 multiplication_table_recursive() ``` ### 方法3:列表推导式单行函数 ```python def multiplication_table_oneline(): """单行列表推导式生成99乘法表""" print('\n'.join(['\t'.join([f"{j}×{i}={i*j}" for j in range(1, i+1)]) for i in range(1, 10)])) # 调用函数 multiplication_table_oneline() ``` ### 方法4:while循环函数 ```python def multiplication_table_while(): """使用while循环实现99乘法表""" i = 1 while i < 10: j = 1 while j <= i: print(f"{j}×{i}={i*j}", end="\t") j += 1 print() i += 1 # 调用函数 multiplication_table_while() ``` ### 函数优化:返回字符串代替直接打印 ```python def multiplication_table_string(): """返回字符串格式的乘法表""" lines = [] for i in range(1, 10): line = [] for j in range(1, i+1): line.append(f"{j}×{i}={i*j}") lines.append("\t".join(line)) return "\n".join(lines) # 调用函数 print(multiplication_table_string()) ``` ### 格式对齐优化版 ```python def multiplication_table_aligned(): """对齐优化的乘法表""" for i in range(1, 10): for j in range(1, i+1): # 使用格式控制保证对齐 print(f"{j}×{i}={i*j:<2}", end=" " if i*j < 10 else " ") print() # 调用函数 multiplication_table_aligned() ``` ### 练习题建议 1. 尝试修改函数使乘法表逆序输出(从9×9开始) 2. 添加参数控制乘法表的大小(如$n\times n$表) 3. 实现函数返回HTML格式的乘法表 4. 比较递归和循环实现的性能差异 5. 添加颜色标记特定数字(如质数用红色显示) 这些实现展示了函数封装的核心思想:将重复逻辑封装为可重用单元。递归实现利用了函数自调用特性[^2],而列表推导式展示了Python的简洁性[^3]。格式对齐优化中,`{i*j:<2}`通过左对齐和固定宽度保证布局整齐,其中`<2`表示左对齐占2字符宽度[^1]。 最后,建议尝试执行这些函数并观察输出差异,理解不同实现方式的优缺点。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值