云计算百科
云计算领域专业知识百科平台

【Python基础|DAY03】Python函数详解:从语法到参数传递的思考

文章目录

  • 📚 今日学习目标
  • 📦 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, "第二组")

    # 多组计算只需要简单的一行调用

  • 更贴切的生活案例比喻:假设你每天都需要“泡咖啡”。这个流程可以封装成一个 make_coffee() 函数,里面包含了“研磨豆子”、“加热水”、“冲泡”等步骤。每天调用一次这个函数,就能得到一杯咖啡。

  • 二、函数的定义和调用

    函数必须先定义、后调用,就像必须先制造工具,才能使用它。

    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 传参方式

    特性PythonJavaC++
    关键字传参 ✅ 支持,调用时指定参数名 ❌ 不支持 ❌ 不支持
    传参方式 位置,关键字或者两种混合 仅位置传参 仅位置传参

    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默认参数

    特性PythonJavaC++
    默认参数 ✅ 支持 ❌ 不支持 ✅ 支持

    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 作为动态语言,其实现方式有显著区别。

    特性Java/C++ 重载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 xy

    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

    方面C++ LambdaPython 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)修改指针所指向的原对象,但无法改变实参指针本身存储的地址。
    • 引用传递:传递的是实参的别名,可以修改实参的值。

    所以我认为:能否“修改形参改变实参”,不单纯取决于“传值”还是“传址”,而是由该语言中,实参所指向的数据类型是否可变,以及是否提供了合法的修改途径共同决定的。


    以上为个人学习总结,旨在梳理个人理解。如有疏漏或不当之处,欢迎指正与交流。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Python基础|DAY03】Python函数详解:从语法到参数传递的思考
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!