Python——函数速通

13 阅读11分钟

自定义函数

  • 我们在之前的学习中,已经简单的定义或调用过一些函数,函数简单来说,就是将一些语句打包起来,方便我们再次使用
  • 比如 python 内置的一些函数,如选出最大值的 max 函数 02 Python基础-1766122817798.png
  • Max 函数在给定序列中找出最大的那个并返回,如果我们要实现这个功能,可能需要多行语句02 Python基础-1766122941286.png
  • 如果有一个序列需要进行这样的操作,写一遍这样的语句也没什么,但如果有几十个几百个这样的序列需要进行同样的操作呢?这时我们就需要用到函数
  • 我们可以尝试将上述写的语句打包成一个找出最大值的函数,如果需要用到这些语句,我们只需要进行简单的调用即可02 Python基础-1766123237368.png
  • 定义函数要用 def 关键字,后面是函数名,括号内代表函数的形式参数
  • 所谓形式参数,就是在定义时赋予函数,并可以在函数内进行调用的参数
  • 我们可以定义一个简单的函数尝试一下02 Python基础-1766124205703.png
  • 将变量 name 传入函数进行调用02 Python基础-1766124504082.png
  • 有些人可能会感到奇怪,我们定义函数时,接受的形式参数,一个是位置参数 my_name,另一个是默认值参数 my_nickname ,可传入的变量不是这两个中的任意一个,而是名为 name 的变量,为什么函数还能继续运行呢?
  • 如果我们将 name 这个变量改名为 my_name 再传入函数中,函数还能运行吗?
  • 要知道这些问题的答案,我们首先要明白作用域这个概念02 Python基础-1766125536503.png
  • 我们刚才定义的函数 my_introduction 会开辟一个新的作用域,这个作用域里面存放了各种变量,函数在调用变量时,会现在自己开辟的作用域中寻找,如果找不到再去全局作用域中寻找。就像是村里有个人四处奔走在找一个叫狗蛋的人,我们肯定会觉得他是在找本村的叫狗蛋的人。如果你是这个村的村支书,知道这个村里没有叫狗蛋的,你就会上前告诉他这里没有叫狗蛋的,要去其他地方找找
  • my_introduction 被调用时,他会返回 f"在外面叫{my_name},在里面叫{my_nickname}" 这个字符串,由于这个字符串中需要用到 my_namemy_nickname,这两个变量,所以函数会去寻找这两个变量
  • 函数接受了 name 变量,实际是将 name 变量的值赋值给了 my_name,而 my_nickname 的值在函数的作用域中本身就已经赋给过了,所以函数能正常找到变量的值成功运行
  • 像上面的 my_nickname 这样的形式参数,叫做默认值参数,我们在定义时给了该变量一个默认的值,如果不在函数调用的进行修改,变量将会引用默认值02 Python基础-1766127571156.png
  • 介绍了基本的函数定义和形式参数后,我们来学习一下函数的完整定义
  • 函数的完整定义由下图所示02 Python基础-1766388873883.png
  • 仅限位置参数只能通过按位也就是按顺序传参的方式传值,仅限关键字参数只能通过关键字的方式传值
  • 当我们不使用 /* 对参数进行限制时,我们的参数,既可以通过位置传入,也可以通过关键字的方式传入
  • 也就是说,当我们不对形参做任何特殊限制时,各个形参之间没有任何区别
  • 所谓的位置参数和关键字参数,只是按传入值的方式给参数归类而已
  • 我们定义一个简单的函数,来看下位置或关键字参数,也就是不受任何限制的参数的实际应用02 Python基础-1766391611301.png
  • 位置或关键字参数,既能通过顺序传值,也能通过关键字传值,甚至能混合传值,通过位置传值时,该参数就是位置参数,通过关键字传值时,该参数就是关键字参数 02 Python基础-1766391728462.png
  • 要注意,在参数没有默认值的情况下,形式参数和实际参数的个数一定要对应02 Python基础-1766391766029.png
  • 少传了值后会报错02 Python基础-1766391927988.png
  • 参数也能和变量一样,通过解包的方式进行传值,接收一个序列02 Python基础-1766539815598.png
  • 解包传值时,一定要注意实际传入的参数数量和形参数量是否相等 02 Python基础-1766462048593.png
  • 否则会报错02 Python基础-1766462093973.png
  • 也可以用解包字典的方式,来进行特殊方式的关键字传值02 Python基础-1766467416105.png
  • 在利用解包字典传值时,我们要注意,字典的键和形参的名字一定要对应,顺序则可以随意打乱,否则会报错02 Python基础-1766467671768.png
  • 报错信息显示意料之外的关键字参数02 Python基础-1766467710819.png
  • 当我们将给形参赋一个默认值时,这个参数就变成了默认值参数,默认值参数最大的特点时像字典一样 K = V 的形式在定义时就有了默认值02 Python基础-1766452144325.png
  • 同样,默认值参数也是位置或关键字参数,可以按位或者按关键字方式传入参数02 Python基础-1766452363636.png
  • 结果02 Python基础-1766452402300.png
  • 从调用和结果看来,默认值参数并没有太多不同,都可以通过按位置传入和按关键字传入的方式来传值,最大的区别,是默认值参数有默认值,可以不传值,以默认值使用,我们之后用到的很多模块内或内置的函数,都具有默认值参数
  • 要注意的是,默认值参数在没有特殊需求的时候,尽量使用不可变类型的对象,原因我们在列表那一节的深浅拷贝已经讲过
  • 大家在进行各种尝试的过程中,尤其是用混合方式传入值的时候,可能会发生报错,例如我们先用关键字的形式给第一个参数赋值,然后再按位置给其他参数赋值02 Python基础-1766452684406.png
  • 我们已经可以看到,vscode 自动在 200 和 300 下面标记了波浪线,这就代表这部分是有问题的
  • 我们运行程序,果然报错02 Python基础-1766455877557.png
  • 报错提示,位置参数必须要在关键字参数之后,这是为什么呢?
  • 这是因为,python 函数的赋值逻辑,是优先按照位置顺序赋值,再按照关键字来赋值的
  • 我们以为的赋值顺序02 Python基础-1766457159169.png
  • 实际上的赋值02 Python基础-1766457818861.png
  • 如果关键字参数在位置参数之前,赋值就会像上述的流程一样出现重复赋值,为了避免出现这种歧义,python 就规定了位置参数必须要在关键字之前。注意:这个规则不仅要在实际传值的时候遵守,在定义时也要遵守
  • 位置参数能用解包的方式,接收一个序列,那么,关键字参数也同样可以通过解包的方式,接收一个序列,甚至接收一个字典 02 Python基础-1766458830912.png
  • 运行结果02 Python基础-1766458859132.png
  • 在对不限制的参数有一定了解后,我们来看一下函数定义中的两个特殊符号 /*
  • / 用来限制形式参数,在 / 前面的形式参数只能是位置参数,只能通过按位传值的方式传入参数,/ 后面的是位置或关键字参数02 Python基础-1766459064944.png
  • 正常调用没有问题,但如果对形式参数 a1a2 用关键字的方式传入参数的话程序则会报错02 Python基础-1766459147106.png
  • 报错结果如下02 Python基础-1766459212575.png
  • / 对应的 * 则是仅限关键字参数,在 * 前的是位置或关键字参数,之后的则是仅限关键字参数02 Python基础-1766459411855.png
  • 同样,对于仅限关键字参数,不能通过按位传值的方式传入02 Python基础-1766460081226.png
  • 通过上面的学习,我们发现,对函数的参数进行定义时,最重要的就是该参数的==传值方式==
  • 传值的方式大体分为三种:==按位置传值(解包序列),按关键字传值(解包字典)以及默认值传入==
  • 直到现在,我们学习的都是像 a 这样的普通参数,接下来我们来学习两个特殊的形参 *a**a
  • 普通的形参,例如 a、b、c ,可以接收任意类型的对象,就像这样,既可以接收数字,也可以接收字典和列表02 Python基础-1766474383492.png
  • 既然普通的参数功能这么齐全,那么为什么还要在参数前面加一个或两个 * 号呢?这个特殊符号的功能是什么呢?
  • 注意,这是一个非常关键的问题,形参 a 和形参 *a 和形参 **a 到底有什么本质上的区别?
  • 我们定义三个函数,形参分别是 a*a**a 02 Python基础-1766477957983.png
  • 首先我们来看 a*a 在接收序列时的差异
  • 我们传入不同形式的序列02 Python基础-1766478155146.png
  • 大家会发现,和想象中并不一样,形参 a 在接收时,只能接收单个值,或者单个序列,并不能直接接收多个值。而 *a 可以接收无限个值,也就是说 *a 接收的是无限个传入值组成的序列
  • a**a 的区别也在于此,a 接收的是单个字典,而 **a 接收的是无限个键值对模式传入的值 02 Python基础-1766479170163.png

函数文档

  • 到现在,我们已经学会了函数的基本定义和传参,接下来,我们学一个和编程技术没有太大关系但仍然非常重要的东西——函数文档
  • 我们在 VSCode 中,写一个普通的“hello world”语句,然后将鼠标移到 print02 Python基础-1766540776754.png
  • 我们会发现在鼠标的位置突然出现了一大堆东西,这就是函数的说明文档
  • 其中,*valuesependfileflush 都是我们之前学到过的参数
  • *value 用来接收无限个传入值组成的序列,例如我们传入的 "hello world!" 这个字符串
  • sep 接收字符串类型的对象,None = " " 代表 sep 的默认值是 " "
  • endsep 一样,接收字符串类型的对象,默认值是 "\n"
  • file 是支持写入的字符类型,默认为空
  • flush 接收一个布尔值,默认为 False
  • 我们向下滚动,查看更多说明02 Python基础-1766546129804.png
  • 我们可以从说明中看到,sep 参数的作用是给所有输入值增加分割符,默认是空格
  • 我们可以试一下 02 Python基础-1766547828063.png
  • 再看一下 end,它的值代表输出完所有值后的结束符,默认是 "\n",我们在之前字符串那节的转义字符中学过,是换行的意思,我们也可以尝试一下,把 end 的值换成其他 02 Python基础-1766548316603.png
  • 通常将 end 参数改成 "" 来取消换行
  • 接下来是 file 参数,可以将原本打印到终端的值写入到文本中 02 Python基础-1766548524728.png
  • 结果如下02 Python基础-1766553021237.png
  • 最后是 flush 参数,用来决定是否立即刷新缓存区,一般不推荐使用(暂时还不明白实践中 True 和 False 之间的区别)
  • 以上是 python 内置的 print 函数所自带的文档,现在,我们要给自己写的函数写一份文档
  • 我们写一个非常简单的加法函数,将两个参数相加02 Python基础-1766630734089.png
  • 函数中第一个字符串就是函数的注释,也就是说明文档
  • 为了方便和简洁,我们用多行字符串来书写说明文档
  • 字符串内最开始可以简单地写一下函数的作用是什么,用中文也可以,最好用英文
  • Args 标题后,用来对各个形参进行说明
  • Return 标题后跟着这个函数的返回值
  • Example 标题后跟着以交互模式表现出的实例
  • 函数的说明文档不需要多高大上,最重要的是告诉使用这个函数的人,你的函数以及各个参数的作用是什么
  • 说明文档和注释一样,虽然不起眼但非常重要,尤其是大型项目中
  • 如果要进阶说明参数的类型和返回值的类型,可以在参数后面加 : 02 Python基础-1766632127086.png

Lambda函数

  • 最后我们来介绍一下 lambda 函数
  • Lambda 函数是一个非常简单的轻量化函数,我们可以用 lambda 快速创建一个返回两数相加的值的函数 02 Python基础-1766639158094.png
  • Lambda 函数和普通函数一样,接收参数并返回值,我们可以把它当作一个一句话函数