从零开始的python学习——函数(2)

ʕ • ᴥ • ʔ

づ♡ど

 🎉 欢迎点赞支持🎉

个人主页:励志不掉头发的内向程序员;

专栏主页:python学习专栏;


文章目录

前言

一、变量作用域

二、函数执行过程

三、链式调用

四、嵌套调用

五、函数递归

六、参数默认值

七、关键字参数

总结


前言

上一章节我们了解到了 Python 中的函数的最基本的定义和调用规则,本章节我们继续来深入函数学习,来学习函数的不同结构以及不同的调用方法。我们一起来看看吧。


一、变量作用域

来观察以下代码。

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

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

我们可能会经常发现有的时候同一个变量名在不同的地方多次出现。但是这个同一个变量名并不意味着是同一个变量。就比如我管我最喜欢的东西叫做 “宝贝儿”,但是大家肯定也有自己的 “宝贝儿”。虽然都是 “宝贝儿”,但是显而易见不是同一个东西。

虽然名字一样,但其实也是不会混淆的。因为它是在各自不同的范围生效的。我叫我的宝贝是我和我宝贝之间的事情,而其他人叫他的宝贝也只是他和他宝贝之间的事情。它们互不影响。

以上的一个情况就描述了一个概念,那就是变量的作用域。一个变量名的有效范围是一定的,只在一个固定的区域内生效。

我们刚才的那一串代码。

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

getPoint() 中的 x,y 是函数内部的变量名,函数内部的变量名只能在函数内部生效,出了函数就无效了。

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

我们来尝试直接在函数外面打印我们函数内部的变量名。

它会报错,并且说 x 未定义。既然我们函数内部的变量在函数外部无法生效,这也就意味着我们在函数外面同样可以使用 x,y 的变量名。此时,这两个 x,y 就是完全不相同的变量。

我们再来看看这个例子。

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

此时我们就能更加清楚的看到相同变量名的区别了。

我们把 x = 10 代码所处的位置叫做全局变量,全局变量是在整个程序中都有效的。而函数内部的 x = 20 叫做局部变量,只在函数内部有效。由于全局变量是在整个程序中都有效的,所以我们函数中也是可以使用我们的全局变量的。

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

当函数访问某个变量的时候,会先尝试在局部变量中查找,如果找到,就直接访问,如果没找到,就会往上一级作用域中进行查找。test 再往上一级作用域,就是全局了。

可以如愿的输出我们的 10。

我们读取还是比较简单的,但是我们修改操作就没那么容易了。

x = 10
# 使用这个函数,把全局变量 x 改成20
def test():x = 20test()
print(f"x = {x}")

我们如果直接修改是不行的。

这是因为我们 x = 20 的 x 被当成函数内部创建的一个局部变量。所以没办法改,如果想要改成20,我们就得使用 Python 中的一个关键字 global 声明一下我们的 x,我们的就能修改全局变量的 x 了。

x = 10
# 使用这个函数,把全局变量 x 改成20
def test():global xx = 20test()
print(f"x = {x}")

此时我们的修改就是针对全局的修改了。

当然,像 if,else,while,for 这些关键字也会引入 “代码块”,但是这些代码块不会对变量的作用域产生影响。在上述语句代码块内部定义的变量,可以在外面被访问。

for i in range(1, 11):print(i)print("--------------------")
print(i)

我们可以看到,我们在 for 循环里面创建的变量 i,在外面也能打印出来。

其他的也是同理。所以说不是遇到代码块就一定会影响作用域,在 Python 中只要函数和类里面的代码块才会涉及到作用域。

二、函数执行过程

  • 调用函数才会执行函数体代码,不调用则不会执行。
  • 函数体执行结束(或者遇到 return 语句),则回到函数调用位置,继续往下执行。
def test():print("执行函数体代码")print("执行函数体代码")print("执行函数体代码")print("111111")
test()
print("222222")
test()
print("333333")
test()
print("444444")

我们通过这个代码来尝试看看我们函数的执行过程。

我们可以通过执行结果来简单分析一下。

