函数简介(function)
函数也是一个对象 , 对象是内存中专门用来存储数据的一块区域 , 函数可以用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次的调用 . 函数中保存的代码不会立即执行,需要调用函数代码才会执行 .
1 | # 创建函数 |
定义函数一般都是要实现某种功能的 .
1 | # 比如有如下三行代码,这三行代码是一个完整的功能 |
函数的参数
在定义函数时,可以在函数名后的()中定义数量不等的形参,多个形参之间使用,
隔开 .
- 形参(形式参数),定义形参就相当于在函数内部声明了变量,但是并不赋值
- 实参(实际参数) , 如果函数定义时,指定了形参,那么在调用函数时也必须传递实参,实参将会赋值给对应的形参,简单来说,有几个形参就得传几个实参
1 | # 定义函数时指定形参 |
定义形参时,可以为形参指定默认值 , 指定了默认值以后,如果用户传递了参数则默认值没有任何作用 , 如果用户没有传递,则默认值就会生效 .
1 | def fn(a = 5 , b = 10 , c = 20): |
实参的传递方式有两种.
-
位置参数
位置参数就是将对应位置的实参赋值给对应位置的形参 , 第一个实参赋值给第一个形参,第二个实参赋值给第二个形参 . . .
例如:
fn(1,2,3)
-
关键字参数
关键字参数,可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数 .
例如:
fn(b=1 , c=2 , a=3)
;print('hello' , end='')
中end
就是关键字参数.
位置参数和关键字参数可以混合使用 , 混合使用关键字和位置参数时,必须将位置参数写到前面 .
Python函数在调用时,解析器不会检查实参的类型 , 实参可以传递任意类型的对象 .
1 | def fn(a): |
在函数中对形参进行重新赋值,不会影响其他的变量 . 如果形参执行的是一个对象,当我们通过形参去修改对象时 , 会影响到所有指向该对象的变量 .
1 | def fn4(a): |
1 | def fn4(a): |
为了避免函数对对象进行修改, 可以使用切片的方式传入一个副本 , 也可以使用copy()
方法来传入浅复制对象.如:fn4(b.copy())
或fn4(b[:])
为了使函数功能更加灵活 , 可以在定义函数时使用不定长参数 . 即不限制参数的个数. 定义函数时 , 在形参前面加*
即表示该形参为不定长参数. 此时传入的参数将会被接收为一个元组 . 此过程称为参数的装包.
1 | def fn(*a): |
利用不定长参数 , 可以实现求任意个数字的和.
1 | # 定义一个函数,可以求任意个数字的和 |
不定长参数在使用过程中需要注意以下几点:
-
带星号的形参只能有一个 , 带星号的参数,可以和其他参数配合使用 .
1
2
3
4
5
6
7
8
9def fn2(a,b,*c):
print('a =',a)
print('b =',b)
print('c =',c)
fn2(1,2,3,4,5)
# a = 1
# b = 2
# c = (3, 4, 5) -
可变参数不是必须写在最后,但是注意,带*的参数后的所有参数,必须以关键字参数的形式传递
1
2
3
4
5
6
7
8
9
10def fn2(a,*b,c):
print('a =',a)
print('b =',b)
print('c =',c)
#fn2(1,2,3,4,5) # fn2() missing 1 required keyword-only argument: 'c'
fn2(1,2,3,4,c = 5)
# a = 1
# b = (2, 3, 4)
# c = 5 -
如果在形参的开头直接写一个
*
,则要求我们的所有的参数必须以关键字参数的形式传递1
2
3
4
5def fn2(*,a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
fn2(a=3,b=4,c=5) -
*
形参只能接收位置参数,而不能接收关键字参数1
2
3
4
5def fn3(*a) :
print('a =',a)
# fn3(a=2) # fn3() got an unexpected keyword argument 'a'
fn3([1,2],3) # a = ([1, 2], 3)
要想接收不定长的关键字参数, 可以在参数前加**
, **
形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中 , 字典的key就是参数的名字,字典的value就是参数的值 . **
形参只能有一个,并且必须写在所有参数的最后 .
1 | def fn3(b,c,**a) : |
传递实参时,也可以在序列类型的参数前添加星号,这样他会自动将序列中的元素依次作为参数传递 . 此过程称为参数的解包(拆包) , 这里要求序列中元素的个数必须和形参的个数的一致 .
1 | def fn4(a,b,c): |
类似地 , 可以在传递实参时 , 通过**
实现对字典的解包.
1 | def fn4(a,b,c): |
返回值
返回值就是函数执行以后返回的结果 , 可以通过 return
来指定函数的返回值 . return
后边跟什么值,函数就会返回什么值 . return
后边可以跟任意的对象,返回值甚至可以是一个函数 .如果仅仅写一个return
或者 不写return
,则相当于return
None
. 在函数中,return
下方的代码都不会执行,return
一旦执行函数自动结束 .
体会 return
break
与continue
的区别:
1 | def fn4() : |
通过下述代码 , 体会函数名称 与 函数调用之间的区别.
1 | def fn5(): |
文档字符串
help()
是Python中的内置函数 , 通过help()
函数可以查询python中的函数的用法 .
语法:help(函数对象)
1 | help(print) # 获取print()函数的使用说明 |
执行结果:
1 | Help on built-in function print in module builtins: |
在定义函数时,可以在函数内部编写文档字符串,文档字符串就是函数的说明 , 当我们编写了文档字符串时,就可以通过help()
函数来查看函数的说明 , 文档字符串非常简单,其实直接在函数的第一行写一个字符串就是文档字符串 .
1 | def fn(a,b,c): |
执行结果 :
1 | Help on function fn in module __main__: |
可以通过形参:类型
的方式来在帮助文档中显示参数类型 , 可以通过->
来指定返回值类型 , 具体参考示例:
1 | # 帮助文档中会显示所需的参数类型和返回值类型 , 其中c的参数类型为str,并赋予了默认值'hello' |
执行结果:
1 | Help on function fn in module __main__: |
作用域与命名空间
作用域(scope)
作用域指的是变量生效的区域 , 在Python中一共有两种作用域 .
- 全局作用域
- 全局作用域在程序执行时创建,在程序执行结束时销毁
- 所有函数以外的区域都是全局作用域
- 在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问
- 函数作用域
- 函数作用域在函数调用时创建,在调用结束时销毁
- 函数每调用一次就会产生一个新的函数作用域
- 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问
当我们使用变量时,会优先在当前作用域中寻找该变量,如果有则使用,如果没有则继续去上一级作用域中寻找,如果有则使用,如果依然没有则继续去上一级作用域中寻找,以此类推 .直到找到全局作用域,依然没有找到,则会抛出异常 : NameError: name ‘xxx’ is not defined
1 | b = 20 # 全局变量 |
如果希望在函数内部修改全局变量,则需要使用global
关键字,来声明变量 .
1 | a = 20 |
命名空间(namespace)
命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中 , 每一个作用域都会有一个它对应的命名空间 , 全局命名空间,用来保存全局变量。函数命名空间用来保存函数中的变量 . 命名空间实际上就是一个字典,是一个专门用来存储变量的字典 .
locals()
用来获取当前作用域的命名空间 . 如果在全局作用域中调用locals()
则获取全局命名空间,如果在函数作用域中调用locals()
则获取函数命名空间 . 返回的是一个字典 .
1 | def fn2(): |
执行结果:
1 | {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000024A09918310>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'test.py', '__cached__': None, 'fn2': <function fn2 at 0x0000024A099D5160>, 'a': 20, 'fn3': <function fn3 at 0x0000024A099D51F0>, 'scope': {...}} |
既然命名空间就是一个字典 , 那么也可以使用操作字典的方式来添加变量 , 但是一般不这么做.
1 | scope = locals() |
类似地 , 使用globals()
函数可以在任意位置获得全局命名空间 .
1 | def fn4(): |
递归
递归简单理解就是自己去引用自己!
从前有座山,山里有座庙,庙里有个老和尚讲故事,讲的是:
从前有座山,山里有座庙,庙里有个老和尚讲故事,讲的是:
从前有座山,山里有座庙,庙里有个老和尚讲故事,讲的是:
从前有座山,山里有座庙,庙里有个老和尚讲故事,讲的是:
. . .
递归式函数,在函数中自己调用自己!
递归是解决问题的一种方式,它和循环很像 , 它的整体思想是,将一个大问题分解为一个个的小问题,直到问题无法分解时,再去解决问题 .
递归式函数的两个要件:
-
基线条件
问题可以被分解为的最小问题,当满足基线条件时,递归就不在执行了
-
递归条件
将问题继续分解的条件
1 | def factorial(n): |
递归和循环类似,基本是可以互相代替的,循环编写起来比较容易,阅读起来稍难 , 递归编写起来难,但是方便阅读 .
高阶函数
高阶函数至少要符合以下两个特点中的一个.
- 接收一个或多个函数作为参数
- 将函数作为返回值返回
通过把函数作为参数传递的方式 , 可以使函数更加灵活 . 下面是利用高阶函数来实现筛选列表元素的功能 , 体会这么做的好处.
1 | # 创建一个列表 |
Python中提供了内置函数filter()
可以从序列中过滤出符合条件的元素,保存到一个新的序列中 . 该函数就是一个参数为函数的高阶函数 , 其参数有:
- 函数,根据该函数来过滤序列(可迭代的结构)
- 需要过滤的序列(可迭代的结构)
该函数的返回值是一个filter对象. 可以通过list()
函数将其转换为列表.
1 | # 创建一个列表 |
上述代码中fn2
是作为参数传递进filter()
函数中 , 而fn2
实际上只有一个作用,就是作为filter()
的参数 , filter()
调用完毕以后,fn2
就已经没用 , 为了简化代码 , 可以使用匿名函数的方式.
语法: lambda 参数列表 : 返回值
lambda
函数表达式专门用来创建一些简单的函数,他是函数创建的又一种方式 , 匿名函数一般都是作为参数使用,其他地方一般不会使用 .
1 | # 以下两个函数功能等价 |
Python中另一个内置高阶函数map()
可以实现 对可迭代对象进行映射操作 , 然后将其添加到一个新的对象中返回.
1 | l = [1,2,3,4,5,6,7,8,9,10] |
对于序列的操作中 , 可以使用sort()
方法来实现对列表的排序 , sort()
方法默认是直接比较列表中的元素的大小 , 在sort()
可以接收一个关键字参数 key
, key
需要一个函数作为参数,当设置了函数作为参数 , 每次都会以列表中的一个元素作为参数来调用函数,并且使用函数的返回值来比较元素的大小 .
1 | l = ['bb','aaaa','c','ddddddddd','fff'] |
sort()
是用来列表排序的方法 , Python中还存在一个函数可以实现排序功能 , sorted()
, 这个函数和sort()
的用法基本一致,但是sorted()
可以对任意的序列进行排序 . 并且使用sorted()
排序不会影响原来的对象,而是返回一个新对象 .
1 | l = [2,5,'1',3,'6','4'] |
闭包
上面介绍的是参数是函数的高阶函数 , 这里介绍返回值是函数的高阶函数 , 这种高阶函数称为闭包. 通过闭包可以创建一些只有当前函数能访问的变量 , 可以将一些私有的数据藏到的闭包中 .
1 | def fn(): |
形成闭包的条件:
- 函数嵌套
- 将内部函数作为返回值返回
- 内部函数必须要使用到外部函数的变量
1 | def make_averager(): |
装饰器
如果有需求希望在函数执行前输出’‘开始执行’’ , 在函数结束后输出"执行完毕" , 显然如果修改每一个函数增加两个print
语句以实现这个功能 , 太繁琐且不方便后期维护 , 同时也违反了程序设计的OCP原则. 所谓OCP原则 , 简单来说就是程序应该开放对程序的扩展 , 关闭对程序的修改 . 可以通过装饰器来来实现这一功能.
1 | # 实现上述功能的装饰器函数 |
像begin_end
这种函数我们就称它为装饰器 , 通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展 , 在开发中,我们都是通过装饰器来扩展函数的功能的 .
在定义函数时,可以通过@
装饰器,来使用指定的装饰器,来装饰当前的函数 , 可以同时为一个函数指定多个装饰器,这样函数将会按照从内向外的顺序被装饰 .
1 | def fn3(old): |
本节练习
创建一个函数 power 来为任意数字做幂运算 n ** i
1 | def power(n , i): |
创建一个函数,用来检查一个任意的字符串是否是回文字符串,如果是返回True,否则返回False
1 | def hui_wen(s:str)-> bool: |