【Python】3.基础语法(3)函数

文章目录

  • 1.函数
  • 2.语法格式
  • 3.函数参数
  • 4. 函数返回值
  • 5. 变量作用域
  • 6.函数执行过程
  • 7. 链式调用
  • 8.嵌套调用
  • 9. 函数递归
  • 10. 参数默认值
  • 11. 关键字参数


1.函数

编程中的函数, 是一段 可以被重复使用的代码片段。

代码示例: 求数列的和, 不使用函数

# 1. 求 1 - 100 的和
sum = 0
for i in range(1, 101):sum += i
print(sum)
# 2. 求 300 - 400 的和
sum = 0
for i in range(300, 401):sum += i
print(sum)
# 3. 求 1 - 1000 的和
sum = 0
for i in range(1, 1001):sum += i
print(sum)

可以发现, 这几组代码基本是相似的, 只有一点点差异。可以把重复代码提取出来, 做成一个函数。

实际开发中, 复制粘贴是一种不太好的策略. 实际开发的重复代码可能存在几十份甚至上百份。

一旦这个重复代码需要被修改, 那就得改几十次, 非常不便于维护。

代码示例: 求数列的和, 使用函数

# 定义函数
def calcSum(beg, end):sum = 0for i in range(beg, end + 1):sum += iprint(sum)
# 调用函数
sum(1, 100)
sum(300, 400)
sum(1, 1000)

可以明显看到, 重复的代码已经被消除了。


2.语法格式

创建函数/定义函数

def 函数名(形参列表):函数体return 返回值

调用函数/使用函数

函数名(实参列表)           // 不考虑返回值
返回值 = 函数名(实参列表)   // 考虑返回值

函数定义并不会执行函数体内容, 必须要调用才会执行。调用几次就会执行几次

def test1():print('hello')# 如果光是定义函数, 而不调用, 则不会执行. 

函数必须先定义, 再使用

# test3()          # 还没有执行到定义, 就先执行调用了, 此时就会报错. NameError: name 'test3' is not defined
def test3():print('hello')
test3()

3.函数参数

在函数定义的时候, 可以在 ( ) 中指定 “形式参数” (简称 形参), 然后在调用的时候, 由调用者把 “实际参数” (简称 实参) 传递进去。这样就可以做到一份函数, 针对不同的数据进行计算处理。

考虑前面的代码案例:

def calcSum(beg, end):sum = 0for i in range(beg, end + 1):sum += iprint(sum)sum(1, 100)
sum(300, 400)
sum(1, 1000)

实参和形参之间的关系, 就像签合同一样.


注意:

一个函数可以有一个形参, 也可以有多个形参, 也可以没有形参。

一个函数的形参有几个, 那么传递实参的时候也得传几个,保证个数要匹配。

def test(a, b, c):print(a, b, c)
test(10)
# TypeError: test() missing 2 required positional arguments: 'b' and c

C++ / Java 不同, Python 是动态类型的编程语言, 函数的形参不必指定参数类型。换句话说, 一个函数可以支持多种不同类型的参数。

def test(a):print(a)
test(10)
test('hello')
test(True)

4. 函数返回值

函数的参数可以视为是函数的 “输入”, 则函数的返回值, 就可以视为是函数的 “输出”。

此处的 “输入”, “输出” 是更广义的输入输出, 不是单纯指通过控制台输入输出。我们可以把函数想象成一个 “工厂”。工厂需要买入原材料, 进行加工, 并生产出产品。函数的参数就是原材料, 函数的返回值就是生产出的产品。

下列代码

def calcSum(beg, end):sum = 0for i in range(beg, end + 1):sum += iprint(sum)calcSum(1, 100)

可以转换成

def calcSum(beg, end):sum = 0for i in range(beg, end + 1):sum += ireturn sum
result = calcSum(1, 100)
print(result)

这两个代码的区别就在于, 前者直接在函数内部进行了打印, 后者则使用 return 语句把结果返回给函数调用者, 再由调用者负责打印。

我们一般倾向于第二种写法。

实际开发中我们的一个通常的编程原则, 是 “逻辑和用户交互分离”。而第一种写法的函数中, 既包含了计算逻辑, 又包含了和用户交互(打印到控制台上)。这种写法是不太好的, 如果后续我们需要的是把计算结果保存到文件中, 或者通过网络发送, 或者展示到图形化界面里, 那么第一种写法的函数, 就难以胜任了。

