当前位置: 首页 > news >正文

Python基础之函数

代码仓库地址:git@github.com:Liucc-123/python_learn.git

函数介绍

函数是组织好的、可重复使用的,用来实现单一、或相关功能的代码段。

函数可以提高应用的模块性和代码的可重复性。python 有许多内置的函数比如 print 打印函数,python 也支持用户实现自己的函数,这类函数也称之为自定义函数。


定义一个函数

定义一个函数需要遵循以下规则:

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


语法

Python 定义函数使用 def 关键字,一般格式如下:

def 函数名(参数列表):函数体

实例

默认情况下,参数是按照顺序参数进行匹配的。

示例:定义hello() 函数,打印输出“hello world”

#!/usr/bin/python3def hello() :print("Hello World!")hello()

更复杂点儿的示例,函数带参数:

#!/usr/bin/python3def max(a, b):if a > b:return aelse:return ba = 4
b = 5
print(max(a, b))

以上输出结果:

:::color1
5

:::

def area(width, height):return  width * heightdef print_welcome(name):print("Welcome", name)print_welcome("李星云")
width=4
height=5
print("面积是:", area(width, height))

以上输出结果:

:::color1
Welcome 李星云

面积是: 20

:::


函数调用

当一个函数定义好之后,可以在另一个函数中调用此函数,或者在 python 命令提示符中调用。

注意:必须有调用方调用函数,这个函数才有作用,否则仅仅定义一个函数是没有任何输出的。

下面例子中,调用了 printme()函数:

#!/usr/bin/python3# 定义函数
def printme( str ):# 打印任何传入的字符串print (str)return# 调用函数
printme("我要调用用户自定义函数!")
printme("再次调用同一函数")

以上示例输出结果:

:::color2
我要调用用户自定义函数!

再次调用同一函数

:::


函数调用机制

说明如下:

  • python 的内存结构可以粗浅的理解为由数据区、代码区及栈组成
    • 代码区:存放 python 代码的地方,python 解释器会将用户代码翻译为指令,然后由 CPU 顺序的执行
    • 栈:程序执行的地方,每调用一个新函数就会开辟一个新栈,当函数返回后,这片内存空间就会被释放
    • 数据区:存放变量、对象的地方
  • 主程序从result = area(4, 5)开始
  • 此时会调用 area()函数,会开辟一个新栈。实参 4, 5 会被分别传递给参数 width 及 height,此时数据区会创建一块区间用来存放变量 width 和 height 的值
  • 继续执行函数 area的函数体width * height得到结果 20,数据区再创建一块空间存放临时变量 值20
  • area 函数执行完毕,出栈,将结果 20返回给调用方也就是主栈 result,此时会释放这个栈空间
  • 继续执行主程序print("面积是:", result),主栈空间释放
  • 控制台打印输出对应的值面积是: 20

函数的传参机制

数值和字符串的传参机制

数值传参机制

示例代码:

# 字符串和数值类型传参机制
def f1(a):print(f"f1() a的值:{a} 地址是:{id(a)}")a += 1print(f"f1() a的值:{a} 地址是:{id(a)}")a = 10
print(f"调用f1()前 a的值:{a} 地址是:{id(a)}")
f1(a)
print(f"调用f1()后 a的值:{a} 地址是:{id(a)}")

:::info
以上示例输出结果:

调用f1()前 a的值:10 地址是:4357169600

f1() a的值:10 地址是:4357169600

f1() a的值:11 地址是:4357169632

调用f1()后 a的值:10 地址是:4357169600

:::

:::color2
说明如下:

  1. 在主栈空间定义了变量 a=10,假设地址值是 0x1122
  2. 调用函数f1(a),在函数内修改变量 a。因为变量a 是数值类型,python会在数据区创建一块新的空间保存修改后的值 11.所以函数外部的变量 a 并不会受到影响
  3. 在函数内打印 id(a),会发现变量 a 的地址已发生改变
  4. 出栈,因为函数内对变量 a 的修改不会影响到外部变量 a,所以打印 id(a),a 的地址值 仍是 0x1122

