一、函数(function)的简介
函数也是一个对象。对象是内存中专门用来存储数据的一块区域。
函数用来保存一些可执行的代码,并且可以在需要的时候对这些语句多次调用。
函数名是函数的对象,函数名+()是调用函数执行。
'''
函数简介:
函数也是一个对象;
对象是内存中专门用来存储数据的一块区域。
函数用来保存一些可执行的代码,并且可以在需要的时候对这些语句多次调用。
语法:
def 函数名([形参1,形参2,形参3...]):
代码块
'''
# 函数名是一个对象,跟变量一样,用来存储数据的地址,fn()调用函数
def fn():
print('这是一个函数')
print(fn) #打印地址变量fn的内存地址
#返回:<function fn at 0x000002879FB7C9D8>
f=fn #在这里fn是函数对象
f()#调用fn函数,并执行代码
二、函数的参数
在定义函数的时候,可以在函数名后面的括号里面定义数量不等的形参;多个形参我们用逗号隔开;
形参(形式参数)定义形参就是相当于在函数内部定义了变量,但是并没有赋值;
实参(实际参数)函数定义时指定了形参,那么调用函数的时候也必须传递实参;实参将会赋值给对应的形参,有几个形参就有几个实参。
'''
函数的参数:
在定义函数的时候,可以在函数名后面的括号里面定义数量不等的形参;
多个形参我们用逗号隔开;
形参(形式参数)定义形参就是相当于在函数内部定义了变量,但是并没有赋值;
实参(实际参数)函数定义时指定了形参,那么调用函数的时候也必须传递实参;
实参将会赋值给对应的形参,有几个形参就有几个实参。
'''
def fn2(a,b):#a,b为形参
print('a=',a)
print('b=',b)
fn2(10,11) #实参,实参个数与形参个数相同
三、函数的传递方式
1、定义形参的时候我们可以为形参指定一个默认值,指定了默认值以后,如果用户传递了参数,默认值不会有任何作用;如果用户没有传递,默认值才会生效。
2、位置传参数:
位置传参就是将对应位置的实参传递给对应位置的形参;
3、关键字传参:
关键字传参可以不按照关键字定义的顺序的传参数,而是根据参数名来传递参数;
4、位置传参和关键字传参可以混合使用
混合使用的时候,必须将位置参数写到关键字参数前面
'''
函数的传递方式:
1、定义形参的时候我们可以为形参指定一个默认值,
指定了默认值以后,如果用户传递了参数,默认值不会有任何作用;
如果用户没有传递,默认值才会生效。
2、位置传参数:
位置传参就是将对应位置的实参传递给对应位置的形参;
3、关键字传参:
关键字传参可以不按照关键字定义的顺序的传参数,而是根据参数名来传递参数;
4、位置传参和关键字传参可以混合使用
混合使用的时候,必须将位置参数写到关键字参数前面
'''
def fn(a=10,b=50,c=4):
print(a,b,c)
#位置传参
fn(1,2,3)
fn(1,2)
#关键字传参
fn(b=3,c=6,a=1)
#位置传参和关键字传参可以混合使用
#混合使用的时候,必须将位置参数写到关键字参数前面
fn(1,c=6,b=1)
四、实参的类型
函数在调用的时候,解释器不会检查实参的类型;实参的类型可以使任意的对象。
def fn(a=10,b=50,c=4):
print(a,b,c)
'''
实参的类型:
函数在调用的时候,解释器不会检查实参的类型;
实参的类型可以使任意的对象
'''
def fn2(a):
print('a=',a)
b=fn
fn2(b) #a= <function fn at 0x0000020F047BCB88>
def fn3(a):
#在函数中对形参重新赋值不会影响其他的变量
a=30 #形参变量的地址跟实参的地址不一样
print('a=',a,'id(a)=',id(a))
c=10
#两个变量的地址不一样
fn3(c) #a= 30 id(a)= 140715752072496
print('c=',c,'id(c)=',id(c))#c= 10 id(c)= 140715752071856
def fn4(a):
#如果形参在执行的时候,当我们通过形参去修改对象的值时,
#会影响到指向该对象的变量
a[0]=30
print('a=',a,'id(a)=',id(a))
c=[1,2,3]
#两个变量的地址是一样的
fn4(c) #a= [30, 2, 3] id(a)= 2263493661128
print('c=',c,'id(c)=',id(c))#c= [30, 2, 3] id(c)= 2263493661128
五、不定长参数
1、在定义函数的时候,可以在形参的前面加上一个*,这样这个形参将会获取所有的实参,它将所有的实参保存到一个元组当中。
2、**形参可以接收其他的关键字参数,他会将这些参数统一保存到一个字典当中。
'''
不定长参数:
定义一个函数来实现任意数的和
1、在定义函数的时候,可以在形参的前面加上一个*,
这样这个形参将会获取所有的实参,它将所有的实参保存到一个元组当中。
2、**形参可以接收其他的关键字参数,他会将这些参数统一保存到一个字典当中。
3、总结
一个*解决位置参数;
两个*解决关键字参数。
'''
def s(*a):
print('a=',a,type(a))#返回a= (1, 1, 2, 3, 4, 5, 6, 7, 9) <class 'tuple'>
#定义一个变量来保存结果
r=0
#遍历元组,将元组中的元素取出来并进行累加
for n in a:
r+=n
print(r)
s(1,1,2,3,4,5,6,7,9)
#可变长参数不是必须要写到最后的,
#但是注意,带*参数后面的参数,必须以关键字的形式传参
def s(b,*a,c=10):
print('a=',a,type(a))
#定义一个变量来保存结果
r=0
#遍历元组,将元组中的元素取出来并进行累加
for n in a:
r+=n
print(r,'c=',c)
s(1,1,2,3,4,5,6,7,9)#返回a= (1, 2, 3, 4, 5, 6, 7, 9) <class 'tuple'>,37 c= 10,c值没有更改,还是用默认值
s(1,1,2,3,4,5,6,7,9,c=100)#返回a= (1, 2, 3, 4, 5, 6, 7, 9,c=100) <class 'tuple'>,37 c= 100,c值发生了更改
#**形参可以接收其他的关键字参数,他会将这些参数统一保存到一个字典当中。
#字典当中的key是参数的名字,字典当中的value是参数的值
#**形参只能有一个,必须写在最后
def ss(b,c,**a):
print('a=',a,type(a))
print('b=', b)
print('c=', c)
ss(b=1,d=2,c=3,e=4,f=5)
'''
返回值:
a= {'d': 2, 'e': 4, 'f': 5} <class 'dict'>
b= 1
c= 3
'''
六、参数解包
序列都可以用一个来解包;字典不是序列,字典的解包用两个。
'''
参数解包
序列都可以用一个*来解包;
字典不是序列,字典的解包用两个*。
'''
def fns(a,b,c):
print('a=', a)
print('b=', b)
print('c=', c)
t=(5,6,7)
print(*t)#参数的解包,返回:5 6 7
fns(*t)#序列中的元素必须与形参的长度保持一致
tta={'a':1,'b':2}
print(**tta) #解包,返回字典的键:a= 5 b= 6 c= 7
七、函数的返回值
返回值就是函数执行后返回的结果。
用return来指定函数的返回值,return后面可以跟任意对象,甚至可以是一个函数。
如果仅仅只写一个return或者不写return,则相当于return None。
在函数中,return后面的代码不会执行,return一旦执行,函数自动结束。
我们可以直接使用函数的返回值,也可以通过变量来接收函数的返回值。
'''
求任意数的和
'''
def s(*num):
#定义一个变量来保存结果
result=0
#遍历元组,并将元组当中的元素进行累加
for n in num:
result+=n
print(result)
s(1,2,3,4,5,6)
def fn():
#用return来指定函数的返回值
#return后面可以跟任意对象,甚至可以是一个函数。
return 100
#我们可以直接使用函数的返回值,也可以通过变量来接收函数的返回值。
r=fn() #这个函数执行的结果就是返回值的结果。
print(r)
def fn():
def fn2():
print('hello')
return fn2 #返回这个函数时不需要加括号。调用函数才需要加括号
r=fn()
print(r)#返回<function fn.<locals>.fn2 at 0x000002B67F44CC18>;r在此处是一个函数
r() #返回hello
def fn3():
#如果仅仅只写一个return或者不写return,则相当于return None。
print('python')
print('java')
r=fn3()
print(r) #因为没有写return的返回值,
'''
返回值:
python
java
None
'''
def fn3():
#如果仅仅只写一个return或者不写return,则相当于return None。
#在函数中,return后面的代码不会执行,return一旦执行,函数自动结束。
print('python')
return
print('java')
r=fn3()
print(r) ##在函数中,return后面的代码不会执行,return一旦执行,函数自动结束。
'''
返回值:
python
None
'''
def fn4():
for i in range(10):
if i==3:
break #执行到i=3时,循环中没有return函数时,可以打印后面的‘循环执行完毕’
if i==4:
continue #跳过本次循环,后面的继续运行
if i==5:
return #return可以用来结束函数,后面的循环不在执行,也不会打印‘循环执行完毕’
print(i)
print('循环执行完毕')
fn4()
def fn5():
return 1
print(fn5) #返回<function fn5 at 0x0000015D30A3CF78>;是函数对象
print(fn5()) #返回1,fn5()是调用函数,打印的是函数的返回值
八、文档字符串
'''
文档字符串
help()函数,可以查询其他函数的用法;
语法:help(函数对象)
'''
help(print)
'''
返回值:
Help on built-in function print in module builtins:
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
'''
def fn(a,b,c):
'''
:param a: 类型 int 默认值 ...
:param b: 类型 str 默认值...
:param c: 类型 bool 默认值...
:return: int
'''
return 1
help(fn)
'''
返回值:
Help on function fn in module __main__:
fn(a, b, c)
:param a: 类型 int 默认值 ...
:param b: 类型 str 默认值...
:param c: 类型 bool 默认值...
:return: int
'''
def fn2(a:int,b:str,c:bool)->int:#很少使用
'''
:param a: 类型 int 默认值 ...
:param b: 类型 str 默认值...
:param c: 类型 bool 默认值...
:return: int
'''
return 1
help(fn2)
'''
返回值:
Help on function fn2 in module __main__:
fn2(a: int, b: str, c: bool) -> int
:param a: 类型 int 默认值 ...
:param b: 类型 str 默认值...
:param c: 类型 bool 默认值...
:return: int
'''
九、函数的作用域
作用域指的就是变量生效的区域。
全局作用域:在程序执行时创建,在程序执行结束时销毁;所有函数以外的部分都是全局作用域,在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置访问。
函数作用域:在函数调用时创建,在调用结束后销毁,函数每调用一次就会产出一个新的作用域;在函数作用域中定义的变量都是局部变量,它只能在函数内部被访问到。
'''
作用域指的就是变量生效的区域。
'''
b=20
def fn():
a=10#a定义在函数内部,所以它的作用域是函数内部,函数外部是访问不到的
print('函数内部:','a=',a)
print('函数内部:', 'b=', b)
fn()
print('函数外部:','b=',b)
'''
返回值:
函数内部: a= 10
函数内部: b= 20
函数外部: b= 20
'''
def fn2():
a=30
def fn3():#fn3中可以访问到fn2中的变量,fn2的变量相当于fn3的全局变量
print('fn3中:','a=',a)
fn3()
fn2()
'''
在函数内部修改全局变量的方法:global
'''
a=20
def fn3():
#如果希望在函数内部修改全局变量,则需要使用global关键字,来声明
global a #声明在函数内部使用的a是全局变量,再去修改a,是修改的全局变量a
a=10
print('函数内部:','a=',a)
fn3()
print('函数外部:','a=',a)
'''
返回值:
函数内部: a= 10
函数外部: a= 10
'''
十、命名空间
命名空间实际上就是一个字典,是一个专门用来存储变量的字典。
locals()用来获取当前作用域的命名空间,如果在全局作用域中调用locals(),则获取的是全局命名空间,如果在函数作用域中调用locals(),则获取的是函数的命名空间,返回的是一个字典。
s=locals() #当前的命名空间
s['c']=100 #向字典中添加key-value,就相当于在全局中创建了一个变量
print(c)#返回:100
print(s)#返回:{...,'s': {...}, 'c': 100}
def fn4():
s=locals()#在函数内部调用locals()会获取到函数的命名空间
s['c']=20
print(s) #返回:{'c': 20}
fn4()
'''
在函数中获取全局的命名空间。
'''
s=locals() #当前的命名空间
s['c']=100 #向字典中添加key-value,就相当于在全局中创建了一个变量,c=100
def fn4():
a=10
#global()函数可以用来在任意位置获取全局命名空间
global_space=globals()
print(global_space)#返回{..., 's': {...}, 'c': 100, 'fn4': <function fn4 at 0x000002932F7BC9D8>}
fn4()
十一、递归式函数
递归简单的理解就是自己调用自己;
递归式函数就是在函数中自己调用自己。
递归式函数有两个条件:
1、基线条件:
问题可以被分解为最小的问题,当满足基线条件的时候,递归就不再执行了。
2、递归条件:
将问题可以分解的条件。
'''
举例:求10的阶乘。
'''
#方法一:循环方法
def func(n):
result=n
for i in range(1,n):
result*=i
return result
print(func(10))
# #无穷递归,类似于死循环
# def fn():
# fn()
# fn()
#方法二:递归方法
def fn(n):
#基线条件,判断n是否是1,如果是1不能再继续递归。
if n==1:
return 1
#递归条件
return n*fn(n-1)
print(fn(10))
递归的练习:
'''
练习一:创建一个函数,来为任意数字做幂运算,n**i。
'''
def fn1(n,i):
#参数 n:做幂运算的数字,i做幂运算的次数
#基线条件:
if i==1:
#求一次幂运算
return n
#递归条件:
return n*fn1(n,i-1)
print(fn1(3,2))
'''
练习二:创建一个函数,用来检测任意字符串是否是回文字符串,
如果是返回True,如果不是返回False
什么是回文字符串?
回文字符串指的是字符串从后往前读和从前往后读是一样的,例如abcba
'''
def fn2(string):
#充分利用字符串的首尾...
#基线条件
if len(string)<2:
return True
elif string[0] != string[-1]:
return False
#递归条件
return fn2(string[1:-1])
print(fn2('avava'))
十二、高阶函数
1、定义
高阶函数:满足以下2个条件中的任意一个都是高阶函数。
(1)接收函数作为参数的函数;
(2)将函数作为返回值的函数;
'''
高阶函数:满足以下2个条件中的任意一个都是高阶函数。
1、接收函数作为参数的函数;
2、将函数作为返回值的函数;
高阶函数的好处:
当我们使用一个函数作为参数时,实际上,我们就是将指定的代码传递给了目标函数。
'''
l=[1,2,3,4,5,6,7,8,9,10]
#定义一个函数,可以将制定的列表的所有的偶数保存到一个新的列表并返回。
def fn(lst):
#创建一个新的列表
new_lst=[]
for n in lst:
#判断奇数和偶数
if n%2==0:
new_lst.append(n)
return new_lst
print(fn(l))
'''
函数内部定义函数
'''
def fn1(lst):
#函数中再定义一个函数:用来检测任意数的偶数;
def fn2():
if n%2==0:
return True
#函数中再定义一个函数:用来检测指定数字是否大于5;
def fn3():
if n > 5:
return True
#创建一个新的列表
new_lst=[]
for n in lst:
#判断奇数和偶数
if not fn3():
new_lst.append(n)
return new_lst
print(fn1(l))
'''
把规则移除函数外,函数名作为函数的形参
'''
# 定义一个函数:用来检测任意数的偶数;
def fn2(n):
if n % 2 == 0:
return True
# 定义一个函数:用来检测指定数字是否大于5;
def fn3(n):
if n > 5:
return True
def fn1(func,lst):
#创建一个新的列表
new_lst=[]
for n in lst:
#判断奇数和偶数
if not func(n):
new_lst.append(n)
return new_lst
print(fn1(fn2,l))
2、匿名函数
匿名函数:lambda表达式,只会调用一次,执行完后,它从内存中消失。
lambda函数表达式是用来创建一些简单的函数,它是函数创建的另外一种方式。
语法:lambda 参数列表:返回值
#filter()可以从序列中过滤出符合条件的元素,保存到一个新的序列中。
#参数:1、函数,根据该函数来过滤序列(可迭代结构)
# 2、需要过滤的序列(可迭代结构)
#返回值:过滤后新的序列
l=[1,2,3,4,5,6,7,8,9,10]
# 定义一个函数:用来检测任意数的偶数;
def fn2(n):
return n % 2 == 0 #返回值为满足n % 2 == 0的n
# 函数中再定义一个函数:用来检测指定数字是否大于5;
def fn3(n):
if n > 5:
return True
print(filter(fn2,l))#<filter object at 0x000002743091DE48>
print(list(filter(fn2,l)))#[2, 4, 6, 8, 10]
'''
匿名函数:lambda表达式,只会调用一次,执行完后,它从内存中消失。
lambda函数表达式是用来创建一些简单的函数,它是函数创建的另外一种方式。
语法:lambda 参数列表:返回值
'''
#定义一个函数,实现两个数的和
def fn5(a,b):
return a+b
print(fn5(4,5))
print(lambda a,b:a+b)#<function <lambda> at 0x000001E3D135CDC8>
print((lambda a,b:a+b)(1,2))
fun=lambda a,b:a+b
print(fun(4,5))
'''
map()函数可以对可迭代对象中所有元素做指定的操作,
然后将其添加到一个新的对象中返回。
'''
l=[1,2,3,4,5,6,7,8,9,10]
# lambda i:i+1,l#将l各元素加1,然后以新列表返回
r=map(lambda i:i+1,l)
print(list(r))
'''
sort()函数可以对列表当中的元素进行排序。
sort()函数中有一个关键字,需要一个函数名作为参数,
'''
lst=['as','dgfa','gah']
lst.sort() #排的是字符串的大小
print(lst)#['as', 'dgfa', 'gah']
lst=['as','dgfa','gah']
lst.sort(key=len) #排的是字符串的长度,key=len使用的就是高阶函数
print(lst)#['as', 'gah', 'dgfa']
3、闭包
闭包:
将函数作为返回值返回,也是一种高阶函数。
通过闭包可以创建一些只有当前函数才能访问的对象,
还可以将一些私有的数据藏到闭包当中。
形参闭包的三个必须条件:
1、函数嵌套;
2、将内部函数名作为返回值返回;
3、内部函数必须使用的外部函数的变量。
什么时候用闭包?
当有些数据不希望别人访问或修改的时候,就用闭包,这样可以保证一个数据的安全性。
'''
闭包:
将函数作为返回值返回,也是一种高阶函数。
通过闭包可以创建一些只有当前函数才能访问的对象,
还可以将一些私有的数据藏到闭包当中。
形参闭包的三个必须条件:
1、函数嵌套;
2、将内部函数名作为返回值返回;
3、内部函数必须使用的外部函数的变量。
什么时候用闭包?
当有些数据不希望别人访问或修改的时候,就用闭包,这样可以保证一个数据的安全性。
'''
def fn():
#函数内部再定义一个函数
def fn2():
a=10
print('fn2',a)
#在内部函数将fn2内存地址作为返回值返回
return fn2
r=fn()
r()#fn2 10
#求多个数的平均值
nums=[]#列表在全局作用域中是不安全的,容易被修改和覆盖
def avg(n):
nums.append(n)
return sum(nums)/len(nums)
print(avg(10))
print(avg(21))
#通过闭包的方式扭转变量在全局作用域中不安全的特性,闭包必然有函数的嵌套。
def new_avg():
nums = [] # 列表在全局作用域中是不安全的,容易被修改和覆盖
def avg(n):
nums.append(n)
return sum(nums)/len(nums)
return avg
a=new_avg()
print(a(21))
print(a(10))
4、装饰器
装饰器:
通过装饰器,可以在不修改原函数的情况下对函数进行扩展;
在开发当中,都是通过装饰器来扩展函数的功能的。
'''
我们可以直接修改函数中的代码,会产生一些问题:
1、修改的函数很多;
2、不方便后期的维护;
3、会违反开闭原则(OCP)程序设计:
OCP原则就是要求对程序的扩展,但是要关闭对程序的修改。
——-->>>>>>
装饰器:
通过装饰器,可以在不修改原函数的情况下对函数进行扩展;
在开发当中,都是通过装饰器来扩展函数的功能的。
'''
def add(a,b):
return a+b
def mul(a,b):
return a*b
def fn():
print('fn')
#创建一个新的函数,来对原函数进行扩展
def fn2():
print('start')
fn()
print('end')
fn2()
def new_add(a,b):#用新函数扩展已有的函数-->>装饰器的引入
print('start')
r=add(a,b)
print('end')
return r
print(new_add(1,2))
'''
装饰器的使用
'''
#一、无形参的函数扩展
def start_end(old):
#参数old:要扩展的函数对象
#用来对其他函数进行扩展
#创建一个新函数
def new_function():
print('start')
old()
print('end')
#返回新函数
return new_function
f=start_end(fn)
f()
#二、有形参的函数扩展
def start_end(old):
#参数old:要扩展的函数对象
#用来对其他函数进行扩展
#创建一个新函数
def new_function(a,b):
print('start')
result=old(a,b)
print('end')
#返回函数执行的结果
return result
#返回新函数
return new_function
f=start_end(add)
print(f(1,2))
#三、采用不定长参数的形式扩展函数,实现通用形式的函数扩展
def start_end(old):#类似这种函数,在原有函数上增加新的内容,其实就是一个装饰器
#参数old:要扩展的函数对象
#用来对其他函数进行扩展
#创建一个新函数
def new_function(*a,**b):#*a,**b在此处是装包,变成元组和字典的形式
print('start')
result=old(*a,**b)#*a,**b在此处是解包,
print('end')
#返回函数执行的结果
return result
#返回新函数
return new_function
f=start_end(fn)
f()
f=start_end(add)
r=f(1,2)
print(r)
@start_end #用start_end函数,对say_baybay进行装饰
def say_baybay():
print('new function')
say_baybay()