我们程序的运行就是在这些各种函数之间不断的跳转,就像盗梦空间一样,可以在梦里做梦,当梦醒了就又跳转回原来的梦中,就在这些梦中不断的跳转。

我们刚才是通过函数结果来一一分析的,其实我们还可以调用 PyCharm 中的调试器,也能看到一样的效果。我们可以在行首部分点击一下就可以打一个断点,再点一下就会取消。

当我们右键时不要选择运行,而是选择下面的调试。

我们的程序就会执行到调试执行的模式。我们调试执行和运行的区别就在于我们调试执行遇到断点就会停下来,也可以随时停下来,方便我们程序猿去观察程序的中间过程。

此时我们的程序运行到我们断点处就停下来了。此时我们就可以让我们程序一步一步的走。

我们可以在这个地方看到好几个按钮。

我们选择这个按钮,它就是单步执行我们的代码,并且遇到自己的函数,能够进入到函数里面。当然只有调试才可以,运行不行。大家可以自己去尝试一下,可以看到我们程序的执行过程就和刚才推测的一样。

按钮下面是函数调用栈,描述了当时的代码是怎么跳转过去的(进一步的也就是函数之间的调用关系)。

三、链式调用

所谓的链式调用,就是用一个函数的返回值作为另一个函数的参数。

我们之前写过一个代码。

def isOdd(num):if num % 2 == 0:return Falsereturn Trueresult = isOdd(10)
print(result)

直接运行就能打印出我们正确的值。

当然,这个代码我们可以直接简化成这个样子。

def isOdd(num):if num % 2 == 0:return Falsereturn Trueprint(isOdd(10))

形如这样的代码,就叫做函数的链式调用。当然,我们的链式调用还可以有多层。

def isOdd(num):if num % 2 == 0:return Falsereturn Truedef add(x, y):return x + yprint(isOdd(add(10, 3)))

此时就相当于又来了一层链式调用,此时我们add算出来返回值为 13,我们返回值13就变成了isOdd 的参数了,然后 isOdd 判断出来是奇数,返回值 True 又变成了 print 后输出。

在链式调用中,先执行 ( ) 里面的函数,后执行外面的函数。换句话说,调用一个函数,就需要先对他的参数求值。

虽然我们链式嵌套是很常见的,但是我们也不用嵌套层次太深,因为这样会影响我们的可读性。

四、嵌套调用

一个函数的函数体内部,还可以调用其他函数。

举一个简单的例子。

def test():print("hello")test()

这就属于一个嵌套调用,我们的 test 中嵌套了一个 print,虽然不是我们自己写的,但它也是一个函数。

同时,我们嵌套调用的层次可以有很多层

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

这就是我们比较复杂一点的函数嵌套。

我们来看看这个代码的执行过程。

虽然我们函数嵌套了多层,但是函数的执行逻辑是没有什么变化的。

如果我们把代码的位置稍微修改一下,我们的执行结果就会天差地别。

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

我们这里先调用在打印,结果完全相反。

我们代码稍微微调了一下后就出现了截然相反的结果,所以我们一定要搞明白函数调用的基本过程。当然我们上述过程依然可以用调试器分析。

五、函数递归

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

我们大部分人可能小时候就听过一个故事:从前有座山,山里有座庙,庙里有个老和尚再给小和尚讲故事,故事的内容是:从前做山........

这个故事永远没有尽头。

我们可以来试着用上面的逻辑来生成一个代码。

# 写一个函数,来求 n 的阶乘(n 是正整数)
def factor(n):result = 1for i in range(1, n + 1):result *= ireturn resultprint(factor(5))

这是我们通过循环的方式来写的求阶乘的方法。

我们觉得没啥意思,于是决定换一种方式,通过递归的方式来写。

我们可以发现:

n! => n * ( n - 1 )!        // 递推公式

1! == 1                        // 初始条件

我们知道了递推公式和初始条件,我们就能来写递归函数了。

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

我们 return n * factor(n - 1) 其实就是我们的递推公式,而 return 1 就是我们的初始条件。

这一串代码虽然写起来没几行,但是其实理解起来还挺困难的,我们来看看它的执行过程。

