函数

概念

定义: 函数是带名字的代码块,用于完成具体的任务,可以重复使用。
作用: 当程序中需要多次执行同一项任务时,无需反复编写相同代码,只需调用函数即可。
优势: 通过使用函数,程序编写、阅读、测试和修复都更加容易。
执行顺序: Python中的函数必须先定义后使用,与JAVA不同,Python是解释执行的。

语法

def 函数名(参数列表):
    函数体
    [return]

命名规则: 函数名遵循小写蛇形命名法,与变量命名规则一致。

函数定义规则

  • 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
  • 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
  • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
  • 函数内容以冒号起始,并且缩进。
  • return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。

示例

# 打印插入的字符
def print_sign(str):
    print(str)

print_sign("123")

调用机制

执行流程: 程序从上往下执行,遇到函数定义不会立即执行,只有在调用时才会执行函数体内的代码。
调试技巧: 使用debug工具可以逐步查看函数执行过程,包括进入函数体(Step Into)和跳过函数体(Step Over)。

应用实例

问题: 相同功能的代码重复出现,导致代码冗长、维护困难。
例如:

print("床前明月光")
print("-------")
print("疑是地上霜")
print("-------")

函数优化

def print_sign():
    for _ in range(10):
        print("-", end="")
    print()

print("床前明月光")
print_sign()
print("疑是地上霜")
print_sign()

函数的参数

参数的定义

参数概念:函数调用者与函数定义之间传递的数据
参数分类:
形参:函数定义时书写的参数(形式参数)
实参:函数调用时传入的参数(实际参数)

# 优化为用户自定义横线的数量
def print_sign(num):  # 形参
    for _ in range(num):
        print("-", end="")
    print()


print("床前明月光")
print_sign(10)  # 实参
print("疑是地上霜")
print_sign(20)

# print_sign()  # 如不传会报错print_sign() missing 1 required positional argument: 'num'

优势:通过参数提高函数灵活性,同一函数可实现不同效果
注意事项:定义了形参后必须传入对应个数的实参,否则运行报错

参数的默认值

设置方法:在形参后使用等号赋值,如def func(param=10)
调用规则
不传参时使用默认值
传参时覆盖默认值
注意事项:无默认值的形参必须传入实参

# 优化为用户自定义横线的数量,如不传则默认打印10个
def print_sign(num=10):  # 形参
    for _ in range(num):
        print("-", end="")
    print()

print("床前明月光")
print_sign()
print("疑是地上霜")
print_sign(20)
参数类型

位置参数
第一个实参赋值给第一个形参,第二个实参赋值给第二个形参,严格按顺序匹配。
错误示例:若将字符串传给数值型参数,会导致逻辑错误或运行时异常。

关键字参数
通过参数名=值的形式显式指定传参,形参名必须与函数定义一致。
优势:允许颠倒参数顺序,例如func(b=2, a=1)。
限制:混合使用时,关键字参数必须位于位置参数之后,否则报错。

解包参数
若函数的形参是定长参数,可以通过 * 和 ** 对列表、元组、字典等解包传参。

数量必须匹配:容器元素数量需与形参数量严格一致,否则报错。
字典键名约束:​**​dict解包时,字典键必须与形参名完全对应。键名错误或数量不匹配均会导致运行时错误。

可变长参数
在形参前加星号(*)表示接收多个参数
接收的参数会以元组形式存储
如果可变长参数后还有参数,必须使用关键字参数传递
默认值参数规则:
不传参时使用默认值
传参时会覆盖默认值

# 优化为用户自定义横线的数量,如不传则默认打印10个
# 优化为用户自定义横线的数量,传入打印符号,如不传则默认打印10个
def print_sign(num=10, sign="-"):  
    for _ in range(num):
        print(sign, end="")
    print()


print("床前明月光")
print_sign(sign="-", num=10)  # 关键字参数
print("疑是地上霜")
print_sign(20, "*")# 位置参数

# 解包参数
def func(a, b, c):
    return a + b + c


tuple1 = (1, 2, 3)

print(func(*tuple1))  # 6
list1 = (4, 5, 6)
print(func(*list1))  # 15

dict1 = {"a": 1, "b": 2, "c": 3}
print(func(**dict1))  # 6

#可变长参数
def func1(a, *nums):
    print(a, nums)

func1(1, 2, 3, 4, 5)  # 1 (2, 3, 4, 5)

