type
status
date
slug
summary
tags
category
icon
password
函数定义
1、定义:
为了完成某种功能,由若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元。
2、作用:
- 结构化编程对代码的最基本的封装,一般按照功能组织一段代码
- 封装的目的是为了复用,减少冗余代码
- 代码更加简洁美观、可读易懂
3、分类:
- 内建函数:str(),list(),pow() 这些函数可以直接拿来使用
- 标准库函数:通过import语句倒入库,使用其中定义的函数
- 第三方库: Python社区提供了高质量的库,下载这些库,通过import语句倒入并使用
- 用户自定义:适应自身需求,用def关键字定义
4、构建
- def defined的意思, 表示定义函数
- 函数名 标识符, 命名最好见名知意, 一般用蛇形命名法,例:get_sum
- 参数列表 为形式参数(形参), 用于接收实参(需要被处理的参数)(可以没有形参)
- 代码块 需要缩进四个空格, 表示在函数体内部的代码
- return 一方面, 作为函数的终止标志; 另一方面, 将函数处理产生的结果返回(可以没有返回值, 即隐性返回None)
- 返回值 返回值可以是任意类型对象, 返回值只能有一个
5、def语句运行
def 语句是一条真正可执行的语句: 当它运行时, 会创建一个新的函数对象, 并且用一个变量(函数名)指向这个函数对象.所以, 所以,def语句可以出现在所有语句可以出现的地方, 甚至嵌套在其他语句中, 如下面例子所示.
def可以看成是一条赋值语句: 它在运行时给一个变量名赋值. 然后, 在需要调用函数的时候通过这个变量名来调用被定义的函数体.所以, 函数是需要先定义, 然后才能执行该函数的. 所以,当函数调用语句出现在def语句之前, 程序是没有办法运行的.
因为函数名也是一个变量名对函数体对象的引用, 所以函数体对象也可以赋值给一个新的变量名并进行调用.
函数调用
- 函数定义,只是声明了一个函数,它不能被执行,需要调用执行
- 调用的方式,就是函数名后加上小括号,如有必要在括号内填写上参数
- 调用时写的参数是实际参数,是实实在在传入的值,简称实参
- 函数是可调用的对象,callable(函数名)返回True
函数参数
函数在定义是要定义好形式参数的,调用时也要提供足够的实际参数,一般来说,形参和实参个数要一致(可变参数除外)
1、实参传参方式
- 位置传参
- 定义时def f(x, y, z),调用使用f(1, 3, 4),按照参数定义顺序传入实参
- 关键字传参
- 定义时def f(x, y, z),调用使用f(x=1, y=2, z=3),使用形式参数的名字来传入实参的方式,如果使用了形参名字,那么传参顺序就可和定义顺序不同
- 位置传参和关键字传参混用:位置传参要在关键字传参之前传入,位置参数是按照位置对应的
2、形参缺省值
缺省值也称默认值,可以再函数定义时,为形参增加一个缺省值,形参缺省值要放在形参列表后面。其作用:
- 参数默认值可以在为传入足够的实参的时候,对没有给定的参数赋值为默认值
- 参数非常多的时候,并不需要用户每次都输入所有的参数,简化函数调用
3、可变参数
- 可变位置参数 (*args)
- 在形参前使用
*
表示该形参是可变位置参数,可以接受多个实参 - 它将收集来的实参封装到一个元组中
- 可变关键字参数(**kwargs)
- 在形参前使用
**
表示该形参是可变关键字参数,可以接受多个关键字参数 - 他将收集来的关键字实参的名称和值,封装到一个字典中
- 形参列表中,可变位置参数*args要放在可变关键字参数**kwargs前面
- 传参时,可以不给可变位置参数*args和可变关键字参数**kwargs传递参数
有时候无法预知函数需要接受多少个实参,此时,通过在一个形参前面加
*
即可接收任意多个实参,如:*toppings,也可以通过**
加形参接受键值对,存储在字典中4、keyword-only参数
python3之后增加了keywordonly参数,在函数参数列表中,位于 *args后面的参数就不是普通参数了,而是keyword-only参数,只能用关键字传参
5、positional-only参数
python 3.8开始,增加了最后一种形参类型的定义:positional-only参数.
6、参数规则
参数列表参数一般顺序是:positional-only参数、普通参数、缺省参数、可变位置参数、keyword-only参数(可带缺省值)、可变关键字参数
- 代码应该易读易懂,而不是为难别人
- 请按照书写习惯定义函数参数
7、参数解构
- 在给函数提供实参的时候,可以在可迭代对象前使用*或者**来进行结构的解构,提取出其中所有元素作为函数的实参
- 使用*解构成位置传惨
- 使用**解构成关键字传参
- 提取出来的元素数目要和参数的要求匹配
函数返回值 return
函数并非总是直接显示输出,相反,它可以处理一些数据,并返回一个或一组数值。函数返回的值被称为返回值。在函数中,可使用return语句将值返回到调用函数的代码行并停止函数调用
如果函数不包含
return
,则默认执行return None
代码(如有必要,可以显示调用return None,简写为return),即函数不返回值。一般情况下,函数运行到return语句,将直接返回,后面的其他语句将不会运行。在返回值中,注意返回函数名和函数结果的区别
函数作用域(重难点)
一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域。
分类:
- 全局作用域
- 在整个程序运行环境都可见
- 全局作用域中的变量称之为全局变量
- 局部作用域
- 在函数、类等内部可见
- 全局作用域中的变量称为局部变量,也称为本地作用域local,其使用范围不能超过其所在局部作用域
- 每一个函数都会开辟一个本地作用域
- 一般来讲外部作用域变量可以在函数内部可见,可以使用
- 反过来,函数内部的局部变量,不能在函数外部看到
- 在函数内外都有同名变量,则在函数内优先使用内部定义变量,就近原则
嵌套函数的作用域
在一个函数中定义了另外一个函数
- 内部函数inner就是一个标识符,函数outer内部定义的变量而已;故inner不能在外部被调用,会抛异常NameError
- 例三中,inner函数重新定义了变量
o
,根据就近原则,inner函数优先使用内部定义的变量o
局部变量赋值问题
在说明赋值问题之前,先看下面两个例子
只要函数中出现了 x = 变量赋值语句,且此变量不加任何语句的 的修饰,那么此变量就一定是当前函数的局部变量,不会引入外部全局变量。在例一中,x为不可变对象,
x = 300
,此时会生成局部变量x,整个函数内部都会使用这个局部变量x,但是在函数y = x + 1
是在x = 500
之前的,此时当执行到y = x + 1
时,x既不能引用全局变量x,也不被赋值,会抛出异常UnboundLocalError: local variable 'x' referenced before assignment
(赋值前引用局部变量'x’)。如果变量指向可变对象,无论在全局变量还是局部变量,都是存储在内存中的不同位置,而指引则是指向这些位置的指针。因此,无论是在全局作用域还是在局部作用域中,对于可变对象的修改都是通过引用来实现的,而不是直接修改变量本身。因此,无论是在全局作用域还是在局部作用域中对对象的修改,都不会影响到这个对象在内存中的位置。
那么针对例一中问题,如要要在函数内对变量指向的不可变对象进行修改,就需要global语句来实现了。
global语句
使用global关键字定义的变量,虽然在foo函数中声明,但是这将告诉当前foo函数,作用域,这个x变量将使用外部作用域中的x。
即使是在foo中又写了
x = 10
,也不会在foo这个局部作用域中定义局部变量x了。总结
x += 1
这种是特殊形式产生的错误的原因?先引用后赋值,而python动态语言是赋值才算定义,才能被引用。解决办法,在这条语句前增加x=0之类的赋值语句,或者使用global告诉内部作用
- 内部作用域使用
x=10
之类的赋值语句会重新定义局部作用域使用的变量x,但是,一旦这个作用域中使用global声明x为全局的,那么x=10
相当于在为全局作用域的变量x赋值
global使用原则
- 外部作用域变量会在内部作用域可见,,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
- 如果函数需要使用外部全局变量,请尽量使用函数的形参定义,并在调用传实参解决
- 一句话:不用global。学习它就是为了深入理解变量作用域
闭包***
- 自由变量:未在本地作用域中定义的变量。例如定义在内存函数外的外层函数的作用域中的变量
- 闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是JavaScript
在执行
foo = counter()
后,counter内部产生2个局部变量c和inc,foo获得了counter()结果,即inc指向的引用地址。counter()函数执行完之后,局部变量inc和c标识符消亡了(它们指向的对象没有消亡)。执行foo()后,由于外层函数已经执行结束了,内层函数对象没有消亡,什么时候调用不知道,但是内层函数用到外层函数自由变量c,foo指向的函数对象,要使用counter的c,c指向的列表不消亡,由这个不消亡的内存函数对象来保存这个变量,这就是闭包。对于不可变对象作为变量,如何在内层函数中调用外层函数变量并修改实现闭包呢,显然使用global可以实现功能,但是会将变量声明为全局变量,可能会影响到其他部分代码,如何实现呢?先看下面例子:
上面例子中,例二没有实现闭包,也无法跑通,例三虽然通过全局变量可以让代码执行成功但也没有实现闭包。
如果要对普通变量使用闭包,python3中可以使用nonlocal关键字
nonlocal语句
nonlocal:将变量标记为不在本地作用域定义,而是在上级的某一级局部作用域中定义,但不能是全局变量中定义。
- 变量c是外层函数的局部变量,被内部函数引用
- 内部函数使用nonlocal关键字声明c变量在上一级作用域而非本地作用域中定义
- 代码中内存函数引用外部局部作用域中的自由变量,形成闭包
- nonlocal只会作用域函数内部,不会影响全局,推荐使用
可变默认值作用域
Python中“一切皆对象”。Python中对象是分配在堆上的,存储真正的数据。而变量则存储在栈中,保存其所引用对象的地址。执行def定义函数后,系统在堆中创建了函数对象(包含参数信息、代码信息等),其中就包括计算出的默认参数的值。例二中,函数执行时,由于列表是可变类型(引用类型),所以append会直接在原列表上修改而不会创建的新的列表。函数执行结束后,变量y的内容即list的地址会被弹栈,而堆中的信息仍被保存。但下次调用该函数时,会从堆中取出默认参数的值赋给临时变量L。如果在调用时给定实参,不用默认值,则会使用给定的实参,从而创建新的列表。
- 属性__defaults__中使用元组保存所有位置参数默认值,它不会因为在函数体内改变了局部变量(形参)的值而发生变化
改进:
- 通过这种方式定义函数参数,可以使代码更加清晰易懂,也更容易维护。
- 同时避免了出现意外结果,因为每次调用函数时都会重新初始化参数y,而不是使用同一个默认参数
- 属性__defaults__中使用元组保存所有位置参数默认值
- 属性__kwdefualts__中使用字典保存所有keyword-only参数的默认值
x += 与 x = x+间区别
上面两个例子可以看出,
x +=
和 x = x +
是不一样的(对于变量是引用类型来说)从上面例子可以看出,对于可变类型来说,
x +=
相当于就地修改x;x = x +
生成一个新的对象覆盖原来的对象。当然对于不可变类型,两种方式都会生成新的对象。由此,例一中,
x
从始至终都是指向形参默认值[]
,是[]
增加了一个元素,但是在例二中,x
通过合并生成新的列表,x
指向新的列表,默认值参数每次运行之后还是[]
没有发生变化变量名解析原则LEGB***
- Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡
- Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
- Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
- Build—in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如print(open),print和open都是内置的变量
所以一个名词的查找顺序就是LEGB
函数的销毁
- 定义一个函数就是生成一个函数对象,函数名指向的就是函数对象。
- 可以使用del语句删除函数名,使函数对象引用计数减1。
- 可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减1。
- Python程序结束时,所有对象销毁。
- 函数也是对象,也不例外,是否销毁,还是看引用计数是否减为0。
匿名函数
构建和调用
语法:lambda [参数列表]: 表达式
- 参数列表不需要加括号。无参就不写
- 冒号用来分割参数列表和表达式
- 不需要return, 匿名函数在被调用时会将表达式结果返回.
- lambda表达式只能写在一行上,也称单行函数
匿名函数往往用在高阶函数传参时,使用lambda表达式,往往能简化代码
使用匿名函数构造生成器
避坑
自由变量的运行时绑定值问题
应用
- 高阶函数
题目: