14.Python中函数完全指南

Python 中函数完全指南

本文档面向零基础新手,详细讲解 函数 是什么、形参与实参位置实参与关键字实参默认值返回值可选实参返回字典传递列表与禁止修改列表任意数量实参,以及 *args 与 **kwargs 的区别,并配有大量示例。


第一部分:函数是什么?

一、函数的概念

函数 是一段有名字的、可重复使用的代码块。你可以把“要做的一件事”写进函数,以后需要时调用这个函数名即可,不用重复写同样一段代码。

好处:

  • 复用:写一次,多处调用
  • 清晰:给代码块起名,逻辑更易读
  • 维护:改一处,所有调用处都生效

定义函数的基本语法:

def 函数名(参数1, 参数2, ...):
    函数体(缩进的代码)
    return 返回值   # 可选,没有则返回 None

注意:

  • 定义用 def,后面跟函数名和一对圆括号,括号里可以写参数(也可以不写)
  • 括号后要有冒号 :
  • 函数体必须缩进(一般 4 个空格)

调用函数:函数名(实参1, 实参2, ...) 即可。

def say_hello():
    print("你好!")

say_hello()   # 调用,输出:你好!
say_hello()   # 再次调用, again 输出:你好!

二、形参和实参

  • 形参(parameter):定义函数时,写在 def 函数名(...) 括号里的变量名,用来“占位”,表示“这个函数需要这几个数据”。
  • 实参(argument)调用函数时,你真正传进去的具体值,会赋给对应的形参。

可以简单记:

  • 形参 = 函数定义里的“占位符”
  • 实参 = 调用时传入的“实际值”

示例:

def greet(name):     # name 是形参
    print("你好,", name)

greet("小明")        # "小明" 是实参,传给形参 name
greet("小红")        # "小红" 是实参

输出:

你好, 小明
你好, 小红

这里:形参name实参"小明""小红"。调用时,实参按顺序(或按名字)传给形参,函数体内用到的就是传入的值。


第二部分:传递实参——位置实参与关键字实参

三、位置实参(按顺序对应)

位置实参:调用时按形参定义的顺序依次传入实参,第 1 个实参给第 1 个形参,第 2 个给第 2 个,以此类推。

def describe_pet(animal, name):
    print("我有一只", animal, ",它叫", name)

describe_pet("狗", "旺财")    # animal="狗", name="旺财"
describe_pet("猫", "咪咪")    # animal="猫", name="咪咪"

输出:

我有一只 狗 ,它叫 旺财
我有一只 猫 ,它叫 咪咪

注意: 顺序写反,含义就反了:

describe_pet("旺财", "狗")    # 会变成“我有一只 旺财 ,它叫 狗”,不符合本意

所以位置实参要严格按形参顺序传。


四、关键字实参(按名字对应)

关键字实参:调用时写成 形参名=值 的形式,这样顺序可以打乱,也不会搞混。

def describe_pet(animal, name):
    print("我有一只", animal, ",它叫", name)

describe_pet(name="旺财", animal="狗")   # 顺序和定义不同也可以
describe_pet(animal="猫", name="咪咪")

效果与按顺序传 describe_pet("狗", "旺财") 一样。适合参数较多容易记错顺序时使用。


五、位置实参与关键字实参混用

同一次调用里,可以前面用位置实参,后面用关键字实参。但位置实参必须写在关键字实参前面,否则会报错。

describe_pet("狗", name="旺财")   # 正确:第一个位置给 animal,name 用关键字
# describe_pet(animal="狗", "旺财")   # 错误:关键字实参不能出现在位置实参前面

第三部分:默认值

六、给形参指定默认值

定义函数时,可以给某些形参赋一个默认值。调用时如果不传这个参数,就使用默认值;传了则用你传的值。

语法: 形参名 = 默认值

def describe_pet(name, animal="狗"):
    print("我有一只", animal, ",它叫", name)

describe_pet("旺财")           # 只传 name,animal 用默认值 "狗"
describe_pet("咪咪", "猫")     # 两个都传,animal 为 "猫"

输出:

我有一只 狗 ,它叫 旺财
我有一只 猫 ,它叫 咪咪