而第二种写法则专注于做计算逻辑, 不负责和用户交互。那么就很容易把这个逻辑搭配不同的用户交互代码, 来实现不同的效果。


一个函数中可以有多个 return 语句

代码1:

# 判定是否是奇数
def isOdd(num):if num % 2 == 0:return Falseelse:return Trueresult = isOdd(10)
print(result)

执行到 return 语句, 函数就会立即执行结束, 回到调用位置。

代码2:

# 判定是否是奇数
def isOdd(num):if num % 2 == 0:return Falsereturn True
result = isOdd(10)
print(result)

如果 num 是偶数, 则进入 if 之后, 就会触发 return False , 也就不会继续执行 return True

代码1和代码2起到的作用一样。


一个函数是可以一次返回多个返回值的。使用 , 来分割多个返回值。

def getPoint():x = 10y = 20return x, y
a, b = getPoint()

如果只想关注其中的部分返回值, 可以使用 _ 来忽略不想要的返回值。

def getPoint():x = 10y = 20return x, y
_, b = getPoint()

5. 变量作用域

观察以下代码

def getPoint():x = 10y = 20return x, y
x, y = getPoint()

在这个代码中, 函数内部存在 x, y, 函数外部也有 x, y。 但是这两组 x, y 不是相同的变量, 而只是恰好有一样的名字。

变量只能在所在的函数内部生效。

在函数 getPoint() 内部定义的 x, y 只是在函数内部生效。一旦出了函数的范围, 这两个变量就不再生效了。

def getPoint():x = 10y = 20return x, y
getPoint()
print(x, y)
# NameError: name 'x' is not defined

在不同的作用域中, 允许存在同名的变量

虽然名字相同, 实际上是不同的变量。

x = 20
def test():x = 10print(f'函数内部 x = {x}')
test()
print(f'函数外部 x = {x}')

打印:

函数内部 x = 10
函数外部 x = 20

注意:

在函数内部的变量, 也称为 “局部变量”

不在任何函数内部的变量, 也称为 “全局变量”


如果函数内部尝试访问的变量在局部不存在, 就会尝试去全局作用域中查找

x = 20
def test():print(f'x = {x}')
test()

打印:

x = 20

如果是想在函数内部, 修改全局变量的值, 需要使用 global 关键字声明

x = 20
def test():global xx = 10print(f'函数内部 x = {x}')
test()
print(f'函数外部 x = {x}')

打印:

函数内部 x = 10
函数外部 x = 10

如果此处没有 global , 则函数内部的 x = 10 就会被视为是创建一个局部变量 x, 这样就和全局变量 x 不相关了。


if / while / for 等语句块不会影响到变量作用域

换而言之, 在 if / while / for 中定义的变量, 在语句外面也可以正常使用。

for i in range(1, 10):print(f'函数内部 i = {i}')
print(f'函数外部 i = {i}')

6.函数执行过程

调用函数才会执行函数体代码。不调用则不会执行。

函数体执行结束(或者遇到 return 语句), 则回到函数调用位置, 继续往下执行。

def test():print("执行函数内部代码")print("执行函数内部代码")print("执行函数内部代码")
print("1111")
test()
print("2222")
test()
print("3333")

打印:

1111
执行函数内部代码
执行函数内部代码
执行函数内部代码
2222
执行函数内部代码
执行函数内部代码
执行函数内部代码
3333

这个过程还可以使用 PyCharm 自带的调试器来观察:

  • 点击行号右侧的空白, 可以在代码中插入 断点

  • 右键, Debug, 可以按照调试模式执行代码。每次执行到断点, 程序都会暂停下来

  • 使用 Step Into (F7) 功能可以逐行执行代码

step into(F7):单步运行,可以进入函数里面


7. 链式调用

前面的代码很多都是写作:

# 判定是否是奇数
def isOdd(num):if num % 2 == 0:return Falseelse:return Trueresult = isOdd(10)
print(result)

实际上也可以简化写作

# 判定是否是奇数
def isOdd(num):if num % 2 == 0:return Falseelse:return Trueprint(isOdd(10))

把一个函数的返回值, 作为另一个函数的参数, 这种操作称为 链式调用。

这是一种比较常见的写法。


8.嵌套调用

函数内部还可以调用其他的函数, 这个动作称为 “嵌套调用” 。

