Day 32 函数专题1:函数定义与参数

@浙大疏锦行

def: 关键字,表示开始定义一个函数。

function_name: 函数的名称,应遵循Python的命名约定(通常是小写字母和下划线,例如 calculate_area,用英文单词含义和下划线来作为函数名)。

parameter1, parameter2, ...: 函数的参数(也叫形参),是函数在被调用时接收的输入值。参数是可选的。

(): 参数列表必须放在圆括号中,即使没有参数,括号也不能省略。

: 冒号表示函数定义的头部结束,接下来是缩进的函数体。

Docstring (文档字符串): 位于函数定义第一行的多行字符串(通常用三引号 """Docstring goes here""")。用于解释函数的作用、参数、返回值等。可以通过 help(function_name) 或 function_name.doc 查看。这个写法可选,为了后续维护和查看,建议加上这一段更加规范

函数体 (Function Body): 缩进的代码块,包含实现函数功能的语句。

return value: return 语句用于从函数中返回一个值。如果函数没有 return 语句,或者 return 后面没有值,它会自动返回 None。一个函数可以有多个 return 语句(例如在不同的条件分支中)。

不带参数的函数

python 复制代码
# 定义一个简单的问候函数
def greet():
    """打印一句问候语。"""
    message = "大家好!欢迎学习Python函数定义!"
    print(message)

greet()

带函数的参数

函数的参数我们有如下称呼:

Parameters (形参): 在函数定义中列出的变量名 (如 name, feature1, feature2)。

Arguments (实参): 在函数调用时传递给函数的实际值 (如 "张三", 10, 25),也就是实际的数值(实参)传给了 形参(定义时候的变量)

注意点: 定义的时候把函数的参数称之为形参,调用的时候把函数的参数称之为实参。

python 复制代码
# 定义一个带一个参数的问候函数
def greet_person(name):
    """根据给定的名字打印问候语。

    Args:
        name (str): 要问候的人的名字。
    """
    message = f"你好, {name}! 很高兴认识你。"
    print(message)

greet_person("张三")  # 输出: 你好, 张三! 很高兴认识你。

# 定义一个带多个参数的函数 (例如,在机器学习中计算两个特征的和)
def add_features(feature1, feature2):
    """计算两个数值特征的和。

    Args:
        feature1 (float or int): 第一个特征值。
        feature2 (float or int): 第二个特征值。
    """
    total = feature1 + feature2
    print(f"{feature1} + {feature2} = {total}")

add_features(10, 25)       # 输出: 10 + 25 = 35

带返回值的参数

python 复制代码
# 定义一个计算和并返回结果的函数
def calculate_sum(a, b):
    """计算两个数的和并返回结果。

    Args:
        a (float or int): 第一个数。
        b (float or int): 第二个数。

    Returns:
        float or int: 两个数的和。
    """
    result = a + b
    return result
    print("hhh")

calculate_sum(2, 3)

此时,注意到,print("hhh")这个代码并没有被执行,因为函数在遇到return语句时,就会立即返回,而不会继续执行函数后面的代码。

其次,如果没有return语句,或者return后面不带任何参数,那么函数也会返回None(不要把执行的操作理解为返回值)。

变量作用域

理解变量在何处可见和可访问非常重要。

局部变量 (Local Variables): 在函数内部定义的变量,只在该函数内部有效。当函数执行完毕后,局部变量通常会被销毁。

全局变量 (Global Variables): 在所有函数外部定义的变量,可以在程序的任何地方被访问(但在函数内部修改全局变量需要特殊声明,如 global 关键字,初学阶段可以先避免)。

python 复制代码
print("\n--- 变量作用域示例 ---")
global_var = "我是一个全局变量"

def scope_test():
    local_var = "我是一个局部变量"
    print(f"在函数内部,可以看到局部变量: '{local_var}'")
    print(f"在函数内部,也可以看到全局变量: '{global_var}'")
    # global_var = "尝试在函数内修改全局变量" # 如果没有 global 声明,这会创建一个新的局部变量 global_var
    # print(f"在函数内部,修改后的 '全局' 变量: '{global_var}'")

scope_test()

print(f"\n在函数外部,可以看到全局变量: '{global_var}'")

函数参数类型

在我们ctrl跳转到一些函数内部的时候,会发现写法相对我们日常定义的简单函数更加复杂,主要是参数形式比较丰富

参数有以下类型:

位置参数 (Positional Arguments): 调用时按顺序匹配。

默认参数值 (Default Parameter Values): 定义函数时给参数指定默认值,调用时如果未提供该参数,则使用默认值。

可变数量参数 (*args 和 **kwargs):

*args: 将多余的位置参数收集为一个元组。

**kwargs: 将多余的关键字参数收集为一个字典。

可能你还听过关键字参数 (Keyword Arguments)这个说法,但是他并非是一种参数,而是一种传递参数的手段: 调用时通过 参数名=值 的形式指定,可以不按顺序。他可以传位置参数的值,也可以传默认参数的值,也可以传可变参数的值,也可以传关键字参数的值。为了可读性,更推荐对所有参数均采取关键字参数传递。