我们可以看到,虽然都是 n,但是 n是函数形参,形参相当于函数的局部变量。我们的递归代码,不会无限的往下执行,会在每一次递归的时候,都会无限逼近递归的结束条件。

我们递归的代码,虽然很多时候看起来写法很简单,但是执行过程可能会非常复杂,在分析递归代码的时候,光靠脑子想,是很困难的。所以在分析递归过程时,最好就是画图,或者借助调试器去进一步了解递归的过程。

我们递归代码必须有两个要素,第一就是递归结束条件,第二就是递归的递推公式。这和数学归纳法很相像(也是两个条件:初始条件和递推公式)。

我们递归的缺点在于:

1、执行过程非常复杂,难以理解。

2、递归代码容易出现栈溢出的情况。

3、递归代码一般都是可以转换成等价的循环代码的,并且循环的版本通常运行速度比递归版本有优势。

我们的递归代码,不会无限的往下执行,会在每一次递归的时候,都会无限逼近递归的结束条件,但是如果我们的代码写错了,就会导致我们每次递归参数不能正确的接近递归结束条件,就会出现无限递归的情况。

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

假如是这样就会报错。

递归的优点就在于代码非常简洁,尤其是处理一些 “问题本身就是通过递归的方式定义的”。

六、参数默认值

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

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

def add(x, y):print(f"x = {x}, y = {y}")return x + yresult = add(10, 20)
print(result)

我们完成了一个基本的函数调用。

在函数内部加上打印信息,可以方便我们进行调试。但是像这种调试信息,我们希望在正式发布的时候不要有,只是在调试阶段才有,此时我们可以给我们代码增加一个条件语句。

def add(x, y, debug):if debug:print(f"x = {x}, y = {y}")return x + yresult = add(10, 20, True)
print(result)

此时我们可以在调用函数时传递 True/False,当传递 True 时就有调试信息,传递 False 时就没有。

True:

False:

但是这样每次传递函数都得传一个 True/False,看上去比较丑,所以我们可以在 debug 上指定一个默认参数。

def add(x, y, debug = False):if debug:print(f"x = {x}, y = {y}")return x + yresult = add(10, 20)
print(result)

此时如果我们不传 debug 的参数,就默认是 False。

如果我们想要调试,只需要手动加上我们的 True即可。

result = add(10, 20, True)

我们此处的 debug = False 就是我们形参的默认值,带有默认值的形参就可以在调用函数时不必传参,不传参就是使用我们的默认值。通过这样的默认值,就可以让我们函数设计的更加灵活。

我们默认值也有一些要求,它要求带有默认值的形参,得在形参列表后面,而不能在中间。

def add(x, debug = False, y):if debug:print(f"x = {x}, y = {y}")return x + y

这样是不允许的,这样我们传递第二个参数时不知道是给 debug 还是给 y 的。

错误提示是不能把非默认参数放到默认参数前。所以如果多个带有默认参数的形参,这些都得放到后面。

七、关键字参数

在调用函数的时候,需要给函数指定的实参,一般默认情况下是按照形参顺序,来依次传递实参的。但是我们也可以通过关键字参数来调整这里的传参顺序,显式指定当前实参传递给哪个形参。

def add(x, y):return x + yadd(10, 20)

我们的第一个实参 10 就是传递给我们第一个形参 x 的,而第二个实参 20 就是传递给我们第二个形参 y 的。这种按照先后顺序来传参的这种传参风格称为 “位置参数”。这是各个编程语言中最普遍的方式。

除了上种方式外,Python 还有一中非常有特色的传参方式,那就是关键字传参,也就是按照形参的名字来进行传参。

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

我们调用的参数中的 x、y 就是从形参而来。此时我们运行程序。

使用这种关键字参数的最大意义就是能够非常明显的告诉我们程序猿说我们的这些参数是要传给谁。另外可以无视形参和实参的顺序。

test(y = 100, x = 200)

倒过来写也是完全可以的。

我们位置参数和关键字参数还可以混合使用,只不过混合使用时我们要求位置参数在前,关键字参数在后。

