第5章:函数式编程
Posted 墨迹测试开发
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第5章:函数式编程相关的知识,希望对你有一定的参考价值。
[TOC]
函数
简介
函数是应用程序最重要的方面。函数可以定义为可重用代码的有组织块,可以在需要时调用它。函数是由{}括起来的一组编程语句,函数体现了python程序的可重用性和模块性。
函数是应用程序最重要的方面。函数可以定义为可重用代码的有组织块,可以在需要时调用它。
Python允许我们将大型程序划分为称为函数的基本构建块。该函数包含由{}括起来的一组编程语句。可以多次调用函数以为python程序提供可重用性和模块性。
换句话说,我们可以说函数集合创建了一个程序。该函数也称为其他编程语言中的过程或子例程。
Python为我们提供了各种内置函数,如range()或print()。虽然,用户可以创建其功能,可以称为用户定义的功能。
python中函数的优点
函数有以下优点。
通过使用函数,我们可以避免在程序中一次又一次地重写相同的逻辑/代码。
我们可以在程序中和程序中的任何地方多次调用python函数。
当它被分成多个函数时,我们可以轻松地跟踪大型python程序。
可重用性是python函数的主要成就。
但是,函数调用总是在python程序中开销。
创建一个函数
在python中,我们可以使用def关键字来定义函数。下面给出了在python中定义函数的语法。
def my_function():
function-suite
return <expression>
函数块以冒号(:
)开头,所有相同级别的块语句保持相同的缩进。
函数可以接受定义和函数调用中必须相同的任意数量的参数。
功能调用
在python中,必须在函数调用之前定义函数,否则python解释器会给出错误。一旦定义了函数,我们就可以从另一个函数或python提示符调用它。要调用该函数,请使用函数名称,后跟括号。
下面给出了一个打印消息“Hello Word”的简单函数。
def hello_world():
print("hello world")
hello_world()
输出:
hello world
功能中的参数
函数中的信息可以作为参数传递。参数在括号中指定。我们可以提供任意数量的参数,但我们必须用逗号分隔它们。
请考虑以下示例,其中包含一个接受字符串作为参数并打印它的函数。
例1
#defining the function
def func (name):
print("Hi ",name);
#calling the function
func("Ayush")
例2
#python function to calculate the sum of two variables
#defining the function
def sum (a,b):
return a+b;
#taking values from the user
a = int(input("Enter a: "))
b = int(input("Enter b: "))
#printing the sum of a and b
print("Sum = ",sum(a,b))
输出:
Enter a: 10
Enter b: 20
Sum = 30
在Python中通过引用调用
在python中,所有函数都通过引用调用,即,对函数内部引用所做的所有更改都将恢复为引用引用的原始值。
但是,在可变对象的情况下有一个例外,因为对像string这样的可变对象所做的更改不会恢复为原始字符串,而是创建一个新的字符串对象,因此会打印两个不同的对象。
示例1传递不可变对象(列表)
#defining the function
def change_list(list1):
list1.append(20);
list1.append(30);
print("list inside function = ",list1)
#defining the list
list1 = [10,30,40,50]
#calling the function
change_list(list1);
print("list outside function = ",list1);
输出:
list inside function = [10, 30, 40, 50, 20, 30]
list outside function = [10, 30, 40, 50, 20, 30]
示例2传递可变对象(字符串)
#defining the function
def change_string (str):
str = str + " Hows you";
print("printing the string inside function :",str);
string1 = "Hi I am there"
#calling the function
change_string(string1)
print("printing the string outside function :",string1)
输出:
printing the string inside function : Hi I am there Hows you
printing the string outside function : Hi I am there
参数的类型
可能有几种类型的参数可以在函数调用时传递。
必需的参数
关键字参数
默认参数
可变长度参数
必需的参数
直到现在,我们已经了解了python中的函数调用。但是,我们可以在函数调用时提供参数。就所需的参数而言,这些是在函数调用时需要传递的参数,它们在函数调用和函数定义中的位置完全匹配。如果函数调用中未提供任何参数,或者参数的位置发生更改,则python解释器将显示错误。
请考虑以下示例。
例1
#the argument name is the required argument to the function func
def func(name):
message = "Hi "+name;
return message;
name = input("Enter the name?")
print(func(name))
输出:
Enter the name?John
Hi John
例2
#the function simple_interest accepts three arguments and returns the simple interest accordingly
def simple_interest(p,t,r):
return (p*t*r)/100
p = float(input("Enter the principle amount? "))
r = float(input("Enter the rate of interest? "))
t = float(input("Enter the time in years? "))
print("Simple Interest: ",simple_interest(p,r,t))
输出:
Enter the principle amount? 10000
Enter the rate of interest? 5
Enter the time in years? 2
Simple Interest: 1000.0
例3
#the function calculate returns the sum of two arguments a and b
def calculate(a,b):
return a+b
calculate(10) # this causes an error as we are missing a required arguments b.
输出:
TypeError: calculate() missing 1 required positional argument: 'b'
关键字参数
Python允许我们使用关键字参数调用该函数。这种函数调用将使我们能够以随机顺序传递参数。
参数的名称被视为关键字,并在函数调用和定义中进行匹配。如果找到相同的匹配项,则会在函数定义中复制参数的值。
请考虑以下示例。
例1
#function func is called with the name and message as the keyword arguments
def func(name,message):
print("printing the message with",name,"and ",message)
func(name = "John",message="hello") #name and message is copied with the values John and hello respectively
输出:
printing the message with John and hello
示例2在调用时以不同顺序提供值
#The function simple_interest(p, t, r) is called with the keyword arguments the order of arguments doesn't matter in this case
def simple_interest(p,t,r):
return (p*t*r)/100
print("Simple Interest: ",simple_interest(t=10,r=10,p=1900))
输出:
Simple Interest: 1900.0
如果我们在函数调用时提供不同的参数名称,则会引发错误。
请考虑以下示例。
例3
#The function simple_interest(p, t, r) is called with the keyword arguments.
def simple_interest(p,t,r):
return (p*t*r)/100
print("Simple Interest: ",simple_interest(time=10,rate=10,principle=1900)) # doesn?t find the exact match of the name of the arguments (keywords)
输出:
TypeError: simple_interest() got an unexpected keyword argument 'time'
python允许我们在函数调用时提供所需参数和关键字参数的混合。但是,必须在关键字参数之后给出必需的参数,即,一旦在函数调用中遇到关键字参数,以下参数也必须是关键字参数。
请考虑以下示例。
例4
def func(name1,message,name2):
print("printing the message with",name1,",",message,",and",name2)
func("John",message="hello",name2="David") #the first argument is not the keyword argument
输出:
printing the message with John , hello ,and David
以下示例将导致错误,因为在函数调用中传递了关键字和必需参数的不正确混合。
例5
def func(name1,message,name2):
print("printing the message with",name1,",",message,",and",name2)
func("John",message="hello","David")
输出:
SyntaxError: positional argument follows keyword argument
默认参数
Python允许我们在函数定义中初始化参数。如果在函数调用时未提供任何参数的值,则可以使用定义中给定的值初始化该参数,即使函数调用中未指定参数也是如此。
例1
def printme(name,age=22):
print("My name is",name,"and age is",age)
printme(name = "john") #the variable age is not passed into the function however the default value of age is considered in the function
输出:
My name is john and age is 22
例2
def printme(name,age=22):
print("My name is",name,"and age is",age)
printme(name = "john") #the variable age is not passed into the function however the default value of age is considered in the function
printme(age = 10,name="David") #the value of age is overwritten here, 10 will be printed as age
输出:
My name is john and age is 22
My name is David and age is 10
可变长度参数
在大型项目中,有时我们可能不知道要提前通过的参数的数量。在这种情况下,Python为我们提供了灵活性,可以提供逗号分隔值,这些值在函数调用时被内部视为元组。
但是,在函数定义中,我们必须使用(star)将变量定义为
请考虑以下示例。
例
def printme(*names):
print("type of passed argument is ",type(names))
print("printing the passed arguments...")
for name in names:
print(name)
printme("john","David","smith","nick")
输出:
type of passed argument is
printing the passed arguments...
john
David
smith
nick
变量范围
变量的范围取决于声明变量的位置。在程序的一个部分中声明的变量可能无法被其他部分访问。
在python中,变量是使用两种类型的范围定义的。
全局变量
局部变量
已知在任何函数外部定义的变量具有全局范围,而已知在函数内定义的变量具有局部范围。
请考虑以下示例。
例1
def print_message():
message = "hello !! I am going to print a message." # the variable message is local to the function itself
print(message)
print_message()
print(message) # this will cause an error since a local variable cannot be accessible here.
输出:
hello !! I am going to print a message.
File "/root/PycharmProjects/PythonTest/Test1.py", line 5, in
print(message)
NameError: name 'message' is not defined
例2
def calculate(*args):
sum=0
for arg in args:
sum = sum +arg
print("The sum is",sum)
sum=0
calculate(10,20,30) #60 will be printed as the sum
print("Value of sum outside the function:",sum) # 0 will be printed
输出:
The sum is 60
Value of sum outside the function: 0
lambda函数
Python允许我们不以标准方式声明函数,即不使用def关键字,而是使用lambda关键字声明匿名函数。Lambda函数可以接受任意数量的参数,但它们只能以表达式的形式返回一个值。
Python允许我们不以标准方式声明函数,即使用def关键字。而是使用lambda关键字声明匿名函数。但是,Lambda函数可以接受任意数量的参数,但它们只能以表达式的形式返回一个值。
匿名函数包含一小段代码。它模拟C和C ++的内联函数,但它不完全是内联函数。
下面给出了定义匿名函数的语法。
lambda arguments : expression
例1
x = lambda a:a+10 # a is an argument and a+10 is an expression which got evaluated and returned.
print("sum = ",x(20))
输出:
sum= 30
例2
Lambda函数的多个参数
x = lambda a,b:a+b # a and b are the arguments and a+b is the expression which gets evaluated and returned.
print("sum = ",x(20,10))
输出:
Sum = 30
为什么要使用lambda函数?
当我们在另一个函数中匿名使用lambda函数时,lambda函数的主要作用更好地描述。在python中,lambda函数可以用作高阶函数的参数作为参数。Lambda函数也用于我们需要的场景中考虑以下示例。
例1
#the function table(n) prints the table of n
def table(n):
return lambda a:a*n; # a will contain the iteration variable i and a multiple of n is returned at each function call
n = int(input("Enter the number?"))
b = table(n) #the entered number is passed into the function table. b will contain a lambda function which is called again and again with the iteration variable i
for i in range(1,11):
print(n,"X",i,"=",b(i)); #the lambda function b is called with the iteration variable i,
输出:
Enter the number??10
10 X 1 = 10
10 X 2 = 20
10 X 3 = 30
10 X 4 = 40
10 X 5 = 50
10 X 6 = 60
10 X 7 = 70
10 X 8 = 80
10 X 9 = 90
10 X 10 = 100
例2
使用lambda函数与过滤器
#program to filter out the list which contains odd numbers
List = {1,2,3,4,10,123,22}
Oddlist = list(filter(lambda x:(x%3 == 0),List)) # the list contains all the items of the list for which the lambda function evaluates to true
print(Oddlist)
输出:
[3,123]
例3
使用lambda函数与map
#program to triple each number of the list using map
List = {1,2,3,4,10,123,22}
new_list = list(map(lambda x:x*3,List)) # this will return the triple of each item of the list and add it to new_list
print(new_list)
输出:
[3,6,9,12,30,66,369]
递归函数
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出:
fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n
所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。
于是,fact(n)用递归的方式写出来就是:
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
上面就是一个递归函数。可以试试:
>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827
223758251185210916864000000000000000000000000L
如果我们计算fact(5),可以根据函数定义看到计算过程如下:
=> fact(5)
=> 5 * fact(4)
=> 5 * (4 * fact(3))
=> 5 * (4 * (3 * fact(2)))
=> 5 * (4 * (3 * (2 * fact(1))))
=> 5 * (4 * (3 * (2 * 1)))
=> 5 * (4 * (3 * 2))
=> 5 * (4 * 6)
=> 5 * 24
=> 120
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000):
>>> fact(1000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in fact
...
File "<stdin>", line 4, in fact
RuntimeError: maximum recursion depth exceeded
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:
def fact(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
可以看到,return fact_iter(num - 1, num product)仅返回递归函数本身,num - 1和num product在函数调用前就会被计算,不影响函数调用。
fact(5)对应的fact_iter(5, 1)的调用如下:
=> fact_iter(5, 1)
=> fact_iter(4, 5)
=> fact_iter(3, 20)
=> fact_iter(2, 60)
=> fact_iter(1, 120)
=> 120
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。
装饰器
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
>>> def now():
... print '2013-12-25'
...
>>> f = now
>>> f()
2013-12-25
函数对象有一个name属性,可以拿到函数的名字:
>>> now.__name__
'now'
>>> f.__name__
'now'
现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
def log(func):
def wrapper(*args, **kw):
print 'call %s():' % func.__name__
return func(*args, **kw)
return wrapper
观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print '2013-12-25'
调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:
>>> now()
call now():
2013-12-25
把@log放到now()函数的定义处,相当于执行了语句:
now = log(now)
由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(args, *kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print '%s %s():' % (text, func.__name__)
return func(*args, **kw)
return wrapper
return decorator
这个3层嵌套的decorator用法如下:
@log('execute')
def now():
print '2013-12-25'
执行结果如下:
>>> now()
execute now():
2013-12-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log('execute')(now)
我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有name等属性,但你去看经过decorator装饰之后的函数,它们的name已经从原来的'now'变成了'wrapper':
>>> now.__name__
'wrapper'
因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的name等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.name = func.name这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print 'call %s():' % func.__name__
return func(*args, **kw)
return wrapper
或者针对带参数的decorator:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print '%s %s():' % (text, func.__name__)
return func(*args, **kw)
return wrapper
return decorator
import functools是导入functools模块。模块的概念稍后讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。
在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。
decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。
请编写一个decorator,能在函数调用的前后打印出'begin call'和'end call'的日志。
再思考一下能否写出一个@log的decorator,使它既支持:
@log
def f():
pass
也支持:
@log('execute')
def f():
pass
偏函数
Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。Python中的偏函数通过设定参数的默认值,可以降低函数调用的难度。
Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。
在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下:
int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:
>>> int('12345')
12345
但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
def int2(x, base=2):
return int(x, base)
这样,我们转换二进制就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值:
>>> int2('1000000', base=10)
1000000
最后,创建偏函数时,实际上可以接收函数对象、args和*kw这3个参数,当传入:
int2 = functools.partial(int, base=2)
实际上固定了int()函数的关键字参数base,也就是:
int2('10010')
相当于:
kw = { base: 2 }
int('10010', **kw)
当传入:
max2 = functools.partial(max, 10)
实际上会把10作为*args的一部分自动加到左边,也就是:
max2(5, 6, 7)
相当于:
args = (10, 5, 6, 7)
max(*args)
结果为10。
当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
END
时光,在物转星移中渐行渐远,春花一梦,流水无痕,没有在最想做的时候去做的事情,都是人生的遗憾。人生需要深思熟虑,也需要一时的冲动。
以上是关于第5章:函数式编程的主要内容,如果未能解决你的问题,请参考以下文章