位置参数

python 复制代码
def describe_pet(animal_type, pet_name):
    """显示宠物的信息。"""
    print(f"\n我有一只 {animal_type}.")
    print(f"我的 {animal_type} 的名字叫 {pet_name.title()}.")

describe_pet("猫", "咪咪") # 使用关键字参数,顺序不重要

#我有一只 猫.
#我的 猫 的名字叫 咪咪.

默认参数

python 复制代码
def describe_pet_default(pet_name, animal_type="狗"): # animal_type 有默认值
    """显示宠物的信息,动物类型默认为狗。"""
    print(f"我有一只 {animal_type}.")
    print(f"我的 {animal_type} 的名字叫 {pet_name.title()}.")

describe_pet_default(pet_name="小黑") # animal_type 使用默认值 "狗"
describe_pet_default(pet_name="雪球", animal_type="仓鼠") # 提供 animal_type,覆盖默认值
# 注意:带默认值的参数必须放在没有默认值的参数之后

#我有一只 狗.
#我的 狗 的名字叫 小黑.
#我有一只 仓鼠.
#我的 仓鼠 的名字叫 雪球.

*args (收集位置参数)

*args: 将多余的位置参数收集为一个元组。

当函数被调用时,Python 会先尝试用调用时提供的位置参数去填充函数定义中所有明确定义的、非关键字的形参 (也就是那些普通的,没有 * 或 ** 前缀的参数,包括有默认值的和没有默认值的)。

如果在填充完所有这些明确定义的形参后,调用时还有剩余的位置参数,那么这些"多余的"位置参数就会被收集起来,形成一个元组 (tuple),并赋值给 *args 指定的那个变量(通常就是 args)。

如果调用时提供的位置参数数量正好等于或少于明确定义的形参数量(且满足了所有必需参数),那么 *args 就会是一个空元组 ()。

python 复制代码
def make_pizza(size, *toppings):
    """概述要制作的比萨。
    *toppings 会将所有额外的位臵参数收集到一个元组中。
    """
    print(f"\n制作一个 {size} 寸的比萨,配料如下:")
    if toppings: # 只要toppings不为空元组,就会执行
        for topping in toppings:
            print(f"- {topping}")
    else:
        print("- 原味 (无额外配料)")

make_pizza(12, "蘑菇")
make_pizza(16, "香肠", "青椒", "洋葱")
make_pizza(9) # toppings 会是空元组

#制作一个 12 寸的比萨,配料如下:
#- 蘑菇

#制作一个 16 寸的比萨,配料如下:
#- 香肠
#- 青椒
#- 洋葱

#制作一个 9 寸的比萨,配料如下:
#- 原味 (无额外配料)

**kwargs (收集关键字参数)

**kwargs: 将多余的关键字参数收集为一个字典。

当函数被调用时,Python 会先处理完所有的位置参数(包括填充明确定义的形参和收集到 *args 中)。

然后,Python 会看调用时提供的关键字参数 (形如 name=value)。它会尝试用这些关键字参数去填充函数定义中所有与关键字同名的、明确定义的形参(这些形参可能之前没有被位置参数填充)。

如果在填充完所有能通过名字匹配上的明确定义的形参后,调用时还有剩余的关键字参数(即这些关键字参数的名字在函数定义中没有对应的明确形参名),那么这些"多余的"关键字参数就会被收集起来,形成一个字典 (dictionary),并赋值给 **kwargs 指定的那个变量(通常就是 kwargs)。

如果调用时提供的所有关键字参数都能在函数定义中找到对应的明确形参名,那么 **kwargs 就会是一个空字典 {}。

python 复制代码
def build_profile(first_name, last_name, **user_info):
    """创建一个字典,其中包含我们知道的有关用户的一切。
    **user_info 会将所有额外的关键字参数收集到一个字典中。
    """
    profile = {}
    profile['first_name'] = first_name
    profile['last_name'] = last_name
    for key, value in user_info.items():
        profile[key] = value
    return profile

user_profile = build_profile('爱因斯坦', '阿尔伯特',
                             location='普林斯顿',
                             field='物理学',
                             hobby='小提琴')
print(f"\n用户信息: {user_profile}")
# 输出: {'first_name': '爱因斯坦', 'last_name': '阿尔伯特', 'location': '普林斯顿', 'field': '物理学', 'hobby': '小提琴'}

#用户信息: {'first_name': '爱因斯坦', 'last_name': '阿尔伯特', 'location': '普林斯顿', 'field': '物理学', 'hobby': '小提琴'}

*args 和 **kwargs 的核心目的是让函数能够接收不定数量的参数,并以元组和字典的形式在函数内部进行处理。

也就是说 当位置参数用完了 就自动变成*args,当关键词参数用完了 就自动变成**kwarges

python 复制代码
# 同时出现 *args 和 **kwargs,注意参数的传入顺序
def process_data(id_num, name, *tags, status="pending", **details): # 注意,这里的status 是仅关键字参数,必须通过关键词传值
    print(f"ID: {id_num}")
    print(f"Name: {name}")
    print(f"Tags (*args): {tags}")
    print(f"Status: {status}")          # status 是一个有默认值的普通关键字参数
    print(f"Details (**kwargs): {details}")
    print("-" * 20)