而我们关键字参数一般都是搭配默认参数来使用的。一个函数,可以提供很多参数来实现对这个函数内部功能做出一些调整设定,但是为了降低调用者的使用成本,就可以把大部分参数设定出默认值。当调用者需要调整其中的一部分参数时,就可以搭配关键字参数来进行操作。


总结

以上便是我们函数主要的内容,比起其他的语言,多了不少新的特性,我们可以多加感悟它的新特性,但是想要熟练掌握还是得多多刷题,多多敲代码,练习也是学习的一部分。

🎇坚持到这里已经很厉害啦,辛苦啦🎇

ʕ • ᴥ • ʔ

づ♡ど

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

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

相关文章

RAG 的完整流程是怎么样的?

RAG(检索增强生成)的完整流程可分为5个核心阶段:数据准备:清洗文档、分块处理(如PDF转文本切片);向量化:使用嵌入模型(如BERT、BGE)将文本转为向量&#xff1…

研发文档版本混乱的根本原因是什么,怎么办

研发文档版本混乱的根本原因通常包括缺乏统一的版本控制制度、团队协作不畅、文档管理工具使用不当以及项目需求频繁变化等因素。这些问题使得研发团队在日常工作中容易出现文档版本混乱的情况,导致信息的不一致性、沟通不畅以及开发进度的延误。为了解决这一问题&a…

ChartView的基本使用