def test():print("执行函数内部代码")print("执行函数内部代码")print("执行函数内部代码")

test 函数内部调用了 print 函数, 这里就属于嵌套调用。

一个函数里面可以嵌套调用任意多个函数。

函数嵌套的过程是非常灵活的。

def a():print("函数 a")
def b():print("函数 b")a()
def c():print("函数 c")b()
def d():print("函数 d")c()
d()

打印:

函数 d
函数 c
函数 b
函数 a

如果把代码稍微调整, 打印结果则可能发生很大变化。

def a():print("函数 a")
def b():a()print("函数 b")
def c():b()print("函数 c")
def d():c()print("函数 d")
d()

打印:

函数 a
函数 b
函数 c
函数 d

注意体会上述代码的执行顺序,可以通过画图的方式来理解。


def a():num1 = 10print("函数 a")
def b():num2 = 20a()print("函数 b")
def c():num3 = 30b()print("函数 c")
def d():num4 = 40c()print("函数 d")
d()

函数之间的调用关系, 在 Python 中会使用一个特定的数据结构来表示, 称为函数调用栈。每次函数调用, 都会在调用栈里新增一个元素, 称为 栈帧。

可以通过 PyCharm 调试器看到函数调用栈和栈帧。

在调试状态下, PyCharm 左下角一般就会显示出函数调用栈。

每个函数的局部变量, 都包含在自己的栈帧中。

0faaa3fb8499b16a556e48e584589549

调试器的左下角,能够看到函数之间的调用栈,调用栈里面描述了当前这个代码的函数之间的调用关系是什么。每一层这个调用关系就称为函数的栈帧,每个函数的局部变量就在这个栈帧中体现的。

8599dc7ed98cba984cb5180bc9ba5e56

每一层栈帧,你选中之后就能看到里面的局部变量,每个函数的局部变量就保存在对应的栈帧中。

调用函数,则生成对应的栈帧。

函数结束后,则会销毁对应的栈帧(里面的局部变量也就没有了)。

选择不同的栈帧, 就可以看到各自栈帧中的局部变量。

思考:

上述代码, a, b, c, d 函数中的局部变量名各不相同。如果变量名是相同的, 比如都是 num , 那么这四个函数中的 num 是属于同一个变量, 还是不同变量呢?

答案:不是同一个变量,每个函数里面定义的变量只在每个函数里面生效。


9. 函数递归

递归是嵌套调用中的一种特殊情况,即一个函数嵌套调用自己。

代码示例:递归计算 5!

def factor(n):if n == 1:return 1return n * factor(n - 1)
result = factor(5)
print(result)

打印:

120

上述代码中, 就属于典型的递归操作。在 factor 函数内部, 又调用了 factor 自身。

注意:

递归代码务必要保证存在递归结束条件。比如 if n == 1 就是结束条件。当 n1 的时候, 递归就结束了。

每次递归的时候, 要保证函数的实参是逐渐逼近结束条件的。

如果上述条件不能满足, 就会出现 “无限递归”。这是一种典型的代码错误。

def factor(n):return n * factor(n - 1)
result = factor(5)
print(result)
# RecursionError:maximum recursion depth exceeded

如果调用 factor(5),函数会这样执行:

factor(5) 返回 5 * factor(4)

factor(4) 返回 4 * factor(3)

factor(3) 返回 3 * factor(2)

factor(2) 返回 2 * factor(1)

factor(1) 返回 1 * factor(0)

由于没有定义 factor(0) 的返回值,函数会继续尝试调用 factor(-1),factor(-2) 等等,导致无限递归。

定义函数的时候,入参可以为0。


如前面所描述, 函数调用时会在函数调用栈中记录每一层函数调用的信息。

但是函数调用栈的空间不是无限大的。如果调用层数太多, 就会超出栈的最大范围, 导致出现问题。

递归的优点

  • 递归类似于 “数学归纳法” , 明确初始条件, 和递推公式, 就可以解决一系列的问题。

  • 递归代码往往代码量非常少。

递归的缺点

  • 递归代码往往难以理解, 很容易超出掌控范围

  • 递归代码容易出现栈溢出的情况

  • 递归代码往往可以转换成等价的循环代码。并且通常来说循环版本的代码执行效率要略高于递归版本。(函数调用也是有开销的)

实际开发的时候, 使用递归要慎重!


10. 参数默认值

Python 中的函数, 可以给形参指定默认值。

