函数简介
- 函数也是一个对象
- 函数用来保存一些可执行的代码,并且可以对这些语句进行多次调用
- 创建函数:
def 函数名([形参1, 形参2,..., 形参n]): ---> 形参:形式参数
代码块
- 函数中的代码不会立即执行,需要调用才能执行
- 函数名要符合标识符规范(不能以数字开头)
例:
def fn():
pass
与print()函数类比
- 函数的调用
fn是函数对象,fn()调用函数
print是函数对象,print()调用函数
函数的参数
- 形参(形式参数):定义函数时指定形参,相当于在函数内部声明了变量,但是并不赋值
定义形参时,可以为形参指定默认值
指定默认值以后,如果用户传递了实参则默认值没有作用
如果用户没有给该形参传递实参,则默认值会生效
例:
def function(a, b, c=10)
pass
---> 形参c指定了默认值为10
- 实参(实际参数):调用函数时,传递实参(有几个形参就得传几个实参)
1、位置参数:将对应位置的实参赋值给相应位置的形参
第一个实参赋值给第一个形参,以此类推
如:若定义函数 def fn(a, b, c),则调用时需要传位置参数: fn(1, 2, 3), 位置一一对应(a=1,b=2,c=3)
2、关键字参数:可以不按照形参定义的顺序去传递
如:若定义函数 def fn(a, b, c),则调用函数时可以全部使用关键字参数:fn(b=2,a=1,c=3),顺序不重要
- 位置参数和关键字参数可以混合使用:此时必须将位置参数写在前面,如:
def fn(a, b, c) 调用: fn(2,1,c=3) ---> a=2, b=1, c=3
- 函数在调用时,解析器不会检查实参的类型,实参可以传递任意类型的对象
甚至还可以传递函数,如定义函数def fn() , 那么可以这样调用:fn(fn),注意不是: fn(fn())
因此,当传递一个可变对象时,如果不希望函数内部去改变外部变量指定的对象,则需要传递外部变量的副本而不是其本身,如:
def fn(a):
a[0] = 30 # a指向的对象被改变了
print(a)
c = [1,2,3,4] # c是列表,为可变对象,一旦调用函数fn(),则c和函数fn()内部变量a指向同一个对象
fn(c) # 调用函数fn()后,c指向的对象也被改变: c = [30,2,3,4]
如何避免外部可变对象被改变:
传递副本: fn(c.copy()) 或 fn(c[:])
不定长的参数
- 定义函数时,可以在形参前面加一个*,这样这个形参将会获得所有的实参,它会将所有实参保存到一个元组中
例:
def fn(*a):
print('a =', a, type(a)) ---> a是元组tuple
调用: fn(1,2,3,4,5,6) ---> a = (1,2,3,4,5,6) (这是一个装包的过程,与解包相反)
- 定义一个函数,可以求任意个数字的和:
def sum(*nums):
# 定义一个变量来保存结果
result = 0
# 遍历元组,将元组中的数进行累加
for n in nums:
result += n
print(result)
调用: sum(1,2,3,4,0,6,7) ---> 传入实参不定长
注意:带星号*的形参只能有一个,可以跟其他参数配合使用
可变参数(带星号*)不是必须写在最后,但带*的参数后的所有参数,必须以关键字参数的形式传递:
def fn(*a, b, c): 调用:fn(1,2,3,4,5,b=6,c=7) ---> a=(1,2,3,4,5) b=6 c=7
def fn(a, *b, c): 调用:fn(1,2,3,4,5,6,c=7) ---> a=1 b=(2,3,4,5,6) c=7
def fn(a, b, *c): 调用:fn(1,2,3,4,5,6,7) ---> a=1 b=2 c=(3,4,5,6,7)
特例:如果在形参的开头直接写一个*,则要求我们所有的参数<必须>以关键字参数的形式传递:(顺序不定)
def fn(*, a, b, c): 调用:fn(a=1, b=2, c=3)
- *形参(只接收位置参数),而不能接收关键字参数
def fn(*a): 调用:fn(a=1,b=2,c=3) ---> 报错,因为 *形参 不能接收关键字参数
解决办法: **形参 可以接收其他的关键字参数,将它们统一保存在一个字典中,key:参数的名字,value:参数的值
def fn(**a): 调用:fn(d=1,r=4,y=6) ---> a={'d':1, 'r':4, 'y':6}
**形参(只接收关键字参数) 只能有一个,并且必须写在所有参数的后面
def fn(b, c, **a): 调用:fn(b=1, d=10, c=3, j=34) ---> a={'d':10, 'j':34}, b=1, c=3
参数的解包
def fn(a, b, c): 调用: fn(t) ,其中t=(1,2,3) ---> 调用错误,参数不够
解决方案:
1、fn(t[0], t[1], t[2]),其中t=(1,2,3) ---> 可以,但是很麻烦
2、解包(拆包): 调用:fn(*t),其中t=(1,2,3) ---> 调用正确,此过程为解包(拆包),很简单
注意:只要是序列就可以解包,并且要求序列中元素个数必须与形参个数一致
*t 对元组等序列进行解包
**t 对字典进行解包
返回值
- 函数执行后返回的结果
- 可以通过return来指定函数的返回值(可以返回任意对象,甚至可以返回一个函数)
- 可以直接使用函数的返回值,也可以通过变量来接收返回值
例:def fn():
def fn2():
print('hello')
return fn2
r = fn() ---> r 收到的返回值是函数fn2()
- 如果仅仅写retur,或者不写return,则相当于return None
- 在函数中,return后的代码都不会执行,return一旦结束函数自动结束(类似于循环中的break)
- fn 和 fn() 的区别:
print(fn): fn是函数对象,打印fn实际上是打印函数对象,<function fn at 0x05771BB8>
print(fn()): fn()是在调用函数,打印fn()实际上是在打印fn()函数的返回值
(跟在return后面的值,多个值则以元组的形式返回)
文档字符串
help()是python的内置函数,可以查询python中函数的用法
语法:
help(函数对象) 例: help(print) ---> 获取函数print()的说明
那么,在自己写的函数中,怎么在函数内部编写文档字符串(即函数的说明)?
- 文档字符串(doc str)
- 当编写了文档字符串(函数说明)时,就可以通过help()函数来查看该函数的说明
- 文档字符串很简单,直接在函数的第一行写一个字符串就是文档字符串
例:
def fn(a,b,c):
'''
这是一个文档字符串的示例:
函数的作用: ... ---> 三重单引号中间的字符串即为文档字符串(函数说明)
函数的参数: ...
'''
return a,b,c
help(fn)
- 另外,函数上有些描述(这些描述只起到提示作用,不强制要求,也不会报错):
def fn(a:int, b:str='hello', c:list='[1,2,3]') -> int:
解释:
a:int ---> 表示参数a需要传入int类型实参,但也不是强制要求,输入其他类型也不报错
b:str='hello' ---> 表示参数b需要传入str类型实参,且默认值是'hello'
c:list='[1,2,3]' 同上
-> int ---> 表示函数fn()的返回值是int类型, 也是提示作用,没有强制要求
以上设置,是为了使用help()函数查看说明时更加方便用户直观了解该函数,没有其他意义
作用域(scope)
- 作用域指的是变量生效的区域
- 在python中有两种作用域:
1、全局作用域:
- 在程序执行时创建,在程序执行结束时销毁
- 所有函数以外的区域都是全局作用域
- 在全局作用域中定义的变量都属于全局变量,可以在程序的任意位置被访问
def fn():
a = 1
return ---> a在函数内部,不是全局变量;但是fn是属于全局变量
2、函数作用域
- 在函数<调用>时创建,在调用结束时销毁(注意不是函数定义时)
- 函数调用依次就会产生一个新的函数作用域
- 在函数作用域中定义的变量,都是局部变量,只能在函数内部被访问
3、变量的查找
- 当我们使用变量时,会优先在当前作用域中寻找该变量,如果有则使用,
如果没有则继续去上一级作用域中寻找,再没有,继续往上一级,以此类推。。。
直到找到全局作用域依然没有找到,则抛出错误:NameError
例1:
def fn():
a = 10
def fn2():
a = 20
print('a =', a)
fn2()
调用fn() ---> a = 20
例2:
a = 10
def fn():
print('a =', a)
调用fn() ---> a = 10
例3:
a = 10
def fn():
a = 20
print('a =', a)
调用fn() ---> a = 20
4、global 关键字
- 如果希望在函数内部修改全局变量,则需要使用global关键字来声明变量
global a # 声明在函数内部使用的变量a是全局变量,此时再去修改a,则意味着在修改全局变量a
例子:
a = 10
def fn():
global a
a = 31
print('a =', a)
调用fn() ---> a = 31 ---> 全局变量a已被修改
命名空间(name space)
- 指的是变量存储的位置,每个变量都需要存储到指定的命名空间中
- 每一个作用域都会有一个对应的命名空间
- 全局命名空间用来保存全局变量,函数命名空间用来保存函数中的变量
- 命名空间实际上就是一个字典,是一个专门用来存储变量的字典
- locals() 用来获取当前作用域的命名空间
- 如果在全局作用域中调用locals()则获取全局命名空间;
如果在函数作用域中调用loclas()则获取函数命名空间
- 返回的是一个字典
例:
scope = locals() # 当前命名空间,为全局空间
print(type(scope)) ---> 得到的是一个字典
- 向scope中添加一个key-value
scope['c'] = 1000 # 向字典中添加key-value就相当于在全局中创建一个变量(一般不建议这么做)
- 同样,在函数内部调用locals()也可以获取到函数的命名空间
也可以通过scope来操作函数的命名空间,但也不建议这么做
- global()函数可以用来在任意位置获取全局命名空间,但是在全局作用域是看不到函数内部的命名空间的
递归
- 问题:求 10!
- 法一:for循环 ---> 此方法是常规方法
- 法二:递归(简单理解就是自己去引用自己)
递归函数:在函数中自己调用自己
无穷递归:如果这个函数被调用,程序的内存会溢出,效果类似于死循环
例:
def fn():
fn() ---> 无穷递归
fn()
递归是解决问题的一种方式,跟循环很像
整体思想是,将一个大问题分解为一个个小问题,直到问题无法分解,再去解决问题
- 递归式函数的两个条件:
1、基线条件
- 问题可以被分解为最小问题,当满足基线条件时,递归结束
2、递归条件
- 将问题继续分解的条件
10! = 10 * 9!
9! = 9 * 8!
8! = 8 * 7!
...
2! = 2 * 1!
1! = 1
def factorial(n):
'''
该函数用来求任意数n的阶乘
参数:
n 要求阶乘的数字
'''
# 基线条件 判断n是否为1,如果为1则此时不能再继续递归
if n == 1:
# 1的阶乘就是1,直接返回1
return 1
# 递归条件
# return n * (n-1)! ---> 伪代码
return n * factorial(n-1)
总结:递归和循环类似,基本上是可以相互代替的
循环编写起来比较容易,阅读起来稍难
递归编写起来难,但是阅读方便
练习:
1、创建一个函数 power 来为任意数字做幂运算 n ** i
2、创建一个函数,用来检查一个任意的字符串是否是回文字符串,是则返回True,否则返回False
回文字符串:字符串从前往后念跟从后往前念是一样的,如'abcba'
函数式编程
- 在python中,函数是一等对象
- 一等对象一般会具有如下特点:
1、对象是在运行时创建的
2、能赋值给变量或作为数据结构中的元素
3、能作为参数传递
4、能作为返回值返回
---> 一般在python中几乎所有的对象都是一等对象
- 高阶函数
- 高阶函数至少要符合以下两个特点之一:
1、接收一个或多个函数作为参数
2、将函数作为返回值返回
例子:
l = [1,2,3,4,5,6,7,8,9]
def fn1(i):
# 功能1
if i > 2:
return True
return False
def fn2(i):
# 功能2
if i % 3 == 0:
return True
return False
注意,函数fn2的等价简单写法: (函数fn1同理)
def fn2(i):
return i % 3 == 0
def fn(func, lst):
'''
函数功能:按照要求对列表进行修改然后返回
函数参数:
lst: 列表
func: 传入的函数(每个函数均有特定功能)
'''
# 定义一个新的列表
new_list = []
for i in lst:
if func(i):
new_list.append(i)
return new_list
fn(fn1, l) # 调用函数fn1,实现功能1
fn(fn2, l) # 调用函数fn2,实现功能2
匿名函数
- filter() ---> 不是匿名函数
- filter()可以从序列中过滤出符合条件(条件由函数来界定)的元素,保存到一个新的序列中
语法: filter(函数, 序列)
参数:
1、函数,根据该函数来过滤序列(可迭代的结构)
2、需要过滤的序列(可迭代的结构)
返回值:
过滤后的新序列(可迭代的结构)
例子:
l = [1,2,3,4,5,6]
def fn(i):
if i % 3 ==0:
return True
return False
print(list(filter(fn, l))) ---> fn作为filter的参数
- lambda (语法糖) ---> 不能实现复杂的功能
(匿名函数一般是作为参数使用,其他时候不常用)
语法:
lambada 参数列表 : 函数列表
例:
lambda a,b : a + b
等价于下列函数:
def fn(a, b):
return a + b
如何调用:
1、(lambda a,b : a+b)(10, 20) ---> 将(10,20)赋值给(a,b)
2、常用的方法:
l = [1,2,3,4,5,6]
r = (lambda a : a > 5 , l)
print(list(r))
- zip() ---> zip()函数接收任意多个(包括0个和1个)序列作为参数,合并后返回一个tuple列表:
a = [1, 2, 3]
b = [4, 5, 6]
ab = zip(a, b) ---> ab = <zip object at 0x0511DFC8>
list(ab) ---> [(1,4), (2,5), (3,6)] # 需要加list()使得ab可视化
- 可以解包进行迭代:
for i,j in zip(a,b):
print(i, j)
- map()
- map()函数可以对可迭代对象中的所有元素做指定的操作,然后将其添加到一个新的对象中返回
例:
l = [1,2,3,4,5,6]
r = map(lambda i: i ** 2, l) ---> 将列表l中的每个元素做平方运算
print(list(r))
- sort() ---> 方法
- 该方法用来对[列表]中的元素进行排序,返回None,但是原来该列表的值会重新排列
- sort()方法默认是直接比较列表中元素的大小
- 在sort()中可以接收关键字参数,key
key需要一个函数作为参数,当设置了函数作为参数
每次都会以列表中的一个元素作为参数来调用函数,并使用函数的返回值来比较元素的大小
例1:
l = ['bb', 'aaaa', 'c', 'dddd', 'fff']
l.sort(key=len) # 传入函数len(),进行字符串长度的比较
例2:
a = [2,4,3,'5','8']
l.sort(key=int) # 传入函数int(),将所有字符转换为int类型再进行比较
- sorted() ---> 函数
- 此函数和sort()方法的用法基本一致,但是sorted()可以对任意序列进行排序
并且使用sorted()排序不会不会影响原来的对象,而是返回一个新的对象
例:
l = [2,4,3,'5','8']
s = sorted(l, key=int) # 同样可以传入函数作为参数
---> 调用函数后,创建一个新的对象赋值给变量s,但是序列l没有被改变
闭包(不常用)
- 将函数作为返回值返回,也是一种高阶函数
- 这种高阶函数被称为闭包,通过闭包可以创建一些只有当前函数能访问的变量
可以将一些私有的数据藏到闭包中
装饰器
# 创建几个函数
def add(a,b):
# 求和
return a+b
def mul(a,b):
# 求积
return a*b
# 希望函数在计算前,打印开始计算,计算结束后打印计算完毕
可以通过修改函数中的代码来完成这个需求,但是会产生一些问题:
1、如果要修改的函数过多,修改起来会比较麻烦
2、不方便后期的维护
3、这样做会违反开闭原则(OCP)
程序的设计,要求开放对程序的扩展,要关闭对程序的修改
希望在不修改原函数的情况下,来对函数进行扩展:
def fn():
print('我是fn函数...')
# 只需要根据现有的函数,来创建一个新的函数
def fn2():
print('函数开始执行...')
fn()
print('函数执行结束...')
fn2() # 调用fn2(),此时也会间接调用fn(),相当于对fn()进行了功能扩展
# 再举个例子:
def add(a,b):
# 求和
return a+b
def new_add(a,b): ---> 对add()函数进行功能扩展,但不修改add()的内容
print('计算开始...')
result = add(a,b)
print('计算结束...')
return result
# 调用
r = new_add(1,2)
# 问题:如果要对mul()函数进行扩展,就需要再写一个函数new_mul(),很麻烦
解决方法:---> 创建一个函数(能创建函数的函数),让这个函数可以自动帮助我们产生函数
def begin_end(old):
'''
函数功能:
用来对其他函数进行扩展,使其它函数可以在执行前打印开始执行,执行后打印执行结束
参数:
old 要扩展的函数对象
'''
# 创建一个新函数
def new_function(a, b):
print('函数开始执行...')
# 调用被扩展的函数
old()
print('函数执行结束...')
# 返回新函数
return new_function
# 调用函数
f = begin_end(add)
f()
........................
太难了,而且不常用,这个部分不学了