Qt ChartView(准确类名 QChartView)是 Qt Charts 模块里最常用的图表显示控件。一句话概括:“它把 QChart 画出来,并自带缩放、平移、抗锯齿等交互能力”。QML ChartView 简介(一句话先记住:ChartView 是 Q…

系统扩展策略

1、核心指导思想:扩展立方体 在讨论具体策略前,先了解著名的扩展立方体(Scale Cube),它定义了三种扩展维度: X轴:水平复制(克隆) 策略:通过负载均衡器&#…

HBuilder X 4.76 开发微信小程序集成 uview-plus

简介 本文记录了在HBuilder中创建并配置uni-app项目的完整流程。 首先创建项目并测试运行,确认无报错后添加uView-Plus组件库。 随后修改了main.js、uni.scss、App.vue等核心文件,配置manifest.json并安装dayjs、clipboard等依赖库。 通过调整vite.c…

第4章:内存分析与堆转储

本章概述内存分析是 Java 应用性能调优的核心环节之一。本章将深入探讨如何使用 VisualVM 进行内存分析,包括堆内存监控、堆转储生成与分析、内存泄漏检测以及内存优化策略。通过本章的学习,你将掌握识别和解决内存相关问题的专业技能。学习目标理解 Jav…

面经分享一:分布式环境下的事务难题:理论边界、实现路径与选型逻辑

一、什么是分布式事务? 分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。 一个典型的例子就是跨行转账: 用户从银行A的账户向银行B的账户转账100元。 这个操作包含两个步骤: 从A账户扣减100元。 向B账户…

C++的演化历史

C是一门这样的编程语言: 兼顾底层计算机硬件系统和高层应用抽象机制从实际问题出发,注重零成本抽象、性能、可移植性、与C兼容语言特性和细节很多,学习成本较高,是一门让程序员很难敢说精通的语言 C是自由的,支持5种…

Qt6实现绘图工具:12种绘图工具全家桶!这个项目满足全部2D场景

项目概述 一个基于Qt框架开发的专业绘图工具,实现了完整的2D图形绘制、编辑和管理功能。该项目采用模块化设计,包含图形绘制、图层管理、命令模式撤销重做、用户界面等多个子系统,是学习现代C++和Qt框架的最佳实践。 核心功能特性 12种专业绘图工具 多图层绘制系统 完整的…

Linux驱动开发学习笔记

第1章 Linux驱动开发的方式mmap映射型设计方法。【不推荐】将芯片上的物理地址映射到用户空间的虚拟地址上,用户操作虚拟地址来操作硬件。使用文件操作集(file_operatiopns)设计方法。【极致推荐】platfrom总线型设置方法。【比较流行】设备树。【推荐】第2章 Linux…

mac中进行适用于IOS的静态库构建

前沿: 进行C开发完成之后,需要将代码编译成静态库,并且在IOS的手机系统中执行,因此记录该实现过程. 1主要涉及内容 1.1 整体文件架构 gongyonglocalhost Attention % tree -L 2 . ├── build │ ├── __.SYMDEF │ ├── cmake_install.cmake │ ├── CMakeCache…

C++二维数组的前缀和

C二维数组的前缀和的方法很简单&#xff0c;可以利用公式res[i][j]arr[i][j]res[i-1][j]prefix[i][j-1]-res[i-1][j-1]。输入4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16输出1 3 6 10 6 14 24 36 15 33 54 78 28 60 96 136#include<bits/stdc.h> using namespace std; int…

Wifi开发上层学习1:实现一个wifi搜索以及打开的app

Wifi开发上层学习1&#xff1a;实现一个wifi搜索以及打开的app 文章目录Wifi开发上层学习1&#xff1a;实现一个wifi搜索以及打开的app背景demo实现1.添加系统权限以及系统签名2.布局配置3.逻辑设计3.1 wifi开关的实现3.2 wifi扫描功能3.3 连接wifi总结一、WiFi 状态控制接口二…

【DSP28335 入门教程】定时器中断:为你的系统注入精准的“心跳”

大家好&#xff0c;欢迎来到 DSP28335 的核心精讲系列。我们已经掌握了如何通过外部中断来响应“外部事件”&#xff0c;但系统内部同样需要一个精准的节拍器来处理“内部周期性任务”。单纯依靠 DELAY_US() 这样的软件延时&#xff0c;不仅精度差&#xff0c;而且会在延时期间…

从零开始:用代码解析区块链的核心工作原理

区块链技术被誉为信任的机器&#xff0c;它正在重塑金融、供应链、数字身份等众多领域。但对于许多开发者来说&#xff0c;它仍然像一个神秘的黑盒子。今天&#xff0c;我们将抛开炒作的泡沫&#xff0c;深入技术本质&#xff0c;用大约100行Python代码构建一个简易的区块链&am…

网络通信IP细节

目录 1.通信的NAT技术 2.代理服务器 3.内网穿透和内网打洞 1.通信的NAT技术 NAT技术产生的背景是我们为了解决IPV4不够用的问题&#xff0c;NAT在通信的时候可以对IP将私网IP转化为公网IP&#xff0c;全局IP要求唯一&#xff0c;但是私人IP不是唯一的。 将报文发给路由器进行…

国内真实的交换机、路由器和分组情况

一、未考虑拥挤情况理想状态的网络通信 前面我对骨干网&#xff1a; 宜春城区SDH网图分析-CSDN博客 数据链路层MAC传输&#xff1a; 无线通信网卡底层原理&#xff08;Inter Wi-Fi AX201&#xff09;_ax201ngw是cnvio转pci-e-CSDN博客 物理层、数据链路层、网络层及传输层…

atomic常用类方法

Java中的java.util.concurrent.atomic包提供了多种原子操作工具类&#xff0c;以下是核心类及其方法&#xff1a;‌1. AtomicBoolean‌‌方法‌&#xff1a;get()&#xff1a;获取当前值set(boolean newValue)&#xff1a;强制设置值compareAndSet(boolean expect, boolean upd…

算法题打卡力扣第3题:无重复字符的最长子串(mid)

文章目录题目描述解法一&#xff1a;暴力解解法二&#xff1a;滑动窗口题目描述 解法一&#xff1a;暴力解 遍历每一个可能的子串&#xff0c;然后逐一判断每个子串中是否有重复字符。 具体步骤&#xff1a; 使用两层嵌套循环来生成所有子串的起止位置&#xff1a; 外层循环 i…

HTML5 简介和基础骨架

一、HTML5 简介HTML5 是 HTML&#xff08;超文本标记语言&#xff09;的第五个主要版本&#xff0c;于 2014 年 10 月由 W3C&#xff08;万维网联盟&#xff09;正式发布。它不仅是对 HTML4.01 和 XHTML 的升级&#xff0c;更是一套完整的 Web 技术标准&#xff0c;包含了新的标…