本文将按照如下结构介绍:
1.函数介绍
1.1 函数的定义
函数其实有5部分组成,分别是定义函数的关键字,函数的名字,圆括号(存放函数的参数),函数功能的实现,函数的返回语句。
- 函数的关键字,定义函数要使用关键字def开头,后面跟着函数的名字 。两者中间相隔一个空格。
- 函数的名字,每个函数都要有一个名字即函数标识符,函数名字是由开发人员起的,起的名字最好有一定业务语义,比如此功能是一个加法的操作,那么就可以起add.
- 圆括号,圆括号内可以定义参数,参数可以是一个,也可以是多个,也可以没有参数 。圆括号后必须要有个冒号并且加缩进,
- 函数功能实现代码,冒号+缩进的后面就可以编写函数的功能代码块了,但这部分一般都是放在函数的下一行 。
- 函数的返回语句,函数使用return代表函数已经结束,不带表达式return代表返回None 。
函数的语法格式:
def 函数名(参数列表):
函数体
return
示例:
#不带参数的函数
def hello_world():
print("hello world")
#带一个参数
def show(msg)
print(msg)
#带多个参数
def add(x,y):
z = x + y
return z
1.2函数的调用
调用函数,其实就是使用函数。调用函数时直接使用函数名即可。
调用语法:
函数名(参数列表)
调用以上定义的函数
#1.调用hello_world函数
hello_world()
#2.调用show函数
show("我是一个函数") #输出:我是一个函数
#3.调用add函数
print(add(3,4)) #输出:7
1.3函数的返回
函数的返回用于退出该函数,函数返回使用return进行表示,return后面可以根具体值,也可以不跟。如果不跟任何值代表返回None。
一般return后的值有以下几种情况:
- return None,如果是None,可以忽略不写 。
- return value,value可以是代码块中计算出来的值,也可以是一个表达式 。
- return val1,...,valn。return也可以返回多个值,注意:返回多个值时接收需用对应的多个变量。
示例
def show(msg):
print(msg)
return None #这样代码可以忽略。
def add(x,y):
z = x +y
return z #返回某个值
def add(x,y):
return x + y #直接返回参与运算后的结果
def calc(x,y):
add = x+y
sub = x-y
return add,sub #返回了两个值
1.4 小结
最后,我们将上面讲的内容通过一张思维导图来总结,具体如下
2.函数的参数
在调用函数时,经常会用到形式参数(简称“形参”)和实际参数(简称“实参”),二者的主要区别是:
- 形式参数,是在括号内的参数就叫形式参数,例如上面add函数:
def add(x,y): #这里的x,y就是形式参数
z = x + y
return z
- 实际参数,调用函数时,实际传入到函数内的值 。
print(add(3,4)) #调用add函数时,实际传入的3和4就是实际参数 。
需要注意的是,一个函数实际上可以接受任何类型的值,比如字符串,整数,浮点数,列表,元组,字典等。
当函数的形式参数中要传递实际的值时,也会分两种情况,不可变对象值(值传递)传递和可变对象值传递(引用传递)。
函数中的不可变对象值传递,示例:
x = 10
def sum(a):
a += 2
return a
print("调用函数后返回的值:",sum(x))
print("x的值:",x)
运行后可以看出,无论函数中如何作用x的值,实际的x值是不会发生变化的 ,传入sum函数里的值其实就是x=10的一份副本 。
函数中的可变对象传递,示例
my_lst = ['java', 'c++', 'python','go']
def update_lst(lst):
lst.insert(1,'ruby')
return lst
print("调用函数后返回的值:",update_lst(my_lst))
print("my_lst的值:",my_lst)
运行结果:
以上结果可以看出,通过函数作用于my_list列表后,列表内的值也发生改变了,他们两同时指向了一个列表 。
最后,用个形象的例子总结一下,不可变对象就像是一个pdf文件,文件内容本身不能改变,但是可以拷贝,所以在函数中使用不可变对象都是使用的是拷贝后的副本。而可变对象就像是execl。execl中的内容时可变化的 。但是操作的都是源文件,函数中操作的可变对象也是对源文件里的内容进行操作 。
从参数的类型划分,可以将参数划分为:位置参数,默认参数,关键字参数,可变参数。
2.1 位置参数:
所谓的位置参数,主要是指输入函数内的参数数量和位置要和定义的形参中的一致。传多或传少都会报错。
#实例:
def add(x,y): #定义了两个形参,调用该方法时必须传入两个参数值,传多或传少都会报错
z = x + y
return z
#输出
print(1,2) #位置参数传入正确
print('hello','world') #位置参数传入正确
print(3) #不正确,报TypeError异常
print(1,2,3) #不正确,报TypeError异常
2.2 默认参数
如果函数的某个参数经常会使用到一个值,那我们就可以给这个参数设置一个默认值 ,当调用该函数时,该参数若不传入值,就会使用到默认值。设置的默认参数需放在关键字参数后 。
示例:
#默认参数实例
def show_lgg(msg,lgg='python'):
info = ("{}{}".format(msg,lgg))
return info
print(show_lgg("使用默认的开发语言:"))
print(show_lgg("使用默认的开发语言:",'java'))
#输出
使用默认的开发语言:python
使用默认的开发语言:java
从以上结果可以看出,函数show_lgg中的lgg设置了默认值python,当该参数没有传值时,使用的是默认的值。若该参数传入值后,使用的就是传入的值 。
那这个默认参数的用处就是,当函数的参数值频繁的使用某一个值或者优先被使用该值时,都可以将其设置为默认值 。比如我们经常使用的浏览器,那么多的浏览器中,我们最常用的Chrome,那么就可以将Chrome设置为默认值。不传入该参数时默认就会使用Chrome 。
使用默认参数时要注意,位置参数需放在默认参数前面,否则python会报错。使用它可以简化我们的调用,增加灵活度。
2.3 关键字参数
关键字参数是指传递给参数要以key=value形式,key代表函数中的形参名称,value就是传递给函数的实际的值。
使用关键字参数后,参数的位置就可以进行调换了 。
#关键字参数实例
def student_lesson(grade,subject):
z = "{}年级上{}课".format(grade,subject)
return z
print(student_lesson(3,'英语')) #位置参数
print(student_lesson(grade=4,subject='数学')) #关键字参数
print(student_lesson(subject='语文',grade=5)) #关键字参数可以调换位置
print(student_lesson(6,subject='体育')) #关键字参数和位置参数混合使用。
#输出
3年级上英语课
4年级上数学课
5年级上语文课
6年级上体育课
以上方式都可以正常正常调用,但是需要注意的是,当关键字参数和位置参数混合使用时,位置参数必须在前,关键字参数在后,否则会报错。
2.4 可变参数
这里的可变化参数指的参数的数量可以发生变化,比如我可以传递3个参数,也可以传递6个,甚至更多 。可变化参数有两种类型,分别是用来接收元组或列表形式的可变参数;另外一种是用来接收字典形式的可变参数 。
#列表形式的语法格式:
def fun_name(a,b,*args):
pass
#代码实例
def show_info(*args):
print(args)
show_info() #可变参数也可以不传入任何参数
show_info("python") #传入一个参数
show_info("python",'java') #传入多个参数
show_info(*['python','java']) #传入列表,注意,列表前需要加*
show_info(*('python','java')) #传入元组,元组前也需要加*
#输出:
()
('python',)
('python', 'java')
('python', 'java')
('python', 'java')
在可变参数的参数名前需加*代,这样就代表该参数为可变参数了。同时也传入的时候可以是一个值,多个值,列表或元组 。
还有一种可变参数就是接收字典中的元素 。在函数中是以双星**表示。此函数同样能接收多个位置参数,但是传入的形式需要以关键字形式传递 。
#字典形式的可变参数语法格式
def fun_name(a,b,**kw):
pass
#代码实例
def show_info(**kwargs):
print(kwargs)
show_info() #可变参数也可以不传入任何参数
show_info(key1="python") #传入一个参数
show_info(key1="python",key2='java') #传入多个参数
show_info(**{'key1':'python','key2':'java'}) #传入列表,注意,列表前需要加*
#输出:
{}
{'key1': 'python'}
{'key1': 'python', 'key2': 'java'}
{'key1': 'python', 'key2': 'java'}
也可以将可变参数列表和字典形式结合起来使用 。
def fun_name(*args,**kwargs): #此函数可以接收任何长度,任何位置,任何关键字的参数 。
pass
针对以上四种参数,进行总体说明:
- 位置参数,参数的顺序和个数固定,调用函数时也必须按照该顺序和个数进行传递 。但如果参数个数固定了,就不容易扩展了 。所以,可以使用可变参数传入不同数量的参数 。
- 关键字参数,使用形参名称=输入的值来进行表示,如果使用关键字参数,就可以调换参数的位置 ,可变参数的字典形式其实就是关键字参数的扩展,可以支持不同长度的关键字参数。
- 默认参数,是针对位置参数的,相当于给某一个或几个位置参数设置默认值。
- 可变化参数,可以扩展位置参数的数量,支持列表形式和字典形式的位置参数 。
2.5 小结
最后,我们将上面讲的内容通过一张思维导图来总结,具体如下
3.高级函数
3.1 匿名函数
使用关键字lambda的表达式,又称为匿名函数。若函数可以用一行进行实现的话,那么该函数就可以使用匿名函数来代替。
#格式:
lambda x,y,...,n:表达式
#说明:x,y,...,n.相当于普通函数的位置函数,可以是一个,也可以是多个 。
#实例:求两个数的和
#实现1:普通实现
def add(a,b):
return a + b
z1 = add(3,4)
print(z1) #输出:7
#实现2:通过匿名函数
z2 = lambda x,y:x+y
print(z2) #<function <lambda> at 0x0326C618>
print(z2(4,5)) #输出:9
匿名函数除了可以赋值给变量外,还可以通过函数的参数来传递参数。
#实现:将函数的参数a,b传递给匿名函数
def add(a,b):
z = lambda :a+b
return z
3.2 高阶函数-map,reduce,filter,sorted
map函数
map()是python的内置函数。map()函数接收两个或多个参数,第一个参数接收的是函数,后面的参数是序列。
#语法:
map(函数,序列)
#实例:将列表中的每一对元素相加
lst = [(1,2),(3,4),(4,5)]
total = map(sum,lst)
print(list(total))
#输出:
[3, 7, 9]
#说明:
以上代码相当于把(1,2),(3,4),(4,5)这三组中的每两个元素传递给sum,然后由sum来进行运算 。
map后面的序列也可以是多个
#语法:
map(函数,序列1,序列2,...)
#实例
num = map(lambda x,y:x+y,[1,2,3,4,5],[6,7,8,9,10])
print(list(num))
#输出:
[7, 9, 11, 13, 15]
#说明:
map的第二个,第三个参数都是列表,结果就是将这两个列表每一个位置的数进行相加
reduce函数
接收两个参数,第一个是函数,第二个是序列 。reduce()函数会把序列中的每一个元素累积进行计算,如何计算由前面的函数来定。
#语法:
reduce(函数,序列)
#实例:把列表中的数进行相加
from functools import reduce
def add(x,y):
return x + y
sum = reduce(add,[1,2,3,4,5])
print(sum)
#输出:15
#说明:相当于是把整个列表的数进行了一次循环的相加操作
filter函数
也是接收两个参数,第一个是函数,第二个是序列,filter()函数会把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
#语法:
filter(函数,序列)
#实例:查询字典中成绩分大于350分的元素
dt = {'grade_one': 237, 'grade_two': 532, 'grade_three': 356}
dt1 = dict(filter(lambda x:x[1] > 350,dt.items()))
print("查询年级分数大于350分的班级:{}".format(dt1))
#输出:
查询年级分数大于350分的班级:{'grade_two': 532, 'grade_three': 356}
sorted函数
sorted函数对所有可迭代的对象进行排序
#语法
sorted(iterable, cmp=None, key=None, reverse=False) ==等价于下面的
sorted(可迭代对象,比较函数,用来比较的元素,排序规则)
#实例:
study = [('ruby', '3本', 150), ('python', '21本', 122), ('java', '18本', 210)]
print("按第三个元素(价格)排序:{}".format(sorted(study, key=lambda s: s[2])))
print("按第二个元素(书籍)降序排序:{}".format(sorted(study, key=lambda s:s[2],reverse=True)))
#输出:
按第三个元素(价格)排序:[('python', '21本', 122), ('ruby', '3本', 150), ('java', '18本', 210)]
按第二个元素(书籍)降序排序:[('java', '18本', 210), ('ruby', '3本', 150), ('python', '21本', 122)]
3.3 偏函数
偏函数其实就是functools
包中的partial函数。
在前面我们讲到函数的默认值可以减少传递参数的数量,但是一旦定义了函数的默认值,如果修改的话,就得修改这个函数,很多情况我们底层的一些函数都是不建议修改的,这个时候可以使用偏函数来设置默认值,而且不同的类型可以设置不同的默认值 。
#简化版计算器
def calc(x,y,op='+'):
if op == '+':
return x + y
if op == '-':
return x - y
if op == '*':
return x * y
if op == "/":
return x / y
return x + y
#需求:这时我们又需要进行大量的乘法操作,怎么办呢? 如下面的操作
calc(2,3,"*")
calc(2,4,"*")
calc(2,5,"*")
...
calc(2,n,"*")
#注意:这个时候不建议修改原函数,因为原函数也是综合考虑使用+最多的。不能这时用*就修改原函数为* 。
#问题:每次传入的第三个值都是一个值,比较麻烦。能否把它作为默认参数传进来,
#解决方案:使用偏函数
from functools import partial
mult=partial(calc,op='*') #把乘法固定
print(mult(2,4)) #输出:8
print(mult(6,4)) #输出:24
#说明,使用偏函数后,参数op的默认值已经给定,所以后面不需要再传递该参数 。
3.4 递归
递归就是函数内部又调用了自己的函数就被称为递归 。
#语法格式
def funA(
...
funA()
...
)
#实例1:求:1!+2!+3!+4!+5!+...+n!
def factorial(n):
''' n表示阶乘数的最大值 '''
if n==1:
return n # 阶乘为1的时候,结果为1,返回结果并退出
n = n*factorial(n-1) # n! = n*(n-1)!
return n
res = factorial(5)
print(res)
#输出:120
#===============================
#实例2: 1,1,2,3,5,8,13,21,34,55,试判断数列第23个数是哪个?
def fab(n):
''' n为斐波那契数列 '''
if n <= 2:
''' 数列前两个数都是1 '''
val = 1
return val
val = fab(n-1)+fab(n-2) # 由数据的规律可知,第三个数的结果都是前两个数之和,所以进行递归叠加
return val
print(fab(23))
#输出:28657
3.5 闭包和装饰器
闭包
闭包又称为闭合函数。简单来说,闭包的概念就是在函数内再定义一个函数,这个内部函数使用了外部函数的临时变量,且外部函数的返回值是内部函数的引用时,我们称之为闭包。
语法:
def outer_func(): #外部函数
temp = null #外部函数的临时变量
def inner_func():
函数体
return inner_func #将内部函数作为返回值返回
#实例
import operator
def operating(op): #外部函数定义具体的操作符
def calc(a,b): #内部函数定义计算的两个变量
oprt = {
'+': operator.add(a,b),
'-': operator.sub(a,b),
'*': operator.mul(a,b),
'/': operator.truediv(a,b)
}
return oprt.get(op)
return calc #把内函数的引用当做外函数的变量值返回
add = operating('+')
print(add(3,4)) #输出:7
那么,闭包最主要的用途就是装饰器 。
装饰器
装饰器的本质其实就是一个闭包函数,以下就是一个最为普通的装饰器,
def info(func):
def wrapper():
print("[INFO]: 调用 {}()".format(func.__name__))
return func()
return wrapper
def say():
print("hello!")
say_hello = info(say) # 添加功能并保持原函数名不变
通过和闭包进行相比较,两者的写法都是一样的,其实装饰器就是一个闭包函数 。那么除了以上调用方法外,python在后续的版本又引入了@语法糖,同样是上面的代码,可以使用如下方式调用 。
def info(func):
def wrapper():
print("[INFO]: 调用 {}()".format(func.__name__))
return func()
return wrapper
@info
def say():
print("hello!")
say()
#输出:
[INFO]: 调用 say()
hello!
这样的话,就通过@ 标识符将装饰器应用到函数上了 。其实通过以上的调用,实际上是执行了如下2步操作:
- 将say传给info()函数 。
- 将info函数执行完成的返回值反馈say
以上是一个最简单的装饰器,但有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就不能用了 ,因为返回的函数并不能接受参数 。
带参数的函数装饰器
通过制定装饰器函数wrapper接受和原函数一样的参数,就可以参数了 。
def info(func):
def wrapper(arc): #
print("[INFO]: 调用 {}()".format(func.__name__))
return func(arc)
return wrapper # 返回包装过函数
@info
def say(arc):
print("hello {}.".format(arc))
say("装饰器函数")
#输出
[INFO]: 调用 say()
hello 装饰器函数.
以上还是只能接受一个参数,但实际情况下,同一个装饰器可用于多个不同的函数,每个函数所带的参数都各不相同,怎么解决呢 ?
这时我们就可以用到可变参数*args和**kwargs了。通过这两个参数,装饰器就可以用于任意函数了 。
def info(func):
def wrapper(*args, **kwargs): # 指定*args和**kwargs,接收任意参数
print("[INFO]: 调用 {}()".format(func.__name__))
return func(*args, **kwargs)
return wrapper # 返回
@info
def say(arc1):
print("hello {}!".format(arc1))
@info
def todo(x,y):
print(x * y)
say("作用于多个函数的装饰器")
todo(3,5)
#输出:
[INFO]: 调用 say()
hello 作用于多个函数的装饰器!
[INFO]: 调用 todo()
15
3.6 回调函数
简单的来说,把一个函数作为参数传给另一个函数,那么这个函数就是回调函数。这个被传入的参数其实是函数指针,即指向一个函数的指针(地址)。
def calc(func,a,b): #第一个参数接收一个函数
return func(a,b)
def add(x,y):
return x + y
def mul(x,y):
return x * y
print(calc(add,3,5)) #第一个参数为函数,后面的两个参数是函数的参数
print(calc(mul,3,5))
#输出
8
15
回调函数还可以进行异步调用,即非阻塞调用,通常用在多线程或者多进程中。
3.7 生成器函数
生成器函数,就是说定义函数时,内部带yield就算生成器函数。在调用生成器运行的过程中,每次遇到 yield 就会返回 其后的值, 并在下一次执行 next() 方法时从当前位置继续运行。
def scq_func():
for x in range(1,10):
yield x * 2
s = scq_func()
print(s) #输出:<generator object scq_func at 0x0161B510>
print(next(s))
print(next(s))
print(next(s))
print(next(s))
#输出:
<generator object scq_func at 0x0161B510>
2
4
6
8
以上函数在执行过程中,遇到yield就中断下次又继续执行,直到没有yield可执行了,在调用next就会报错 。
那该函数主要的作用是什么呢 ? 在数学中有很多的计算都是无限穷尽的(比如自然数) 。我们不可能通过序列去存放,这时我们就可以使用生成器按照规则计算下出一个数据,可以计算出很大的数 。
import sys
def fibonacci(n): # 生成器函数 - 斐波那契
a, b, counter = 0, 1, 0
while True:
if (counter > n):
return
yield a
a, b = b, a + b
counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
while True:
try:
print (next(f), end=" ")
except StopIteration:
sys.exit()
#输出:
0 1 1 2 3 5 8 13 21 34 55
3.8 小结
最后,我们将上面讲的内容通过一张思维导图来总结,具体如下