Python函数与函数式编程
前言
从寒假才开始规范化系统地学习python。这些是学习的时候记的的一些笔记。
注:根据代码规范,函数名和方法名应该是由全小写英文字母组成。函数必须先定义后使用。
1.1 定义函数
自定义函数的语法如下:
def 函数名(形参1,形参2,形参3...):
函数体
return 返回值
可以不传入参数,括号内容为空,但是小括号不能省略。也可以没有返回值,使用return None或者直接省略return语句。
下面将做一些示范:
有参数且有返回值的函数:
def make_coffee(name):
return "制作一杯{0}咖啡。".format(name)
有参数无返回值的函数:
def make_coffee(name):
print("制作一杯{0}咖啡。".format(name))
无参数也无返回值的函数:
def say_hello():
print("hello world")
1.2函数的参数
1.2.1 位置参数
顾名思义就是按照位置顺序传入参数。举个简单的例子:
def abc(a,b,c)
print(a,b,c)
调用函数abc
abc(1,2,3)
结果为
1 2 3
调用时参数位置从左往右与形式参数一一对应,a就被赋值为1,b为2,c为3。
1.2.2 使用关键字参数调用函数
就是指定哪个实参对应哪个形参(相当于绑定了)。还是上面那个abc函数。
def abc(a,b,c)
print(a,b,c)
使用关键字参数调用函数abc
abc(c=3,a=1,b=2)
执行结果仍为
1 2 3
1.2.3 参数默认值
在定义函数的时候可以为参数设置一个默认值,如果调用函数并传入了值,默认值则被忽略,如果没有传入值,则会使用默认值。
def make_coffee(name='卡布奇诺'):
return "制作一杯{0}咖啡。'.format(name)
默认值为‘卡布奇诺’
调用函数时传入了参数与不传入参数对比:
coffee1 = make_coffee('拿铁')
coffee2 = make_coffee()
print(coffee1)
print(coffee2)
制作一杯拿铁咖啡。
制作一杯卡布奇诺咖啡。
1.2.4 单星号(*)可变参数
Python中函数的参数个数可以变化,它可以接受不确定数量的参数,这种参数成为可变参数。
单星号(*)可变参数在函数中被组装成一个元组。例如:
def sum(*numbers,multiple = 1):
total = 0.0
for number in numbers:
total += number
return total * multiple
print(sum(100.0,20.0,30.0))
结果为
150.0
上述代码定义了一个sum()函数,用来计算传递给它的所有参数之和。number时可变参数,在函数体中参数numbers被封装成一个元组,可以使用for循环遍历numbers元组,计算他们的总和然后返回。
注意:这里用了(*)变量可变参数,想要传入参数multiple必须用关键字参数才能实现(单星号可变量参数不是最后一个参数时,后面的参数需要采用关键字参数形式传入)
print(sum(30.0,80.0,multiple = 2))
结果为
220.0
那么元组变量能否传递给可变参数呢?这需要对元组经行拆包(在元组变量名前面加上一个 ‘ * ’)
例如:
double_tuple = (50.0,60.0,0.0)
print(sun(30.0,80.0,*double_tuple)) #输出220.0
单星号这里将double_tuple拆包成50.0,60.0,0.0形式。
1.2.5 双星号(**)可变参数
双星号(**)可变参数在函数中被组装成为一个字典。
举个例子
def show_info(sep=':',**info):
for key,value in info.items():
print('{0} {2} {1}'.format(key,value,sep))
其中参数spe为信息分隔符,默认为冒号 “:”
注意:双星号可变参数必须在正规参数之后,如果函数定义改为show_info(**info,sep=’:’)形式,会发生错误
show_info('->',name='Tony',age=18,sex=True)
show_info(name='Tony',age=18,sex=True,sep='->')
第二行语句调用show_info(),sep也采用关键字参数传递,这种方式下sep参数可以放置在参数列表的任何位置,其中的关键字参数会被收集到info字典中。
二者执行的结果相同,如下所示
name -> Tony
age -> 18
sex -> True
同单星号可变参数,双星号也可在字典参数前加上 “ ** ” 将字典拆包进行传输。
stu_dict = {'name':'Tony','age':18}
show_info(**stu_dict,sex = True,sep='=')
调用show_info()函数,其中字典对象为stu_dict,传递时stu_dict前面加上双星号 “ ** ” 表示将stu_dict拆包为key = value对的形式。
1.3 函数返回值
1.3.1 无返回值
在1.1中已有介绍,如果没有返回值则使用 return 或 return None 或者省略return 语句。
1.3.2 多返回值
如果需要函数返回多个值有很多种实现方式,简单的方式是使用元组返回多个值。(元组有不能被更改的特性,所以使用起来比较安全)
def position(dt,speed):
posx = speed[0]*dt
posy = speed[1]*dt
return (posx,posy)
move = position(60.0,(10,-5))
print("物体位移:({0},{1})".format(move[0],move[1]))
物体位移:(600.0,-300.0)
可以看到返回的数据 (posx,posy)是元组变量的实例。
1.4 函数变量作用域
变量可以在模块中创建。其作用域是整个模块,称为全局变量。变量也可以在函数中创建,默认情况下其作用域是整个函数,称为局部变量。
x = 20 #创建全局变量
def print_value():
print('在函数中x = {0}'.format(x))
print_value()
print('全局变量x = {0}'.format(x))
输出结果为
函数中x = 20
全局变量x = 20
全局变量x的作用域是整个模块,所以在print_value()函数中也可以访问变量x。
若函数内部有与全局变量名字相同的局部变量会怎样呢
x = 20 #创建全局变量
def print_value():
x = 30
print('在函数中x = {0}'.format(x))
print_value()
print('全局变量x = {0}'.format(x))
输出结果:
函数中x = 30
全局变量x = 20
会先在模块中寻找变量,若找不到才会访问全局变量。并且可以看到,对局部变量的修改是不会影响到全局变量的。
如果我们想通过局部变量修改全局变量,可以使用关键字global。这样就可以将局部变量的作用域变成全局变量。
x = 20 #创建全局变量
def print_value():
global x
x = 30
print('在函数中x = {0}'.format(x))
print_value()
print('全局变量x = {0}'.format(x))
输出结果:
函数中x = 30
全局变量x = 30
注意声明局部变量全局化与对局部变量赋值要分开写,不然会报错
不推荐这种做法,这样很容易导致程序出现难以检查出来的错误
1.5 生成器
在一个函数中经常使用return关键字返回数据,但是有时也会使用yield关键字返回数据。使用yield关键字函数返回的是一个生成器对象,生成器对象是一种可迭代对象。
例如计算平方数列:
如果用return关键字返回的话就需要将每一次迭代的结果存入列表中,最后以列表的形式返回
def square(num):
n_list = []
for i in range(1,num+1):
n_list.append(i*i)
return n_list
for i in square(5):
print(i,end = ' ')
结果如下
1 4 9 16 25
用yield则能使这个过程简化:
def square(num):
n_list = []
for i in range(1,num+1):
yield i * i #
for i in square(5):
print(i,end = ' ')
返回的结果同上
深度解析:yield关键字返回平方数,不再需要return关键字。调用square()函数时返回的是生成器对象。生成器是一种可迭代对象,可迭代对象通过__next__()方法获得元素,使用for循环能够遍历迭代对象就是隐式地调用了生成器地__next__()方法获得元素的。
1.6 嵌套函数
也就是在函数里面再定义函数。
例如:
def calculate(n1,n2,opr):
multiple = 2
def add(a,b):
return (a + b) * multiple
def sub(a,b):
return (a - b) * multiple
if opr == '+':
return add(n1,n2)
else:
return sub(n1,n2)
上述代码中定义了两个嵌套函数add()和sub()。嵌套函数可以访问所在外部函数calculate()中的变量multiple,而外部函数不能访问嵌套函数局部变量。另外,嵌套函数的作用域在外部函数体内,因此在外部函数之外直接访问嵌套函数会发生错误。
1.7函数式编程基础
1.7.1 高阶函数与函数类型
一个函数可以作为其他函数的参数,或者其他函数的返回值,那么这个函数就是“高阶函数”。为了支持高阶函数Python提供了一种函数类型function。任何一种函数的数据类型都是function类型,即“函数类型”。
举个例子:
def calculate():
#定义相加函数
def add(a,b):
return a + b
return add
f = calculate()
print(type(f))
print('10 + 5 = {0}'.format(f(10,5)))
结果如下:
<class 'function'>
10 + 5 = 15
函数calculate()返回值是嵌套函数add(),也可以说calculate()函数返回值数据类型是“函数类型”。
变量 f 指向add 函数,变量f与函数一样可以被调用, f(10,5)表达式就是调用函数,也就是调用add(10,5)函数。
1.7.2 函数作为其他函数返回值使用
例如:
def calculate(opr):
def add(a,b):
return a + b
def sub(a,b):
return a - b
if opr == '+':
return add
else:
return sub
f1 = calculate('+')
f2 = calculate('-')
print("10 + 5 = {0}".format(f1(10,5)))
print("10 - 5 = {0}".format(f2(10,5)))
输出的结果如下:
10 + 5 = 15
10 - 5 = 5
个人将其理解为套娃传参。调用calculate()函数并传入第一层参数以确定calculate()函数返回哪一个函数,将其返回的函数赋值给变量 f (这里的变量 f 可以等同于一个函数)。然后再调用 f 并传入参数,这时候相当于直接调用嵌套函数 add()或 sub()。
1.7.3 函数作为其他函数参数使用
例如:
def calc(value,op):
return op(value)
def square(n):
return n * n
def abs(n):
return n if n > 0 else -n
print('3的平方 = {}'.format(calc(3,square()))
print('-20的绝对值 = {}'.format(calc(-20,abs()))
结果为
3的平方 = 9
-20的绝对值 = 20
op参数是一个函数。上述即为函数作为其他函数的参数使用的方法。
1.7.4 匿名函数与lambda表达式
有时在使用函数的时候不需要给函数分配一个名字,这就是“匿名函数”。
在Python中使用lambda表达式表示匿名函数,声明lambda函数语法如下:
lambda 参数列表 : lambda体
注意:lambda体部分不能是一个代码块,不能包含多条语句,只能有一条语句,语句会计算一个结果返回给lambda表达式
举个例子:
def calculate(opr):
if opr == '+':
return lambda a,b: (a + b)
if opr == '-':
return lambda a,b: (a - b)
f1 = calculate('+')
f2 = calculate('-')
print('10 + 5 = {0}'.format(f1(10,5)))
print('10 - 5 = {0}'.format(f1(10,5)))
结果同1.2.7
1.8 函数式编程常用的基础函数
1.8.1 过滤函数 filter()
其语法为
filter(function,iterable)
其中参数function是一个函数,参数iterable是可迭代对象。filter()函数调用时 iterable会被遍历,它的元素会被逐一传入function函数,function函数返回布尔值。在function函数中编写过滤条件,如果为True的元素被保留,否则被过滤。
例如:
number_list = range(1,11)
number_filter = filter(lambda it : it % 2 == 0,number_list)
print(list(number_filter)
结果为
[2,4,6,8,10]
注意:filter()函数还不是一个列表,需要使用list()函数转换过滤之后的数据为列表
1.8.1 映射函数map()
语法为:
map(function,iterable)
与 filter用法一致,只是将过滤操作换成了执行函数。
举个例子:
users = ['Tony','Tom','Ben']
users_map = map(lambda u: u.lower(),users)
print(list(users_map())
结果如下:
['tony','tom','ben']
1.9 装饰器
简单来说就是在执行一个函数时先把这个函数丢到另一个函数一波操作,也可以理解为两个函数的糅合。
1.9.1 装饰器的使用
上栗子
import time
def time_master(func):
def call_func():
print('开始运行程序...')
start = time.time()
func()
stop = time.time()
print('结束程序运行...')
print(f'一共耗费了{(stop - start):.2f}秒。')
return call_func
@time_master
def myfunc():
time.sleep(2)
print('I love Python.')
myfunc()
运行结果如下
开始运行程序...
我爱python
技术程序运行...
一共耗费了2.05秒。
可以看到,我们调用函数 myfunc是先执行装饰器time_master,time_master()函数的参数就是我们传进来的函数myfunc,然后在time_master函数中被调用。
1.9.2 同时使用多个装饰器
多戴几顶帽子(生动形象)
上栗子:
def add(func):
def inner():
x = func()
return x + 1
return inner
def cube(func):
def inner():
x = func()
return x * x * x
return inner
def square(func):
def inner():
x = func()
return x * x
return inner
@add
@cube
@square
def test():
return 2
print(test())
其结果为
65
可见,装饰器的顺序是从下至上且每一个装饰器的返回值会传给下一个装饰器。
1.9.3 给装饰器传递参数
多加一层嵌套
import time
def logger(msg):
def time_master(func):
def call_func():
start = time.time()
func()
stop = time.time()
print(f"[{msg}]一共耗费了{(stop-start):.2f}")
return call_func
return time_master
@logger(msg = 'A')
def funA():
time.sleep(2)
print("正在调用funA...")
@logger(msg = 'B')
def funB():
time.sleep(2)
print("正在调用funB...")
funA()
funB()
结果如下:
正在调用funA...
[A]一共耗费了2.02
正在调用funB...
[B]一共耗费了2.03
完结撒花