:::

字符串传参机制

示例代码:

def f2(name):print(f"f2() name:{name} 地址是:{id(name)}")name += "hi"print(f"f2() name的值:{name} 地址是:{id(name)}")name = "tom"
print(f"调用f2()前 name的值:{name} 地址是:{id(name)}")
f2(name)
print(f"调用f2()后 name的值:{name} 地址是:{id(name)}")

:::info
以上示例输出结果:

调用f2()前 name的值:tom 地址是:4307138768

f2() name:tom 地址是:4307138768

f2() name的值:tomhi 地址是:4307139584

调用f2()后 name的值:tom 地址是:4307138768

:::

:::success
总结:数值和字符串类型是不可变数据类型,当该类型的变量的值发生变化时,该变量对应的内存地址也会发生变化。也即不可变类型的数据值如果发生变化,那么这个变量已经不再是原来的那个变量了,即使他们的变量值是一样的。

:::

list、tuple、set 和dict 的传参机制

列表的传参机制
def f1(my_list):print(f"②f1() my_list: {my_list} 地址:{id(my_list)} my_list[0]: {my_list[0]} 第一个元素地址:{id(my_list[0])}")my_list[0] = "陆林轩"print(f"③f1() my_list: {my_list} 地址:{id(my_list)} my_list[0]: {my_list[0]} 第一个元素地址:{id(my_list[0])}")print("-" * 30 + "list" + "-" * 30)
my_list = ["李星云", "姬如雪", "张子凡"]
print(f"①my_list: {my_list} 地址:{id(my_list)} my_list[0]: {my_list[0]} 第一个元素地址:{id(my_list[0])}")
f1(my_list)
print(f"④my_list: {my_list} 地址:{id(my_list)} my_list[0]: {my_list[0]} 第一个元素地址:{id(my_list[0])}")print("-" * 30 + "tuple" + "-" * 30)
my_tuple = ("hi", "ok", "hello")
print(f"①my_tuple: {my_tuple} 地址:{id(my_tuple)}")
f2(my_tuple)
print(f"④my_tuple: {my_tuple} 地址:{id(my_tuple)}")def f3(my_set):print(f"②f3() my_set: {my_set} 地址:{id(my_set)}")my_set.add("红楼")print(f"③f3() my_set: {my_set} 地址:{id(my_set)}")print("-" * 30 + "set" + "-" * 30)
my_set = {"水浒", "西游", "三国"}
print(f"①my_set: {my_set} 地址:{id(my_set)}")
f3(my_set)
print(f"④my_set: {my_set} 地址:{id(my_set)}")def f4(my_dict):print(f"②f4() my_dict: {my_dict} 地址:{id(my_dict)}")my_dict['address'] = "兰若寺"print(f"③f4() my_dict: {my_dict} 地址:{id(my_dict)}")print("-" * 30 + "dict" + "-" * 30)
my_dict = {"name": "小倩", "age": 18}
print(f"①my_dict: {my_dict} 地址:{id(my_dict)}")
f4(my_dict)
print(f"④my_dict: {my_dict} 地址:{id(my_dict)}")

:::color2
列表传参机制:

创建列表后,会在数据区开辟一块空间存储列表,地址比如为 0x9800,里面存放了三个元素
调用 f1 函数,将列表作为参数传递进去,此时开辟一个新的栈空间,指针my_list和主栈的指针 mylist 指向的是同一块空间
f1 函数修改第一个元素的值,是列表第一个元素指向了新的地址(陆林轩),但列表的自身的地址并未发生更改。
所以最终打印可以发现调用 f1 函数前后,列表的地址不会发生更改,只有第一个元素的地址发生了变化。

因此说,列表是可变数据类型,当列表的元素发生更改时,列表的地址并不会发生更改。“可变数据类型”中的“可变”是指变量地址是否会发生变化。

:::


