Python学习(三)函数方法等
一、学习网站
廖雪峰 Python进阶 慕课网:点击打开
二、函数式编程
1、函数式编程特点:
把计算视为函数而非指令;
纯函数式编程:不需要变量,没有副作用,测试简单;
支持高阶函数,代码简洁;
2、python函数式编程特点:
不是纯函数式编程:允许有变量;
支持高阶函数:函数也可以作为变量传入;
支持闭包:有了闭包就能返回函数;
有限度的支持匿名函数;
3、高阶函数 案例:
变量可以指向函数,比如:
1 | abs(-10) # 10 |
函数名其实就是指向函数的变量
1 | abs(-10) # 10 |
4、高阶函数 概念:
能接收函数作为参数的函数;
变量可以指向函数;
函数的参数可以接收变量;
一个函数可以接收另一个函数作为参数;
能接收函数作参数的函数就是高阶函数;
1 | def add(x,y,fun): |
5、python内置高阶函数 – map()函数
它接收一个函数 f
和一个 list
,并通过把函数 f
依次作用在 list
的每个元素上,得到一个新的 list
并返回。
1 | def f(x): |
注意:map()函数不改变原有的 list,而是返回一个新的 list。
由于list包含的元素可以是任何类型,因此,map()
不仅仅可以处理只包含数值的 list
,事实上它可以处理包含任意类型的 list
,只要传入的函数f
可以处理这种数据类型。
案例:
将所有姓名转换成 'Adam'
格式:
1 | def format_name(s): |
6、python内置高阶函数 – reduce()函数
它接收一个函数 f
和一个 list
,函数f必须传入2个参数
,reduce()
对list
的每个元素反复调用函数f
,并返回最终结果值。
函数f接收的两个参数,即为list
里相邻的两个参数。
1 | def f(x, y): |
reduce()还可以接收第三个参数,作为计算初始值。
1 | def f(x,y): |
案例:
利用reduce()
实现求积函数。
1 | def prod(x, y): |
7、python内置高阶函数 – filter()函数
它接收一个函数 f
和一个 list
,这个函数作用是对每个元素进行判断,返回True
和 False
。filter()
根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list
。
例如:要从一个list [1, 4, 6, 7, 9, 12, 17]中删除偶数,保留奇数,
1 | def is_odd(x): # 定义判断函数 |
例如:删除None或者空字符串,
1 | def is_not_empty(s): |
注意: s.strip(rm) 删除 s 字符串中开头、结尾处的 rm 序列的字符。
当rm
为空时,默认删除空白符(包括’\n’, ‘\r’, ‘\t’, ‘ ‘),如下:
1 | a = ' 123' |
案例:
利用filter()
过滤出1~100中平方根是整数的数:
1 | import math |
8、python内置高阶函数 – sorted()函数 自定义排序
1 | sorted([36, 5, 12, 9, 21]) # [5, 9, 12, 21, 36] |
它可以接收一个比较函数
来实现自定义排序
,比较函数的定义是,传入两个待比较的元素 x, y
,如果 x
应该排在 y
的前面,返回 -1
,如果 x
应该排在 y
的后面,返回 1
。如果 x
和 y
相等,返回 0
。
1 | def reversed_cmp(x, y): # 实现倒序排序 |
sorted()
也可以对字符串进行排序,字符串默认按照ASCII大小
来比较:
1 | sorted(['bob', 'about', 'Zoo', 'Credit']) # ['Credit', 'Zoo', 'about', 'bob'] |
'Zoo'
排在'about'
之前是因为'Z'
的ASCII码比'a'
小。
案例:
利用sorted()
高阶函数,实现忽略大小写排序的算法。
1 | def cmp_ignore_case(s1, s2): |
9、python中返回函数
Python的函数不但可以返回int
、str
、list
、dict
等数据类型,还可以返回函数
!
1 | def f(): |
注意区分返回函数和返回值:
1 | def myabs(): |
案例:
请编写一个函数calc_prod(lst)
,它接收一个list
,返回一个函数,返回函数可以计算参数的乘积。
1 | def calc_prod(lst): |
10、python中的闭包
在函数内部定义的函数和外部定义的函数是一样的,只是他们无法被外部访问:
1 | def f(): |
闭包案例:
1 | def f(x): |
注意:发现没把g移到f外部,因为它引用了f的参数x。
像这种内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)
。
闭包的特点:
就是返回的函数还引用了外层函数的局部变量
,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。
举例如下:
1 | # 希望一次返回3个函数,分别计算1x1,2x2,3x3: |
结果全是9
,原因是当count()
返回3个函数时,这3个函数引用的变量i
已经都变成3
了,而f1,f2,f3没有被调用,所以此时的i未计算。
因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量。
案例:
返回闭包不能引用循环变量,请改写count()函数,让它正确返回能计算1x1、2x2、3x3的函数。
1 | def count(): |
11、python中的匿名函数
python对匿名函数支持有限,以map函数为例,计算f(x)=x2
时,除了定义一个f(x)
的函数外,还可以直接传入匿名函数:
1 | map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]) # [1, 4, 9, 16, 25, 36, 49, 64, 81 |
通过对比可以看出,匿名函数 lambda x: x * x
实际上就是:
1 | def f(x): |
关键字lambda
表示匿名函数
,冒号前面的 x
表示函数参数
。
注意:匿名函数有个限制,就是只能有一个表达式,不写return,返回值就是该表达式结果。
使用匿名函数可以不必定义函数名,直接创建一个函数对象,很多时候可以简化代码:
1 | sorted([1, 3, 9, 5, 0], lambda x,y: -cmp(x,y)) # [9, 5, 3, 1, 0] |
返回参数的时候,也可以返回匿名函数:
1 | myabs = lambda x:-x if x < 0 else x |
案例:
利用匿名函数,简化下面代码:
1 | def is_not_empty(s): |
简化后:
1 | print filter(lambda s:s and len(s.strip())>0, ['test', None, '', 'str', ' ', 'END'] ) |
12、装饰器(decorator )
通过高阶函数
,接收一个函数作为参数,进行包装返回一个新函数
,即可动态对函数进行增强。
1 | def f1(x): |
作用:极大简化代码,避免每个函数编写重复性代码。
比如:
打印日志:@log;
检测性能:@performance;
数据库事务:@transaction;
URL路由:@post(‘/register’);
内置@语法
就是为了简化
装饰器的调用。
1 |
|
13、无参数的装饰器 (decorator)
Python的 decorator
本质上就是一个高阶函数
,它接收一个函数作为参数,然后,返回一个新函数。
使用 decorator
用Python提供的 @
语法,这样可以避免手动编写 f = decorate(f)
这样的代码。
1 | def log(f): |
若参数不是一个函数,调用将报错:
1 |
|
因为 add()
函数需要传入两个参数,但是 @log 写死了只含一个参数的返回函数。
要让 @log
自适应任何参数定义的函数,可以利用Python
的 *args
和 **kw
,保证任意个数的参数总是能正常调用:
1 | def log(f): |
现在,对于任意函数,@log
都能正常工作。
案例:
请编写一个@performance,它可以打印出函数调用的时间。
time模块介绍 点击查看
1 | import time |
14、有参数的装饰器(decorator)
实现效果比如:如果有的函数非常重要,希望打印出'[INFO] call xxx()...'
,有的函数不太重要,希望打印出'[DEBUG] call xxx()...'
,这时,log函数
本身就需要传入'INFO'
或'DEBUG'
这样的参数,类似这样:
1 |
|
把上面的定义翻译成高阶函数的调用,就是
1 | my_func = log('DEBUG')(my_func) |
上面的语句看上去还是比较绕,再展开一下:
1 | log_decorator = log('DEBUG') |
上面的语句又相当于:
1 | log_decorator = log('DEBUG') |
所以,带参数的log函数
首先返回一个decorator
函数,再让这个decorator函数
接收my_func
并返回新函数:
1 | def log(prefix): |
案例:
上一节的@performance
只能打印秒,请给 @performace
增加一个参数,允许传入's'
或'ms'
:
1 | import time |
15、完善decorator
@decorator 可以动态实现函数功能的增加,但是,经过
@decorator “改造”后的函数,和原函数相比,除了功能多一点外,有没有其它不同的地方? 在没有
decorator`的情况,打印函数名
1 | def f1(x): |
在有decorator
的情况下,打印函数名:
1 | def log(f): |
可见,由于 decorator
返回的新函数函数名已经不是 'f2'
,而是 @log
内部定义的 'wrapper'
。
如果要让调用者看不出一个函数经过了 @decorator
的“改造”,就需要使用Python内置的 functools
,自动把原函数的一些属性复制到新函数中。
1 | import functools |
最后需要指出,由于我们把原函数签名改成了(*args, **kw)
,因此,无法获得原函数的原始参数信息。
即便我们采用固定参数来装饰只有一个参数
的函数:
1 | def log(f): |
也可能改变原函数的参数名,因为新函数的参数名始终是 'x'
,原函数定义的参数名不一定叫 'x'
。
16、偏函数
当一个函数有很多参数
时,调用者就需要提供多个参数。如果减少参数个数,就可以简化调用者的负担。
如:
1 | # int() 函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换: |
假设要转换大量的二进制字符串,每次都传入int(x, base=2)
非常麻烦,
于是,我们定义一个int2()
的函数,默认把base=2
传进去:
1 | def int2(x, base=2): |
functools.partial
就是帮助我们创建一个偏函数
的,不需要我们自己定义int2()
,
可以直接使用下面的代码创建一个新的函数 int2
:
1 | import functools |
所以,functools.partial
可以把一个参数多的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值,这样,新函数调用的难度就降低了。
案例:
在sorted这个高阶函数中传入自定义排序函数就可以实现忽略大小写排序。请用 functools.partial
把这个复杂调用变成一个简单的函数:
1 | import functools |