带有默认值的参数, 可以在调用的时候不传参。

代码示例: 计算两个数字的和

def add(x, y, debug=False):if debug:print(f'调试信息: x={x}, y={y}')return x + y
print(add(10, 20))
print(add(10, 20, True))

打印:

30
调试信息: x=10, y=20
30

此处 debug=False 即为参数默认值。当我们不指定第三个参数的时候, 默认 debug 的取值即为 False


带有默认值的参数需要放到没有默认值的参数的后面,否则会出错

def add(x, debug=False, y):if debug:print(f'调试信息: x={x}, y={y}')return x + y
print(add(10, 20))

062ae633bacaf433607c895b633f69e9


11. 关键字参数

在调用函数的时候, 需要给函数指定实参。一般默认情况下是按照形参的顺序, 来依次传递实参的。

但是我们也可以通过 关键字参数, 来调整这里的传参顺序, 显式指定当前实参传递给哪个形参。

def test(x, y):print(f'x = {x}')print(f'y = {y}')
test(x=10, y=20)
test(y=100, x=200)

打印:

x = 10
y = 20
x = 200
y = 100

形如上述 test(x=10, y=20) 这样的操作, 即为 关键字参数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/53052.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“立创EDA专业版”笔记

目录 二、立创EDA专业版 2.0 整体功能 2.0.1 快捷键 2.1 右侧功能栏 2.1.1 过滤 2.2 PCB设计 2.2.1 切换亮度 2.2.2 偏移 2.2.3 单位切换 2.2.4 检查DRC 2.2.5 重新铺铜 2.2.6 布线 2.2.7 锁定 2.2.8 “过滤”设置锁定 2.3 上方菜单栏 2.3.1 保存文件 2.4 元件…

安卓(Android)平台上的MVVM架构:关键知识点、优劣分析及实践示例

​ 一、安卓MVVM架构核心知识点 1.1、架构组成 1.1.1、Model层 承载业务逻辑与数据实体,独立于UI并与ViewModel进行交互,实现数据获取与处理功能。 1.1.2、View层 负责用户界面展示,借助Android XML布局文件及Activity/Fragment等组件&a…

Java学习第六天

Java进阶知识面向对象 static:是静态的意思,可以修饰成员变量,表示该成员变量在内存中只存储一份,可以被共享访问。 静态成员变量(有static修饰,属于类,内存中加载一次)&#xff1a…

网络安全-安全渗透简介和安全渗透环境准备

文章目录 前言1. 安全渗透简介1.1 什么是安全渗透?1.2 安全渗透所需的工具1.3 渗透测试流程 2. 使用 Kali Linux 进行安全渗透2.1 下载ISO镜像2.2 下载VMware Workstaion软件2.3 Kali Linux简介2.4 准备Kali Linux环境2.5 Kali Linux初始配置2.6 VIM鼠标右键无法粘贴…

从汇编层看64位程序运行——likely提示编译器的优化案例和底层实现分析

大纲 代码分析with_attributes::powno_attributes::pow分析 我们在《Modern C——使用分支预测优化代码性能》一文中介绍了likely提示编译器进行编译优化,但是我们又讲了最终优化不是对分支顺序的调换,那么它到底做了什么样的优化,让整体性能…

Temu、Shein半托管vs全托管?养号测评怎么整?

2024年,跨境电商的市场风向又变了!1月4日,阿里旗下的速卖通推出半托管模式,通过免佣金和现金补贴吸引卖家;同月,拼多多的Temu也在美国上线了半托管服务,TikTok Shop和SHEIN紧随其后。这给才流行…

Vue学习笔记 二

4、Vue基础扩展 4.1 插槽 组件的最大特性就是复用性,而用好插槽能大大提高组件的可复用能力在Vue中插槽是很重要的存在,通过插槽,我们可以把父组件中指定的DOM作用到子组件的任意位置,后面我们坐项目用到的组件库比如element-ui,vant-ui都频繁用到的插槽,Vue的插槽主要有…

MySQL:简述多版本并发控制MVCC

一、MVCC的概念 1、MVCC 数据库并发场景有三种,分别为: (1)读读:不存在任何问题,也不需要并发控制。 (2)读写:有线程安全问题,可能会造成事务隔离性问题&am…

最新!yolov10+deepsort的目标跟踪实现