小结

  • python 数据类型主要有整型 int / 浮点型 float / 字符串 str / 布尔值 bool / 元组 tuple / 列表 list / 字典 dict / 集合 set,数据类型分为两个大类,一种是可变数据类型,一种是不可变数据类型。
  • 可变数据类型和不可变数据类型。
    • 可变数据类型:当该数据类型的变量的值发生了变化,如果它的内存地址不变,那么这个数据类型就是可变数据类型。
    • 不可变数据类型:当该数据类型的变量的值发生了变化,如果它的内存地址改变了,那么这个数据类型就是不可变数据类型。
  • python 的数据类型。
    • 不可变数据类型:数值类型(int、float)、bool(布尔)、string(字符串)、tuple(元组)。
    • 可变数据类型:list(列表)、set(集合)、dict(字典)。

递归机制

定义

递归函数,简单来说,就是自己调用自己的函数,它将问题分解为更小的同类子问题,通过不断的自我调用来解决复杂问题。

递归函数的核心特点:

  1. 自我调用:函数内部直接或间接调用自身
  2. 基准条件:递归函数必须具备终止递归的条件
  3. 逐步推进:每次调用都使问题规模向基准条件方向靠拢

递归能解决什么问题?

  • 各种数学问题:汉诺塔、八皇后、阶乘问题、迷宫问题等
  • 一些算法中也会用到递归:快排、归并排序、二分查找、分支算法等
  • 需要用栈数据结构解决的问题

递归举例

1、分析下面代码的执行流程:

def test(n):if n > 2:test(n - 1)print("n =", n)
test(4)

上面代码的执行流程分析如下:

2、阶乘问题

# 阶乘问题
def factorial(n):if n == 1:return 1else:return n * factorial(n - 1)
print(factorial(4))

上面代码的执行流程分析如下:

递归重要规则

  1. 执行一个函数时,就会开辟一个新的栈空间
  2. 函数的变量是独立的,比如每个栈空间的变量 n 都是互相独立的
  3. 递归的走向必须是向递归的基准条件逐渐逼近的,否则就是无限递归了
  4. 当函数执行完毕,或遇到 return 语句,这个函数对应的栈空间和数据区就会被释放,且遵循谁调用,就将结果返回给谁

练习题

  1. 斐波那契问题
## 题目一:请使用递归的方式,求出斐波那契数 1,1,2,3,5,8,13… 给你一个整数 n,求出它的值是多少?
# 斐波那契数列 1,1,2,3,5,8,13…
# f(0)=1, f(1) = 1, f(n) = f(n-1) + f(n-2) (n >= 2)
def fibonacci(n):if n == 0:return 1elif n == 1:return 1else:return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(5))
  1. 猴子吃桃
## 题目二:猴子吃桃问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个。以后每天猴子都吃其中的一半,然后再多吃一个。
# 当到第 10 天时,想再吃时(即还没吃),发现只有 1 个桃子了。问最初共多少个桃子?
"""
第 10天还有 1 个桃子
第9天还有:(1 + 1) * 2 = 4 个
第 8 天还有:(4 + 1) * 2 = 10 个
第 7 天还有:(10 + 1) * 2 = 22 个
...
"""
def eatPeach(day: int) -> int:"""查询这一天还剩多少个桃子:param day: 第几天:return: 这天还剩下几个桃子"""if day == 10:return 1return (eatPeach(day + 1) + 1) * 2# 第一天还有多少个桃子,也即最初有多少个桃子
print(eatPeach(1))
  1. 求函数值
## 题目三:求函数值,已知 f(1) = 3; f(n)= 2*f(n-1)+1;请使用递归的思想,求出 f(n)的值?
def f(n: int) -> int:if n == 1:return 3return 2 * f(n - 1) + 1
print(f(2))
  1. 汉诺塔