要点: 带默认值的形参一般放在没有默认值的形参后面,否则调用时容易混淆(Python 允许把默认参数放前面,但那样调用就必须用关键字,不推荐新手这样写)。

推荐:

def func(a, b, c=0, d=1):   # 无默认值的 a、b 在前
    pass

第四部分:返回值

七、用 return 返回结果

函数可以通过 return 把计算结果(或任意值)“还给”调用者。调用处可以把这个结果赋给变量或参与运算。

语法: return 表达式。执行到 return 后,函数立即结束,后面的代码不会执行。

def add(a, b):
    s = a + b
    return s

result = add(3, 5)
print(result)   # 8

无 return 或只写 return: 函数默认返回 None

def no_return():
    print("只打印,不返回")

x = no_return()
print(x)   # None

八、返回多个值(实质是元组)

Python 允许一次 return 多个值,用逗号分隔。实际上返回的是一个元组,调用处可以解包成多个变量。

def get_name_and_age():
    return "小明", 18

name, age = get_name_and_age()
print(name, age)   # 小明 18

# 也可以整体接收
t = get_name_and_age()
print(t)   # ('小明', 18)

第五部分:让实参变成可选的

九、用默认值实现“可选实参”

有时某个参数有时传、有时不传,可以给该形参设一个默认值(例如 None 或空字符串),在函数里再判断:若未传(即等于默认值),就按“未提供”处理。

示例:中间名可选

def get_full_name(first_name, last_name, middle_name=None):
    if middle_name is None:
        return first_name + " " + last_name
    else:
        return first_name + " " + middle_name + " " + last_name

print(get_full_name("张", "三"))              # 张 三
print(get_full_name("张", "三", "小"))       # 张 小 三

这样 middle_name 就是“可选实参”:不传就用 None,函数内部再分支。


第六部分:返回字典

十、函数返回一个字典

return 可以返回任意类型,包括字典。适合“组装一条记录”再返回给调用者。

def build_person(name, age):
    person = {
        "name": name,
        "age": age,
    }
    return person

p = build_person("小明", 18)
print(p)   # {'name': '小明', 'age': 18}

可选字段用默认值: 若某些键“有时有、有时没有”,可以先用默认值(如 None),只在有值时写入字典。

def build_person(name, age, city=None):
    person = {"name": name, "age": age}
    if city is not None:
        person["city"] = city
    return person

print(build_person("小明", 18))           # {'name': '小明', 'age': 18}
print(build_person("小红", 19, "北京"))   # {'name': '小红', 'age': 19, 'city': '北京'}

第七部分:传递列表

十一、把列表作为实参传入函数

实参可以是列表(或任何对象)。函数内部通过形参拿到的是同一个列表对象的引用,所以可以在函数里遍历、读取、修改列表内容。

def greet_all(names):
    for name in names:
        print("你好,", name)

users = ["小明", "小红", "小刚"]
greet_all(users)

输出:

你好, 小明
你好, 小红
你好, 小刚

十二、在函数内修改列表会影响原列表

因为传的是引用,函数里对列表做 append、remove、修改元素 等操作,都会作用在调用者手里的那个列表上。

def append_one(lst):
    lst.append(1)

nums = [10, 20]
append_one(nums)
print(nums)   # [10, 20, 1]

这是 Python 的“可变对象”行为:形参和实参指向同一块数据,所以“在函数里改列表”就等于“改外面的列表”。


第八部分:禁止函数修改列表(传副本)

十三、若不想让函数改原列表,可传副本

如果希望函数只对列表做操作,但不改变调用者原来的列表,可以在调用时传入列表的副本,而不是原列表本身。

做法:切片 列表[:]list(列表) 造一个副本,再把这个副本传给函数。

def append_one(lst):
    lst.append(1)

nums = [10, 20]
append_one(nums[:])   # 传的是副本,原列表不变
print(nums)           # [10, 20]

或者:

append_one(list(nums))
print(nums)   # [10, 20]

要点: 函数内部拿到的若是副本,对副本的修改不会影响外面的原列表。需要“禁止函数修改列表”时,就在调用处传副本


第九部分:传递任意数量的实参