目录 yolov10介绍——实时端到端物体检测 概述 主要功能 型号 性能 方法 一致的双重任务分配,实现无 NMS 培训 效率-精度驱动的整体模型设计 提高效率 精度提升 实验和结果 比较 deepsort介绍: yolov10结合deepsort实现目标跟踪 效果展示…

.NET周刊【9月第1期 2024-09-01】

国内文章 【音视频通话】使用asp.net core 8vue3 实现高效音视频通话 https://www.cnblogs.com/1996-Chinese-Chen/p/18384394 该文章描述了使用SRS实现音视频通话和共享桌面的经验。从最初使用nginx的RTMP到研究SRS和ZLMediaKit的过程,再到最终实现功能的详细步…

TF | SD 卡出现无法删除的文件,乱码文件该如何处理 macOS

TF | SD 卡出现无法删除的文件,乱码文件该如何处理 macOS 一、问题描述 最近手头有张用在 Miyoo 掌机上的游戏 TF 卡,在macOS 系统下在回收站中出现了几个特殊文件名的文件,始终无法删除。 二、试着解决 我试过了网上的所有方法都无法删除…

RuoYi-Cloud 部署与配置 [CentOS7]

静态IP设置 # 修改网卡配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33# 修改文件内容 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno BOOTPROTOstatic IPADDR192.168.18.130 NETMASK255.255.255.0 GATEWAY192.168.18.2 DEFROUTEyes IPV4_FAILURE_FATALno IPV6INIT…

Electron桌面应用与文件路径处理:从Git、SourceTree到TortoiseGit的安装与配置

更多内容前往个人网站:孔乙己大叔 在开发Electron桌面应用程序时,正确处理文件路径是一个至关重要的环节。特别是当涉及到需要调用外部程序(如Git、SourceTree或TortoiseGit)时,确保这些程序安装在正确的位置&#xff…

@Tanstack/vue-query 的使用介绍

Tanstack/vue-query 的使用介绍 前言 在今年的vue conf 会议上,提到了vue-query这个库,这里对它的基本使用做一个介绍。 会议资料地址: https://vueconf.cn/ Tanstack-query的前身是react-query,是一个本地的服务端状态管理的库…

3.6 逻辑运算

🎓 微机原理考点专栏(通篇免费) 欢迎来到我的微机原理专栏!我将帮助你在最短时间内掌握微机原理的核心内容,为你的考研或期末考试保驾护航。 为什么选择我的视频? 全程考点讲解:每一节视频都…

wpf prism 《3》 弹窗 IOC

传统的弹窗 这种耦合度高 new 窗体() . Show(); new 窗体() . ShowDialog(); 利用Prism 自动的 IOC 弹窗的 必须 必须 必须 页面控件 弹窗的 必须 必须 必须 页面控件 弹窗的 必须 必须 必须 页面控件 弹窗的 必须 必须 必须 页面控件 弹窗的 必须 必须 必须 页面控件 》》否…

【C语言】十六进制、二进制、字节、位、指针、数组

【C语言】十六进制、二进制、字节、位 文章目录 [TOC](文章目录) 前言一、十六进制、二进制、字节、位二、变量、指针、指针变量三、数组与指针四、指针自加运算五、二维数组与指针六、指向指针的指针七、指针变量作为函数形参八、函数指针九、函数指针数组十、参考文献总结 前…

系统功能性能优化:从问题定位到解决方案的系统性分析

引言 在现代软件系统中,性能优化是确保系统稳定、响应迅速和资源高效利用的关键。面对复杂的系统架构和业务逻辑,进行性能优化往往需要遵循一系列系统性的步骤,以确保问题被准确识别,解决方案被有效实施。以下是一套专业的系统功…

(四)Kafka离线安装 - Kafka下载及安装

Kafka官方下载地址:Apache Kafka 这时候下载安装版本。 我这里的安装目录在 /usr/local/ cd /usr/local/# 创建目录 mkdir kafka cd kafka mkdir kafka_log 把下载的压缩包,放入到/usr/local/kafka/目录下,解压。 # 解压 tar -zxvf kafka…

前端踩坑记录:javaScript复制对象和数组,不能简单地使用赋值运算

问题 如图,编辑table中某行的信息,发现在编辑框中修改名称的时候,表格中的信息同步更新。。。 检查原因 编辑页面打开时,需要读取选中行的信息,并在页面中回显。代码中直接将当前行的数据对象赋值给编辑框中的表单对…