## 题目四:汉诺塔: 给定盘子的数量 num,有三个塔 a, b, c,打印出 num 个盘子从 a 塔移动到 c 塔的移动顺序
def hanoi_tower(num, a, b, c):"""打印指定数量 num 个盘子的移动顺序:param num: 盘子的数量:param a: 原始位置:param b: 中间借助位置:param c: 目标移动位置:return:"""if num == 1:# 只有一个盘,将其移动到 C 塔print(f"第1个盘:{a} -> {c}")else:# 多个盘情况:我们认为两个盘,即最下面的盘和上面所有盘# 1.先将上面所有盘移动到 B 塔,这时需要借助 C 塔来完成移动hanoi_tower(num - 1, a, c, b)# 2.然后移动最底下的盘print(f"第{num}个盘:{a} -> {c}")# 3.最后把剩余盘从 B 塔移动到 C 塔,这时需要借助 A 塔来完成移动hanoi_tower(num - 1, b, a, c)# 3个汉诺塔的移动顺序
hanoi_tower(3, "A", "B", "C")

以上示例输出结果如下:

:::success
第1个盘:A -> C

第2个盘:A -> B

第1个盘:C -> B

第3个盘:A -> C

第1个盘:B -> A

第2个盘:B -> C

第1个盘:A -> C

:::


函数作为参数传递

作用

将一个函数作为参数传递给另一个函数使用,提高了代码的复用性。

示例代码如下:

# 定义一个函数,可以返回两个数的最大值
def get_max_val(num1, num2):max_val = num1 if num1 > num2 else num2return max_valdef f1(fun, num1, num2):"""功能:调用fun返回num1和num2的最大值:param fun: 表示接收一个函数:param num1: 传入一个数:param num2: 传入一个数:return: 返回最大值"""return fun(num1, num2)def f2(fun, num1, num2):"""功能:调用fun返回num1和num2的最大值,同时返回两个数的和:param fun::param num1::param num2::return:"""return num1 + num2, fun(num1, num2)print(f1(get_max_val, 10, 20))
print(f2(get_max_val, 10, 20))
sum, max = f2(get_max_val, 10, 20)
print(f"sum: {sum}, max: {max}")

以上示例代码运行结果如下:

:::info
20

(30, 20)

sum: 30, max: 20

:::

示例二:计算器函数

# 定义运算函数
def add(a, b):return a + bdef subtract(a, b):return a - b# 高阶函数:接受运算函数作为参数
def calculate(operation, x, y):return operation(x, y)  # 调用传入的函数# 传递函数作为参数
print(calculate(add, 5, 3))       # 输出: 8
print(calculate(subtract, 10, 4)) # 输出: 6

注意事项

  1. 函数作为参数传递,传递的不是数据,而是业务处理逻辑
  2. 传递函数时使用函数名而非函数调用(不带括号)
  3. 被传递的函数需满足目标函数的参数签名

lambda匿名参数

lambda函数定义

Python使用 lambda 关键字来创建函数。

lambda 函数是一种小型的、匿名的函数,可以有任意数量的参数,但只能有一个表达式。这种特性也决定了 lambda 函数只适合编写简单的函数。

lambda 函数不需要使用 def 关键字来定义函数

常用的场景:以函数的形式传参给其他函数使用,例如在 map()、filter()、reduce()等函数中

lambda 函数特点:

  • lambda 函数是匿名的,它们没有函数名称,只能通过赋值给变量或作为参数传递给其他函数来使用。
  • lambda 函数通常只包含一行代码,这使得它们适用于编写简单的函数。

lambda 函数语法:

lambda arguments: expression
  • lambda是 python 的关键字,用于定义匿名函数
  • arguments 是匿名函数的参数列表,可以为空,也可以有多个
  • expression是一个表达式,用于计算并返回结果

以下示例的 lambda 函数没有参数列表:

f = lambda: print("你好,这是一个无参函数")
f()

输出结果为:

:::color2
你好,这是一个无参函数

:::

以下 lambda 函数用于计算参数 a和 10 的求和结果,并返回:

var = lambda a: a + 10
print(f"结果为{var(15)}")

输出结果为:

:::color2
结果为25

:::

lambda 函数也可以设置多个参数,用逗号隔开。

以下示例用于计算矩形(a,b)的面积:

area = lambda a, b: a * b
print(f"矩形面积为{area(10, 5)}")

