函数:这个词属于一个数学概念,在编程语言借鉴了这个概念,表现形式是一段程序代码的组合,也叫“程序集”。有过编程基础的人很容易理解这个概念,当我们编写程序越来越多的时候,程序设计大师们会把散乱的程序进行高度的抽象化的概念。可能有:程序-函数-模块-包-库,这样一个路径。
怎么认识函数?
函数是在一个完整的项目中,某些功能会返回使用,那么我们会讲功能进行“封装”,当我们要使用功能的时候直接调用函数即可。
本质:函数就是对功能的封装
优点:
1. 简化了代码结构,增加了代码的复用度(重复使用的程度)
2. 如果想修改某些功能或者修改某个BUG,修改对应的函数即可。
1. 定义函数:
格式:
def 函数名(参数列表):
语句
return表达式
解释:
* def:函数代码块以def关键字开始
* 函数名:遵循标识符规则
* 参数列表:任何传入函数的参数和变量必须放在圆括号之间,用逗号分隔。参数列表从函数的调用者哪里获取信息。
* ( ) :是参数列表的开始和结束
* ::冒号是函数内容(封装功能)以冒号开始并缩进
* 语句:函数封装的功能,也可以说是函数体
* return:一般用于语句结束函数,并返回给信息调用者。
* 表达式:即为要返回给函数的调用者的信息。
注意:最后的return表达式,可以不写,相当于返回一个None,return none
举例1:定义一个最简单的函数——无参数无返回值。
def myprint(): print("Thomas is a good man!") return # 或者不写,就是不需要返回值的话
2. 函数的调用:
格式1:
函数名(参数列表)
格式2:
变量名 = 函数名(函数列表)
解释:格式2就是拿一个变量名去接收这个函数
# 调用一个最简单的函数 # 格式1: myPrint() # 返回值:Thomas is a good man! # 格式2: ret = myPrint() print(ret) # 返回值:Thomas is a good man!
3. 函数的返回值:
举例2:定义一个带返回值的函数:
# 定义一个带返回值的函数 def mysum(num1,num2): # 将结构返回给函数的调用者 return num1 + num2 # 执行完return语句,该函数就结束了,return后面的代码不执行了 # 输入实参并获得返回值 result = mysum(1,2) print(result) # 返回值: # 3
4. 函数的格式的解释:
4.1 函数的参数
函数的参数分形参和实参
实参:调用函数时给函数传递的数据,本质就是值
形参:定义函数时小括号中的变量,本质是变量
参数必须按照顺序传递,个数目录要对应(不一定这个事儿)
举例2:定义一个带参数的函数并返回这个函数
# 定义一个带参数的函数 # 定义一个形参 def myprint2(str,age): print(str,age) # 输入一个实参: myprint2("Thomas is a good man",18)
4.2 函数参数的传递:
值传递:传递的不可变类型(string,tuple,number等不可变的类型)
引用传递:传递的可变类型(list,dict,set等可变的类型)
举例3:值传递,我们通过观察内存地址来证明这个现象
# 值传递: def func1(num): print(id(num)) num = 10 print(id(num)) temp = 20 print(id(temp)) func1(temp) print(temp) # 返回值: # 140734591231216 # 140734591231216 # 140734591230896
举例4:引用传递,我们通过输入一个列表,发现原先列表的第一个值已经发生变化了
# 引用传递 def func2(list): list[0] = 100 list1 = [1,2,3,4,5] func2(list1) print(list1) # 返回值: # [100, 2, 3, 4, 5]
4.3. 函数参数的其他形式:
关键字参数:运行函数调用时参数的顺序与定义不一致。(在C#当中叫做命名参数)
默认参数:调用函数时,如果没有传递参数,则默认参数。
不定长参数:
* :加星号的变量存放所有未命名的变量参数,如果在函数调用时没有指定参数,那么他就是一个空元祖。
*args:表示传入参数值(比较规范的写法)
*Kwargs:key-value形式,表示键值对的参数传入形式,这个键值对是字典形式(比较规范的写法)
备注:一般这两个位置全部写上形参可接受任意参数的形式。
偏函数的使用:所谓偏函数其实是起到默认参数的作用,通过模块导入functools工具包,在不改变原函数的前提下进行偏函数的锁定
举例5:我们使用关键字参数(其实这种形式 = *Kwargs,键值对的输入方式)
# 使用关键字参数 def func3(str, age): print(str,age) func3(age = 18,str = "Thomas")
举例6:使用默认参数。就是规定好了参数的值无需再输入了。
# 使用默认参数 # 要以默认参数,最好将默认参数放到最后 def func4(str, age = 18): print(str,age) func4("Thomas")
举例7:不定长参数——值参数
# 不定长参数 def func5(name,*args): print(name) for x in args: print(x) func5("Thomas","Jessie","China")
# *args表示值参数
def func6(*args):
sum = 0
for i in args:
sum += i
return sum
print(func6(1,2,3,4,5,6,7))
举例8:不定长参数——键值对参数
# **kwargs表示键值对的参数 def func7(**kwargs): print(kwargs) print(type(kwargs)) func7(x=1,y=2,z=3)
举例9:表示可以接受任意的参数
# 表示可以接收任意的参数 def func8(*args,**kwargs): print(args,kwargs) """
举例10:偏函数的使用
例子1:将字符串当做10进制来算
# 将字符串当作10进制来算 print(int("1010",base=10)) # 将字符串当作2进制来算 print(int("1010",base=2))
例子2:对函数返回值进行限制。
# 偏函数就是在返回值的位置进行添加其他的返回限制 def int2(str): return int(str,base = 2) print(int2("101010"))
例子3:偏函数functool工具去调用偏函数
# 偏函数理论上不需要自己去定义 import functools # 把一个参数固定住,形成一个新函数 int3 = functools.partial(int,base = 2) print(int3("111000"))
如下为函数的进阶内容:
5. 匿名函数____lambda表达式:
匿名函数:不使用def关键字这样的语句定义,类似于函数的一种简写形式。使用Lambda表达式进行创建匿名函数。在其他语言中还可以通过@函数句柄形式创建(matlab),还有其他语言有内联函数的创建,内联函数主要是为了加快函数的运算,类似于用fevel函数,还有inline的方式进行。Python没看到内联函数的这种形式
格式:
lambda 参数1,参数2,... ,参数n : 表达式(expression)
举例11:
sum = lambda num1,num2:num1 + num2 print(sum(1,2))
6. 递归
我们上面对函数有了基本的概念之后,另外思考一下:在一个函数代码当中,如果我们还想处理更多的内容,是不是可以在函数当中再写一个函数,还可不可以在函数当中去调用自身等方式进行函数和函数之间的一种组合呢?当然可以,实际过程当中也是这么用的。在Python当中常用的两种形式就是递归和装饰器,下面对递归进行讲解。
递归:一个函数调用自身成为了递归的调用。一会儿调用自身的函数成为递归函数。凡是循环能干的事儿,都能干。
递归常见于return返回值的后面和函数体内。
方式:
1. 写出临界条件。
2. 找这一次和上一次的关系。
3. 假设当前函数已经能用,调用自身计算上一次的记过,在去求本次的结果。
举例12:我们用一个最简单的循环写这么一个过程:
# 输入一个数,大于等于1,求1+2+3+---+n的和。 # 如果用循环写 # def sum(n): # sum = 0 # for x in range(1,n+1): # sum += x # return sum # result = sum(5) # print("result = ",result)
举例13:比如我们有一个1到5的累加
数学过程:1+2+3+4+5,假设这个函数叫sum2
我们有如下的表达式:
sum2(1) + 0 = sum2(1)
sum2(1) + 2 = sum2(2)
sum2(2) + 3 = sum2(3)
sum2(3) + 4 = sum2(4)
sum2(4) + 5 = sum2(5)
在这个过程中我们发现sum2( )这个函数被不断的调用,得到的结果带入到函数进行循环计算。
因此这个计算过程如下(反向)
5 + sum2(4)
5 + 4 + sum2(3)
5 + 4 + 3 + sum2(2)
5 + 4 + 3 + 2 + sum2(1)
5 + 4 + 3 + 2 + 1 # return 1
我们用递归来写这个函数
# def sum2(n): # if n == 1: # return 1 # else: # return n + sum2(n-1) # # result2 = sum2(5) # print("result2 = ", result2)
7. 装饰器:
7.1 装饰器的解释:
装饰器:是一个闭包,在Python当中叫做装饰器,把一个函数当做参数返回一个替代版的函数,本质上就是一个返回函数的函数,因此装饰器叫做函数的函数。
我们先来看一个最简单的装饰器
举例14:一个简单的装饰器
# 简单的装饰器 def func1(): print("Thomas is a good man!") # 制作一个装饰叫做outer def outer(func): def inner(): print("**********************") func() return inner # 通过装饰器outer把原函数func1带入到里面,形成一个新函数f f = outer(func1) f() # 运行结果: # **************** # Thomas is a good man
我们把这个而过程图示一下:
注意:我们这里发现这个装饰器的调用稍微复杂了一点儿,通过一个函数句柄返回一个新的函数。在Python2.4之前只能这样操作,在Python2.4之后的版本我们可以通过@这个符号来简化这个调用操作
格式:
@装饰器名
要装饰的函数代码
@装饰器名
要装饰的函数代码
在这里要有一个就近原则,比如说
@装饰器名
函数代码A
函数代码B
这总形式:@装饰器只装饰函数代码B
图示:
这样是不能装饰后面那个,如果要装饰后面那个要一个装饰器名对应一个要装饰的代码。
举例15:我们在写一个复杂一点儿的装饰器
# 复杂一点儿的装饰器 def outer(func): def inner(age): if age < 0: age = 0 func(age) return inner # 使用@符号将装饰器用用到函数 # @python2.4出现支持@符号 # 其实这里的@outer1 = say = outer(say) @outer # def say(age): print("Thomas is %d years old" %age) def say1(age): print("Thomas is %d years old" %age) say(-10) # Thomas is 0 years old # @ = 下面句柄形式的调用 # say1 = outer(say) #say1(-10)
7.2 换个角度再来理解装饰器:
1. 我们知道一个函数写完了,里面的东西可以通过修改函数的内容进行修改。还有一种方式不动原函数进行修改(前面说过偏函数也是一种不动原函数的方式进行内容修改),这种方式就是装饰器。
2. 装饰器我们可以抽象的理解把原函数的函数名和函数参数拆分成两个部分形成一个嵌套函数来组成这个装饰器。
根据这个理解我们再举几个例子:
举例16:
# 我们一个简单的装饰器 # 正如前面说的在不动原函数的基础上进行修改 # 写装饰 def outer(func): # 把函数名传进来 def inner(): # 让函数再键入什么内容 print("Hello World") return func() # 返回上一层 return inner # 返回最外层 def sayHello(): print("Say Hello") s = outer(sayHello) # 做好了装饰器,然后去装饰谁 s()
举例17:
# 根据类似于上面的句柄函数的概念,我们可以把装饰器的调用用@进行简写 def outer(func): # 把函数名传进来 def inner(): # 让函数再键入什么内容 print("Hello World") return func() # 返回上一层 return inner # 返回最外层 @outer def sayHello(): print("Say Hello") # 这里空缺@outer这个装饰器调用,因此不再调用 def sayHello2(): print("Say Thomas") sayHello() sayHello2() # 注意:装饰器只是对就近的函数进行装饰调用,再往下就不再调用,想要再调用再写 # 一个@调用。 # 输出: # Hello World 增加@outer,进行装饰 # Say Hello # Say Thomas 缺少@outer,不用装饰
举例18:
# 有参数的装饰器 # 装饰器还可以增加参数 def sayName(func): def inner(name): print("I am shi") func(name) return inner @sayName def sayHi(name): print("Hi " + name) sayHi("Thomas")
举例19:我们可以搞两个装饰器共同修饰一个函数
# 我们还可以搞两个装饰器 def sayName(func): print("name") def inner(*args,**kwargs): print("I am Thomas") func(*args,**kwargs) return inner def sayAge(func): print("age") def inner(*args,**kwargs): print("I am 18") func(*args,**kwargs) return inner @sayName @sayAge def sayHi(name,age): print("Output:" + name , age) sayHi("Thomas",18) # 输出内容: # age # name # I am Thomas # I am 18
7.3 通用装饰器:
我们前面学过*args和*Kwargs这种不定长的参数表示形式,因此我们可以制作一个通用装饰器。
举例20:
def outer(func): def inner(*args,**kwargs): # 添加修改的功能的位置 print("################") func(*args,**kwargs) return inner @outer def say(name,age): # 函数的参数理论上是无限的,但是实际上最好不要超过6-7个 print("My name is %s, I am %d year old" % (name,age)) say("Thomas",18) # 返回结果: # ################ # My name is Thomas, I am 18 year old # # 因此我们可以改写成一个通用装饰器 def sayName(func): def inner(*args,**kwargs): print("I am shi") func(*args,**kwargs) return inner @sayName def sayHi(name): print("Hi " + name) sayHi("Thomas") # 备注:一般来说,我们用一个通用装饰器就可以,这种方式用的最多