廖雪峰课程 Python笔记(二)
一、学习网站
廖雪峰 Python课程:点击打开
二、函数式编程
以下是引用 廖雪峰课程
里面对 Python
函数式编程的介绍,了解点原理也是很重要的。
函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
1、高阶函数
这个案例已经写好在另外一篇,点击查看:python高阶函数demo
2、返回函数
高阶函数除了可以接收函数作为参数外,还可以把函数作为结果值返回。如:
1 | def lazy_sum(*args): |
在函数 lazy_sum
函数中又定义了函数 sum
,且函数 sum
内部又引入外部函数 lazy_sum
的参数和局部变量,当 lazy_sum
返回函数 sum
时,相关参数和变量都保存在返回的函数中,即 闭包
。
另外,每次调用 lazy_sum
都会返回一个新的函数,即使传入相同参数:
1 | f1 = lazy_sum(1,3,5,7,9) |
闭包
还需要注意:返回的函数并没有立刻执行,而是需要再次调用 f()
才可以获得结果。
还有个案例:
1 | def count(): |
在这例子中,每次循环都创建了一个新的函数,然后把创建的3个函数都返回了。
你可能认为调用 f1()
,f2()
和 f3()
结果应该是 1
,4
,9
,但实际结果是:
1 | f1() # 9 |
原因是:在返回的函数引用了变量 i
,但它并非立刻执行,等到3个函数都返回时,变量 i
已经变成 3
了,所以结果都是 9
。
返回闭包时牢记:返回参数不要引入任何循环变量,或者后续会发生变化的变量。
如果必须使用循环变量,可以再创建一个函数,用该函数的参数绑定循环变量的当前值,无论该循环变量后续如何修改,已绑定到函数参数值不变化:
1 | def count(): |
缺点是代码较长,可利用 lambda
函数缩短代码。
3、匿名函数
即不需要显式定义函数,直接传入匿名函数使用。例子:
1 | # 正常写法: |
匿名函数格式如: lambda 参数名称 : 执行操作内容 , 参数内容
注意:匿名函数只能有一个表达式,不用写
return
,返回值就是该表达式的结果。
匿名函数的好处:没有名字所以不用担心函数名冲突,此外,匿名函数是个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用这个函数。
1 | f = lambda x : x*x |
同样,也可以把匿名函数作为返回值返回:
1 | def build(x,y): |
4、装饰器
用于代码运行期间动态添加功能,称为 "装饰器(Decorator) "
。
由于函数是一个对象,而且函数对象可以被赋值给变量,所以变量也可以调用该函数。
本质上,Decorator
就是一个返回函数的高阶函数,所以,我们定一个个能打印日志的 Decorator
如下:
1 | def log (func): |
接下来借助 Python
的 @
语法,把 Decorator
置于函数的定义处:
1 |
|
调用 now
函数,不仅运行 now
函数本身,还会在 now
函数前打印一行日志。
把 @log
放到 now
函数定义处,相当于执行了语句:
1 | now = log(now) |
因为 log
函数返回一个函数,所以原来d now
函数依然在,只是同名的变量 now
指向新函数,并调用 now()
执行函数,即在 log()
函数中返回的 wrapper()
函数。 简单理解就是,先执行了 log(now)
然后先返回了,再去执行后面定义的 now()
。
5、偏函数
Python中的 functools
模块提供了很多有用的功能,其中一个就是偏函数 ,要注意,它与数学上的偏函数不同。
之前讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数可以实现,如下:int()
函数默认将 字符串
按 十进制
转成 整数
:
1 | int("12345") # 12345 |
但 int()
函数提供额外的 base
参数,默认 10
, base
参数代表按照N进制转换:
1 | int("12345",base = 8) # 5349 |
通过下面方法实现同时转换大量二进制字符串:
1 | def int2(x,base=2): |
functool.partila
就是帮助我们创建一个偏函数的,不需要我们自定义 int2()
,如下:
1 | import functools |
总结 functools.partial
的作用就是:设置函数默认值,返回一个新函数,调用新函数会更简单。
上面新的 int2
函数只是把 base
参数重新设置默认值为 2
,但也可以传入其他值:
1 | int2("1000000",base=10) # 1000000 |
实际上,创建偏函数时可以接收 3
个参数:函数对象
、*args
和 **kw
。
当函数的参数个数太多,需要简化时,使用 functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。