# 如果可变长参数之后还有参数,必须通过关键字参数的方式进行传递
def func2(a, *nums, b):
    print(a, nums, b)

func2(1, 2, 3, 4, 5, 6, b=9)  # 1 (2, 3, 4, 5, 6) 9
强制使用位置参数或关键字参数

可以通过特定符号强制规定参数传递方式

#/ 前的参数必须使用位置传参,* 后的参数必须用关键字传参。
def f(a, b, /, c, d, *, e, f):
    print(a, b, c, d, e, f)

f(1, 2, 3, d=4, e=5, f=6)

深拷贝和浅拷贝

直接赋值浅拷贝深拷贝区别

直接赋值:仅复制引用
浅拷贝:“换汤不换药”(外层新容器,内层旧对象)
深拷贝:“脱胎换骨”(完全独立的新对象树)
典型应用场景:

  • 直接赋值:需要同步修改的场景
  • 浅拷贝:外层结构变化需要独立,但内层数据可共享
  • 深拷贝:需要完全隔离的复杂嵌套结构

python中严格来讲,是没有值传递和引用传递的说法,因为在python中所有的数据都属于对象,都属于引用传递。
在函数体内对参数的修改是否会影响原变量,完全取决于是可变类型还是不可变类型

import copy

"""
# 直接传递
本质:复制对象的地址值(如 list2 = list1)
特点:新旧变量指向同一内存区域
"""


# 传递的参数没有被改变
def func1(a):
    a += 1
    print("函数中a的值", a)  # 11


b = 10
func1(b)
print("实参中b的值", b)  # 10


# 传递的参数改变了
def func2(nums):
    nums[0] += 1
    print("函数中nums的参数值", nums)  # [2, 2, 3]


list1 = [1, 2, 3]
func2(list1)
print(list1)  # [2, 2, 3]

print("-" * 20)


"""
深拷贝 (copy.deepcopy()):

递归地复制对象及其包含的所有子对象。
新对象与原对象完全独立,修改任何层级的数据都不会影响原列表。
"""


def func3(nums):
    list1 = copy.deepcopy(nums)
    list1[3][0] = 0
    print(list1)


list2 = [100, 200, 300, [3, 4, 6]]
func3(list2)
print(list2)

"""
浅拷贝
只复制对象本身(第一层)。
如果列表中包含可变对象(如嵌套列表、字典),新旧列表中的这些子对象仍然指向同一个内存地址。
修改嵌套对象会影响原列表。
"""


def func4(nums):
    list1 = list.copy(nums)
    list1[3][0] = 0
    print(list1)  # [100, 200, 300, [0, 4, 6]]


list2 = [100, 200, 300, [3, 4, 6]]
func4(list2)
print(list2)  # [100, 200, 300, [0, 4, 6]]

return关键字

概念及作用

基本概念:函数通过return关键字将结果返回给调用者,返回到函数调用的位置
关键作用:实现函数调用前后逻辑的串联,使函数结果能在后续代码中被利用
语法特点:return表示结束函数并返回内容,具有双重功能
返回类型:可以返回任意数据类型(字符串、数字、布尔值、列表、元组等)

  • 无return语句的函数默认返回None
  • 接收无返回值函数的结果会得到None(可能引发类型错误)
def sum_fun(a, b):
    return a + b
    # 可以写对个return,但只会执行第一个
    return a
    return b


get_num = sum_fun(4, 2)
if get_num % 2 == 0:
    print("偶数")
else:
    print("奇数")


# 无返回值或不写返回值,默认返回None
def func2(a, b):
    print(a + b)
    return


num1 = func2(1, 2)
print(num1)  # None

多级调用

在一个函数中调用另一个函数,当内层函数执行完之后才会继续执行外层函数。

def func_a():
    print("函数a开始执行")
    print("函数a执行中")
    print("函数a结束执行")


def func_b():
    print("函数b开始执行")
    func_a()
    print("函数b执行中")
    print("函数b结束执行")


"""
函数b开始执行
函数a开始执行
函数a执行中
函数a结束执行
函数b执行中
函数b结束执行
"""
func_b()

递归

简单说就是函数自己调自己
递归的核心是函数自我调用,但需满足以下条件:

  • 逻辑拆分:将大问题分解为若干小问题
  • 终止条件:必须存在明确的递归出口
  • 自我调用:通过函数调用当前函数实现问题分解

