文章目录
- 📚 今日学习目标
- 📦 Python函数详解
- 一、函数是什么?
-
- 1.1 为什么要用函数?
- 1.2 示例
- 二、函数的定义和调用
-
- 2.1 函数定义
- 2.2 函数调用
- 三、函数参数详解
-
- 3.1 传参方式
-
- 3.1.1 位置参数
- 3.1.2 关键字参数
- 3.1.3 混合参数
- 3.1.4 对比两种传参方式
- 3.1.5 对比 C++ 和 Java 传参方式
- 3.2 默认参数
- 3.3 不定长参数
-
- 3.3.1 接收任意数量的位置参数(*args)
- 3.3.2 接收任意数量的关键字参数(**kwargs)
- 3.3.3 组合使用 *args 和 **kwargs
- 3.3.4 对比C++和Java重载
- 3.4 参数是函数
- 四、匿名函数
-
- 4.1 基本语法:
- 4.2 何时使用Lambda表达式?
- 4.3 对比 C++ Lambda
- 五、如何设计一个函数?
- 六、变量的作用域
-
- 6.1 局部变量与全局变量
- 6.2 使用global关键字
- 6.3 对比C++和Java类似情况
- 6.4 使用场景以及注意事项
- 七、个人思考:参数传递
-
- 7.1 变量存储的是什么?
- 7.2 参数传递的本质
- 7.3 能否通过修改形参来改变实参?
- 7.4 不同语言分析
-
- 7.4.1 Python
- 7.4.2 Java
- 7.4.3 C++
📚 今日学习目标
✨注:本文的结构与 Java 学习笔记(Day05)保持相似,有助于进行对比学习。
📦 Python函数详解
一、函数是什么?
函数即可重复使用的、具有独立功能的代码片段,以提高代码的维护性和复用性。
1.1 为什么要用函数?
1.2 示例
print("不使用函数的示例:")
# 第一组数据计算
a1 = 10
b1 = 5
sum1 = a1 + b1
diff1 = a1 – b1
product1 = a1 * b1
quotient1 = a1 / b1
print(f"第一组结果: 和={sum1}, 差={diff1}, 积={product1}, 商={quotient1:.2f}")
# 第二组数据计算(完全重复的结构)
a2 = 20
b2 = 4
sum2 = a2 + b2
diff2 = a2 – b2
product2 = a2 * b2
quotient2 = a2 / b2
print(f"第二组结果: 和={sum2}, 差={diff2}, 积={product2}, 商={quotient2:.2f}")
# 多组重复…
✅ 使用方法后的代码(简洁)
print("使用函数的示例:")
# 定义一个计算并打印结果的函数
def calculate_and_print(a, b, group_name):
sum_result = a + b
diff = a – b
product = a * b
quotient = a / b
print(f"{group_name}结果: 和={sum_result}, 差={diff}, 积={product}, 商={quotient:.2f}")
# 只需调用函数,传入不同数据即可
calculate_and_print(10, 5, "第一组")
calculate_and_print(20, 4, "第二组")
# 多组计算只需要简单的一行调用
二、函数的定义和调用
函数必须先定义、后调用,就像必须先制造工具,才能使用它。
2.1 函数定义
将一段功能代码封装到一个函数中
def 函数名(形参列表):
"""
:param 形参列表:
:return:
"""
函数体
return 返回值
各部分说明:
- def:定义函数的关键字
- 函数名:遵循下划线命名法(如 calculate_sum)
- 形式参数:函数定义时声明的参数,用于接收调用时传入的值
- 函数体:实现函数功能的所有代码
- return:结束函数或者结束函数并返回一个值
- 函数说明文档:使用三个双引号包裹,用于解释函数功能、参数和返回值,方便调用者清楚知道函数的具体作用以及细节
2.2 函数调用
使用函数名来调用执行该函数中的代码
函数名(实参列表)
三、函数参数详解
3.1 传参方式
传参方式指的是,在调用函数时,传递实参的方式。
3.1.1 位置参数
调用函数时根据函数定义时的位置来传递参数,也就是说实参的顺序必须与形参的定义顺序一一对应。
示例:
def reg_stu(name,age,gender):
print(f"姓名:{name},年龄:{age},性别:{gender}")
reg_stu("kela",18,"女") # 姓名:kela,年龄:18,性别:女
3.1.2 关键字参数
调用函数时以函数定义时的形参名称作为关键字,以“关键字=值”的形式来传递参数,不要求顺序。
示例:
def reg_stu(name,age,gender):
print(f"姓名:{name},年龄:{age},性别:{gender}")
reg_stu(name="kela",age=18,gender="女") # 姓名:kela,年龄:18,性别:女
reg_stu(name="上好佳",gender="女",age=18) # 姓名:上好佳,年龄:18,性别:女
3.1.3 混合参数
可以即使用位置参数又使用关键字参数,但是关键字参数一定要在位置参数之后,位置参数一定是和形参一一对应的有顺序要求,关键字参数之间没有顺序要求。
示例:
def reg_stu(name,age,gender):
print(f"姓名:{name},年龄:{age},性别:{gender}")
reg_stu("kela",18,gender="女") # 姓名:kela,年龄:18,性别:女
reg_stu("上好佳",gender="女",age=18) # 姓名:上好佳,年龄:18,性别:女
3.1.4 对比两种传参方式
| 位置参数 | func(a, b, c) | 简洁,依赖顺序,可读性差 | 参数少,且含义非常明确时 |
| 关键字参数 | func(a=1, c=3, b=2) | 较为繁琐,顺序无关,可读性强 | 参数较多、含义容易混淆,或想明确指定某个参数时 |
核心原则:确保代码在几个月后,能一眼看懂每个参数的意义。
3.1.5 对比 C++ 和 Java 传参方式
| 关键字传参 | ✅ 支持,调用时指定参数名 | ❌ 不支持 | ❌ 不支持 |
| 传参方式 | 位置,关键字或者两种混合 | 仅位置传参 | 仅位置传参 |
3.2 默认参数
在定义函数时,可以为形参指定一个默认值。调用时若未提供该参数,则使用默认值。带有默认值的形参必须定义在所有没有默认值的形参之后。
示例:
def reg_stu(name,age,gender,city="上海",hobby="跳舞"):
print(f"姓名:{name},年龄:{age},性别:{gender},城市:{city},爱好:{hobby}")
# 使用默认city和hobby
reg_stu("上好佳",18,"女") # 姓名:上好佳,年龄:18,性别:女,城市:上海,爱好:跳舞
# 传入参数city,使用默认hobby
reg_stu("上好佳",18,"女",city="北京") # 姓名:上好佳,年龄:18,性别:女,城市:北京,爱好:跳舞
# 使用默认参数city,传入参数hobby,注意此时hobby要使用关键字传参,否则系统以为传入参数city,hobby是默认
reg_stu("上好佳",18,"女",hobby="唱歌") # 姓名:上好佳,年龄:18,性别:女,城市:上海,爱好:唱歌
对比C++和Java默认参数
| 默认参数 | ✅ 支持 | ❌ 不支持 | ✅ 支持 |
3.3 不定长参数
当不确定函数会接收多少个参数时,可以使用不定长参数。
3.3.1 接收任意数量的位置参数(*args)
在形参前添加一个星号(*),如 args,可以将调用时传入的所有位置参数收集并打包成一个元组(tuple)。形参名 args 是约定俗成的名称,可替换为其他名称,但星号()必不可少。
示例:
def cal_data(*args):
print(args)
print(type(args))
cal_data(1,2,3,4,5,6,7,8,9,10,11) # (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) <class 'tuple'>
cal_data(1,2,3) # (1, 2, 3) <class 'tuple'>
3.3.2 接收任意数量的关键字参数(**kwargs)
在形参前添加两个星号(**),如 **kwargs,可以将调用时传入的所有关键字参数收集并打包成一个字典(dict)。形参名 kwargs 同样是约定俗成的名称。
示例:
def cal_data(*args,**kwargs):
print(args) # (1, 2, 3)
print(type(args)) # <class 'tuple'>
print(kwargs) # {'name': '123', 'age': '18'}
print(type(kwargs)) # <class 'dict'>
cal_data(1,2,3,name="123",age="18")
3.3.3 组合使用 *args 和 **kwargs
两者可以同时在函数定义中使用,*args 必须在 **kwargs 之前。这允许一个函数接收任意形式和大小的参数集合,非常灵活。
3.3.4 对比C++和Java重载
在C++/Java中,存在方法/函数重载,即方法/函数名相同,参数列表(类型,顺序,个数)不同。Python 作为动态语言,其实现方式有显著区别。
| 参数个数不同 | 需定义多个同名函数,参数个数不同(重载) | 使用*args、**kwargs | Python只定义一个函数,需在函数内判断参数数量 |
| 参数类型不同 | 需定义多个同名函数,参数类型不同 | 无需特别处理。Python 是动态类型语言,一个函数可处理任意类型的参数(必要时可在函数内部进行类型检查或依赖“鸭子类型”) | Python是动态类型语言 |
| 参数顺序不同 | 顺序不同可构成重载(但不常用) | 通过关键字参数调用 | Python 的关键字传参提供了极强的可读性和灵活性 |
代码对比示例:
- Python (一个函数,动态处理)
def add(a, b):
return a + b # Python会根据a,b的实际类型自动调用对应的+运算符
# 测试
print(add(1, 2)) # 3 – 整数相加
print(add(1.5, 2.5)) # 4.0 – 浮点数相加
print(add("Hello", "World")) # HelloWorld – 字符串连接
- Java (重载):
// Java中必须为不同类型分别实现
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public String add(String a, String b) {
return a + b;
}
3.4 参数是函数
函数本身也可以作为参数传递给另一个函数
示例:
def add(x,y):
return x+y
def substract(x,y):
return x–y
def cal(x,y,oper):
return oper(x,y)
print(cal(5,3,add)) # 8
print(cal(5,3,substract)) # 2
四、匿名函数
匿名函数指的是没有具体函数名的函数,它通过lambda关键字创建,通常用于简单、一次性的操作场景。
4.1 基本语法:
lambda 参数列表 : 表达式
- lambda:关键字,表示创建匿名函数
- 参数列表:与普通函数的参数类似,可以有0个或多个参数
- 表达式:只能有一个表达式,该表达式的结果就是函数的返回值
4.2 何时使用Lambda表达式?
4.3 对比 C++ Lambda
| 完整语法 | [捕获](参数) -> 返回类型 { 函数体 } | lambda 参数: 表达式 |
| 简化写法 | [](int x){return x*x;} | lambda x: x*x |
| 多语句 | 支持包含多条语句和复杂逻辑:{stmt1; stmt2;} | ❌ 不支持,只能是单个表达式 |
| 返回值 | 自动推导或显式指定 | 表达式的计算结果 |
五、如何设计一个函数?
当你要创建一个新函数时,可以遵循以下流程思考:
这些刚好对应了函数说明文档的三项。
六、变量的作用域
作用域决定了变量的“可见性”,即在程序的哪个部分可以访问哪个变量。
6.1 局部变量与全局变量
- 局部变量:在函数内部定义的变量。只能在定义它的函数内部访问。
- 全局变量:在函数外部(模块层面)定义的变量。在整个模块内都可访问。
x = 10
def func():
x = 20 # 创建新的局部变量,不修改全局x
print(x) # 20(局部)
func()
print(x) # 10(全局未变)
6.2 使用global关键字
如果需要在函数内部修改全局变量的值,而不是创建一个同名的局部变量,需要使用 global 关键字进行声明。
x = 10
def func():
global x
x = 20 # 全局x修改为20
print(x) # 20
func()
print(x) # 20
6.3 对比C++和Java类似情况
- C++:局部变量隐藏全局变量
int x = 10;
void func() {
int x = 20; // 定义新的局部变量,隐藏全局x
// 使用::访问全局变量
std::cout << x; // 20(局部)
std::cout << ::x; // 10(全局)
}
- Java:局部变量隐藏成员变量
public class Test {
int x = 10;
void func() {
int x = 20; // 局部变量隐藏实例变量
System.out.println(x); // 20(局部)
System.out.println(this.x); // 10(实例变量)
}
}
6.4 使用场景以及注意事项
于是我在想是否可以利用global关键字在函数体内改变变量的值,但是应谨慎使用 global。过度使用会破坏函数的封装性,使程序难以理解和维护。更好的做法是将需要修改的值作为参数传入,并通过返回值传出。
七、个人思考:参数传递
7.1 变量存储的是什么?
变量存储的是一个 “值”。这个值有两种可能:
- 基本数据本身
- 内存地址
7.2 参数传递的本质
参数传递是一个 “赋值” 过程。具体流程为:将实参的值复制一份,赋予给形参。
- 如果实参的值是一个数据值,形参得到的是该数据的拷贝
- 如果实参的值是一个地址值,形参得到的是该地址的拷贝
关键在于:当形参获得地址拷贝后,可以通过该地址找到并修改其指向的原对象的内容。
7.3 能否通过修改形参来改变实参?
我认为这取决于编程语言所提供的语法和方法。
- 即使形参获得了某个对象的地址,如果语言没有提供任何方法来修改该地址内存放的数据,就无法改变数据值。
- 如果语言为其定义了修改自身内容的方法(如 append, 下标赋值),那么通过形参持有的地址拷贝,就可以调用这些方法来修改原对象的内容,从而实现“改变实参所看到的数据”。
核心结论:底层上,我们都是通过地址来访问和修改内存数据的。但能否通过形参改变实参,取决于编译器/语言是否为该类型定义了“通过地址修改其内容”的合法操作。
7.4 不同语言分析
7.4.1 Python
- 变量存储:存储的是对象的引用(即地址)。
- 参数传递:传递的是对象引用的拷贝。
- 修改行为:
- 不可变对象(int, tuple 等):由于没有提供修改自身的方法,任何更改都会创建新对象。无法通过形参修改实参指向的内容。
- 可变对象(list, dict 等):提供了 append、pop 等方法。可以通过形参持有的地址拷贝调用这些方法,从而修改原对象,影响实参。
- 注意:使用 = 赋值,是让变量指向一个新对象(改变其存储的地址),这并非“修改原对象”。
7.4.2 Java
-
变量存储:
- 基本类型(int, double 等):存储数据值本身
- 对象类型(数组、类实例):存储对象的引用(地址)
-
参数传递:
- 基本类型:传递值的拷贝
- 对象类型:传递引用的拷贝
-
修改行为:
类似的对不可改变对象无法修改,对可变对象可以通过形参持有的引用拷贝,调用其方法或直接操作元素来修改原对象的内容,影响实参。若想改变基本类型实参的值,需将其包装成对象或通过返回值实现。
7.4.3 C++
- 值传递:形参与实参是两个独立对象,修改形参不影响实参。
- 指针传递:传递指针(地址)的拷贝。可以通过解引用(*ptr)修改指针所指向的原对象,但无法改变实参指针本身存储的地址。
- 引用传递:传递的是实参的别名,可以修改实参的值。
所以我认为:能否“修改形参改变实参”,不单纯取决于“传值”还是“传址”,而是由该语言中,实参所指向的数据类型是否可变,以及是否提供了合法的修改途径共同决定的。
以上为个人学习总结,旨在梳理个人理解。如有疏漏或不当之处,欢迎指正与交流。
网硕互联帮助中心







评论前必须登录!
注册