十四、用 *形参名 接收“任意个位置实参”

有时希望函数能接受任意多个位置实参(个数不固定),可以在形参前加一个 *,例如 *args。这样,多出来的所有位置实参会被收进一个元组里,在函数内通过这个形参使用。

示例: 求任意个数的和

def add_many(*args):
    total = 0
    for n in args:
        total = total + n
    return total

print(add_many(1, 2, 3))        # 6
print(add_many(1, 2, 3, 4, 5))  # 15

这里 args 在函数里就是一个元组,例如 add_many(1, 2, 3) 时,args == (1, 2, 3)

注意: 名字不一定要叫 args,习惯上常用 *args 表示“多出来的位置实参”,但 * 后面的名字可以自己取,例如 *numbers


十五、用 **形参名 接收“任意个关键字实参”

如果希望函数能接受任意多个关键字实参(键=值 形式),可以用 **形参名,例如 **kwargs。多出来的所有关键字实参会被收进一个字典里:键是参数名,值是对应的实参值。

def build_profile(**kwargs):
    profile = {}
    for key, value in kwargs.items():
        profile[key] = value
    return profile

p = build_profile(name="小明", age=18, city="北京")
print(p)   # {'name': '小明', 'age': 18, 'city': '北京'}

十六、*args 和 **kwargs 的区别(重要)

写法 含义 调用时对应写法 函数内得到的类型
*args 接收多余的位置实参 func(1, 2, 3) 中 1,2,3 元组 tuple
**kwargs 接收多余的关键字实参 func(a=1, b=2) 中 a=1,b=2 字典 dict

记忆:

  • *args:多出来的按顺序的实参 → 收成元组
  • **kwargs:多出来的名字=值 → 收成字典

可以同时使用: 定义时顺序一般为:普通形参、*args、**kwargs。

def mix(a, b, *args, **kwargs):
    print("a =", a)
    print("b =", b)
    print("args =", args)
    print("kwargs =", kwargs)

mix(1, 2, 3, 4, x=5, y=6)

输出:

a = 1
b = 2
args = (3, 4)
kwargs = {'x': 5, 'y': 6}

常见用法:

  • *args:写“可以接受任意个参数”的函数(如求和、打印多行等)
  • **kwargs:写“可以接受任意个键值对”的函数(如组装配置、构造字典、包装其他函数等)

十七、综合示例:位置、默认值、*args、**kwargs 混用

推荐形参顺序: 普通位置形参 → 默认值形参 → *args → **kwargs

def example(a, b, c=0, *args, **kwargs):
    print("a:", a, "b:", b, "c:", c)
    print("args:", args)
    print("kwargs:", kwargs)

example(1, 2)
# a: 1 b: 2 c: 0
# args: ()
# kwargs: {}

example(1, 2, 3, 4, 5, x=10, y=20)
# a: 1 b: 2 c: 3
# args: (4, 5)
# kwargs: {'x': 10, 'y': 20}

第十部分:小结与常见注意点

十八、形参与实参、传参方式小结

概念/写法 说明
形参 定义时括号里的变量,占位用
实参 调用时传入的具体值
位置实参 按顺序传,第 1 个实参给第 1 个形参
关键字实参 形参名=值,顺序可打乱
默认值 形参 = 默认值,调用可不传该参数
return 返回一个值(或元组、字典等),不写则返回 None

十九、传递列表与“禁止修改”

需求 做法
函数内可以改列表 直接传列表:func(my_list)
函数内不能改原列表 传副本:func(my_list[:])func(list(my_list))

二十、*args 与 **kwargs 速查

形参写法 接收什么 类型
*args 多余的位置实参 元组
**kwargs 多余的关键字实参 字典

二十一、新手常见陷阱

  1. 默认值用可变对象(列表、字典)
    默认值在定义时只求值一次,若写 def f(a=[]),多次调用且不传 a 时,会共用同一个列表。应写成 def f(a=None),函数里再 if a is None: a = []

  2. return 之后还有代码
    return 后面的语句不会执行,若逻辑要分多步,注意 return 写对位置。

  3. 实参顺序
    混用位置与关键字时,位置实参必须在关键字实参前面。

发表评论