未设置终止条件导致无限调用,最终耗尽栈内存空间。

def func1():
    func1()

# 报错RecursionError: maximum recursion depth exceede
func1()
# 求1~n之和 递归实现
def num_sum(nums):
    # get_sum = 0
    # if nums >= 1:
    #     get_sum = nums + num_sum(nums - 1)
    get_sum = nums + num_sum(nums - 1) if nums > 1 else 1
    return get_sum

print(num_sum(5)) # 15

匿名函数

Haskell介绍

纯函数式编程: Haskell是一种标准化的纯函数编程语言,具有非限定性语义和强静态类型特性
函数第一性: 在Haskell中"函数是第一类对象",主要控制结构就是函数
Lambda演算: 以λ演算为基础发展而来,使用希腊字母λ作为标志
历史渊源: 命名源自美国逻辑学家哈斯凯尔·加里,1990年在Miranda语言基础上标准化

匿名函数介绍

核心思想: 不关注函数名称,只关注函数功能(面向函数编程)
语法格式: lambda 参数列表: 表达式
与传统函数区别:

  • 不需要def关键字定义
  • 没有函数名
  • 函数体只能是单个表达式
sum = lambda a, b: a + b

print(sum(10, 20))  # 30s
匿名函数作为内置函数的参数
from functools import reduce

# sorted
"""
将下列学生按学生年龄排序
"""

student_list = [
    {"name": "张三", "age": 36},
    {"name": "李四", "age": 14},
    {"name": "王五", "age": 27},
]

print(sorted(student_list, key=lambda x: x["age"]))

print("-" * 20)

# map,对列表中的元素进行逐个处理
map_result = map(lambda x: x * x, [2, 3, 4, 5])  # 每个数与自身相乘
print(list(map_result))  # [4, 9, 16, 25]

print("-" * 20)
# filter 对元素进行过滤
filter_list = list(filter(lambda x: x % 2 == 0, [2, 3, 5, 6, 8, 9]))  # 筛选出偶数
print(filter_list)  # [2, 6, 8]

# reduce 累积操作

reduce_list = reduce(lambda x, y: x * y, [1, 2, 3, 4, 5, 6])  
print(reduce_list)# 720

作用域

Python中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了哪一部分程序可以访问哪个变量,Python的作用域一共有4种,分别是:

  • L (Local) 局部作用域
    • 局部作用域变量仅在定义它的函数内部有效。
  • E (Enclosing)嵌套作用域 闭包函数外的函数中
    • 嵌套作用域变量存在于外层函数中,可被内层函数访问
  • G (Global) 全局作用域
    • 全局作用域变量在模块层级定义,全局有效
  • B (Built-in) 内建作用域
    • 内建作用域包含Python预定义的函数或变量(如print、len)。当其他作用域均未找到变量时,解释器会查找内建作用域
      以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。以下案例演示各种作用域类型。
# 全局作用域
a = 2
b = 0
def outer():
    c = 2  # 嵌套作用域

    def inner():
        d = 6  # 局部作用域
        print(a, b, c, d) # 2 0 2 6
        # 由于没有在其他作用域找到对应的变量,所以在解释器内部找变量和函数,找到就返回
        print(len)  # <built-in function len>

        # NameError: name 'llm' is not defined  所有都没找到则报错
        # print(llm)

    return inner

in_func = outer()

in_func()

global和nolocal关键字

glbal
  • global关键字用于显式声明使用全局作用域的变量,而非默认作用域的变量
  • 对于可变数据类型(如列表),可直接修改全局变量
  • 对于不可变数据类型(如数值),需使用global关键字声明后才能修改全局变量
nonlocal
  • nonlocal关键字用于声明使用嵌套函数作用域的变量
  • 必须存在嵌套函数结构才能使用
  • 不使用默认局部作用域,而使用外层嵌套函数作用域的变量
num = 100


def fun1():
    # 显式说明使用全局作用域中的变量,而非局部作用域
    global num
    num = 200
    print(num)


fun1()
print(num)  # 200


print("-" * 20)


def fun2():
    nums1 = 100
    print(nums1)  # 100

    def fun3():
        nonlocal nums1
        nums1 = 200
        print(nums1)

    fun3()  # 200


inner_fun = fun2
inner_fun()

更多推荐