输出结果为:

:::color2
矩形面积为50

:::

lambda 函数通常与内置函数如 map()、filter() 和 reduce() 一起使用,以便在集合上执行操作。例如:

numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
print(squared)  # 输出: [1, 4, 9, 16, 25]

使用 lambda 函数与 filter() 一起,筛选偶数:

numbers = [1, 2, 3, 4, 5, 6, 7, 8]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers)  # 输出:[2, 4, 6, 8]

使用 reduce() 和 lambda 表达式计算一个序列的累积乘积:

from functools import reducenumbers = [1, 2, 3, 4, 5]# 使用 reduce() 和 lambda 函数计算乘积
product = reduce(lambda x, y: x * y, numbers)print(product)  # 输出:120

return 语句

return [表达式] 语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的 return 语句返回 None。

:::color2
需要说明的是,None 虽然是空,但也是一个有效的数据,通过内置函数 id()也是有对应的值的。

:::

示例代码:

def getStr():print("hello")res = getStr()
print(res)
print(id(res))

以上输出结果:

:::color2
hello

None

4358644536

:::


全局变量和局部变量

基本介绍

  • 全局变量:定义在函数外部,在整个程序范围内都可以访问,拥有全局作用域
  • 局部变量:定义在函数内部,在函数内部可以访问,拥有局部作用于

代码示例:

# n1 是全局变量
n1 = 100def f1():# n2是局部变量n2 = 200print(n2) # 200# 可以访问全局变量n1print(n1) # 100# 调用
f1()
print(n1) # 100# 不能访问局部变量n2
# print(n2)  # NameError: name n2' is not defined.

注意事项

  1. 函数内部修改全局变量 n1,默认是新创建了一个变量n1,全局变量 n1 的值并不会被修改
n1 = 100def f1():# n1 重新定义了n1 = 200print(n1) # 200f1()
print(n1) # 100
  1. python 通过 global关键字允许函数内直接修改全局变量,这种方式下的修改是真的改变了全局变量的值
n1 = 100def f1():# n1 重新定义了global n1n1 = 200 # 全局变量n1的值被改为200print(n1) # 200f1()
print(n1) # 200
http://www.xdnf.cn/news/1066753.html

相关文章:

  • Python基础(​​FAISS​和​​Chroma​)
  • Redis哨兵模式深度解析与实战部署
  • 如何实现财务自由
  • 操作系统 第九章 部分
  • 飞往大厂梦之算法提升-7
  • 第一节 布局与盒模型-Flex与Grid布局对比
  • Java的SpringAI+Deepseek大模型实战【二】
  • Vue实现选中多张图片一起拖拽功能
  • 华为HN8145V光猫改华为蓝色公版界面,三网通用,xgpon公版光猫
  • [NocoDB] 在局域网中调整Float类型显示精度的部署经验
  • 《哈希表》K倍区间(解题报告)
  • 数组题解——​轮转数组【LeetCode】
  • K8S下http请求在ingress和nginx间无限循环的问题
  • Docker 永久换源步骤
  • 基于ASP4644多通道降压技术在电力监测系统中集成应用与发展前景
  • Maven 之 JUnit 测试体系构建全解析
  • 基于SpringBoot + Vue 的网上拍卖系统
  • leetcode543-二叉树的直径
  • 通信网络编程3.0——JAVA
  • Spring Cloud微服务
  • Java面试题027:一文深入了解数据库Redis(3)
  • 【软考高级系统架构论文】论数据分片技术及其应用
  • Redis中的bigkey的介绍及影响
  • 安全再升级! 正也科技通过信息安全等级保护三级备案
  • 七八章习题测试
  • 高级版 Web Worker 封装(含 WorkerPool 调度池 + 超时控制)
  • 本地文件深度交互新玩法:Obsidian Copilot的深度开发
  • 能耗管理新革命:物联网实现能源高效利用
  • 小学期前端三件套学习(更新中)
  • 开启游戏新时代:神经网络渲染技术实现重大跨越