# 调用1:
process_data(101, "Alice", "vip", "new_user", location="USA", age=30)
# ID: 101
# Name: Alice
# Tags (*args): ('vip', 'new_user')  <-- "vip", "new_user" 是多余的位置参数,被 *tags 收集
# Status: pending                    <-- status 使用默认值,因为调用中没有 status=...
# Details (**kwargs): {'location': 'USA', 'age': 30} <-- location 和 age 是多余的关键字参数,被 **details 收集
# --------------------

# 调用2:
process_data(102, "Bob", status="active", department="Sales")
# ID: 102
# Name: Bob
# Tags (*args): ()                   <-- 没有多余的位置参数
# Status: active                     <-- status 被关键字参数 'active' 覆盖
# Details (**kwargs): {'department': 'Sales'} <-- department 是多余的关键字参数
# --------------------

# 调用3:
process_data(103, "Charlie", "admin") # 'admin' 会被 *tags 捕获
# ID: 103
# Name: Charlie
# Tags (*args): ('admin',)
# Status: pending
# Details (**kwargs): {}
# --------------------

# 调用4: (演示关键字参数也可以用于定义中的位置参数)
process_data(name="David", id_num=104, profession="Engineer")
# ID: 104
# Name: David
# Tags (*args): ()
# Status: pending
# Details (**kwargs): {'profession': 'Engineer'}
# --------------------
python 复制代码
import math

def calculate_circle_area(radius):
    try:
        # 处理半径为负的情况,返回0
        if radius < 0:
            return 0
        # 计算圆的面积
        return math.pi * (radius ** 2)
    except:
        # 异常情况(如传入非数字)也返回0
        return 0

# 测试:半径为5、0、-1的情况
print("圆面积(半径5):", calculate_circle_area(5))
print("圆面积(半径0):", calculate_circle_area(0))
print("圆面积(半径-1):", calculate_circle_area(-1))
#圆面积(半径5): 78.53981633974483
#圆面积(半径0): 0.0
#圆面积(半径-1): 0

def calculate_rectangle_area(length, width):
    # 长度或宽度为负时返回0
    if length < 0 or width < 0:
        return 0
    # 计算矩形面积
    return length * width

print("矩形面积(长3,宽4):", calculate_rectangle_area(3, 4))
print("矩形面积(长-2,宽5):", calculate_rectangle_area(-2, 5))
# 矩形面积(长3,宽4): 12
# 矩形面积(长-2,宽5): 0

def calculate_average(*args):
    # 没有传入数字时返回0
    if not args:
        return 0
    # 计算平均值
    return sum(args) / len(args)

print("平均值(1,2,3):", calculate_average(1, 2, 3))
print("平均值(无参数):", calculate_average())
#平均值(1,2,3): 2.0
#平均值(无参数): 0

def print_user_info(user_id, **kwargs):
    # 打印用户ID
    print(f"User ID: {user_id}")
    # 逐行打印额外信息
    if kwargs:
        for key, value in kwargs.items():
            print(f"{key}: {value}")
    else:
        print("No additional information.")

print_user_info(1001, name="Alice", age=25, email="alice@example.com")
#User ID: 1001
#name: Alice
#age: 25
#email: alice@example.com

def describe_shape(shape_name, color="black", **kwargs):
    # 处理尺寸部分
    if kwargs:
        dim_str = ", ".join([f"{k}={v}" for k, v in kwargs.items()])
        dim_part = f"with dimensions: {dim_str}"
    else:
        dim_part = "with no specific dimensions"
    # 拼接描述字符串
    return f"A {color} {shape_name} {dim_part}."

print(describe_shape("circle", "red", radius=5))
print(describe_shape("rectangle", length=10, width=4))
print(describe_shape("square"))
#A red circle with dimensions: radius=5.
#A black rectangle with dimensions: length=10, width=4.
#A black square with no specific dimensions.
相关推荐
yaoxin5211231 小时前
262. Java 集合 - Java 中 ArrayList 与 LinkedList 读取元素性能大对决
java·开发语言
椰萝Yerosius1 小时前
MATLAB简介
开发语言·数学建模·matlab
fruge1 小时前
前端性能优化实战:首屏加载从 3s 优化到 800ms
前端·性能优化
李日灐1 小时前
C++STL:list(双链表)的底层实现 && 部分源码解析
开发语言·c++
zlpzlpzyd1 小时前
vue.js 2和vue.js 3的生命周期与对应的钩子函数区别
前端·javascript·vue.js
无限进步_1 小时前
C语言宏的魔法:探索offsetof与位交换的奇妙世界
c语言·开发语言·windows·后端·算法·visual studio
代码雕刻家1 小时前
C语言关于换行符的注意事项
c语言·开发语言
鸡吃丸子2 小时前
前端需要掌握的关于代理的相关知识
前端
认真敲代码的小火龙2 小时前
【JAVA项目】基于JAVA的图书管理系统
java·开发语言·课程设计