廖雪峰课程 Python笔记(二)

一、学习网站

廖雪峰 Python课程:点击打开

二、函数式编程

以下是引用 廖雪峰课程 里面对 Python 函数式编程的介绍,了解点原理也是很重要的。

函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。

越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

1、高阶函数

这个案例已经写好在另外一篇,点击查看:python高阶函数demo%E5%B8%B8%E7%94%A8%E9%AB%98%E9%98%B6%E5%87%BD%E6%95%B0demo.py)

2、返回函数

高阶函数除了可以接收函数作为参数外,还可以把函数作为结果值返回。如:

1
2
3
4
5
6
7
8
9
10
11
def lazy_sum(*args):
def sum():
ax = 0
for i in args:
ax = ax + i
return ax
return sum
f = lazy_sum(1,3,5,7,9)
print(f) # <function lazy_sum.<locals>.sum at 0x101c6ed90>
# 输出 f 返回的是个函数对象,需要再执行 f() 才是真正的值
print(f()) # 25

在函数 lazy_sum 函数中又定义了函数 sum ,且函数 sum 内部又引入外部函数 lazy_sum 的参数和局部变量,当 lazy_sum 返回函数 sum 时,相关参数和变量都保存在返回的函数中,即 闭包
另外,每次调用 lazy_sum 都会返回一个新的函数,即使传入相同参数:

1
2
3
f1 = lazy_sum(1,3,5,7,9)
f2 = lazy_sum(1,3,5,7,9)
f1 == f2 # false f1()和f2()的调用结果互不影响。

闭包 还需要注意:返回的函数并没有立刻执行,而是需要再次调用 f() 才可以获得结果。
还有个案例:

1
2
3
4
5
6
7
8
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()

在这例子中,每次循环都创建了一个新的函数,然后把创建的3个函数都返回了。
你可能认为调用 f1()f2()f3() 结果应该是 149 ,但实际结果是:

1
2
3
f1()    # 9
f2() # 9
f3() # 9

原因是:在返回的函数引用了变量 i ,但它并非立刻执行,等到3个函数都返回时,变量 i 已经变成 3 了,所以结果都是 9

返回闭包时牢记:返回参数不要引入任何循环变量,或者后续会发生变化的变量。

如果必须使用循环变量,可以再创建一个函数,用该函数的参数绑定循环变量的当前值,无论该循环变量后续如何修改,已绑定到函数参数值不变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs

f1, f2, f3 = count()
f1() # 1
f2() # 4
f3() # 9

缺点是代码较长,可利用 lambda 函数缩短代码。

3、匿名函数

即不需要显式定义函数,直接传入匿名函数使用。例子:

1
2
3
4
5
6
# 正常写法:
def f(x):
return x*x

# 匿名函数写法:
list(map(lambda x:x*x,[1,2,3,4,5])) # [1,4,9,16,25]

匿名函数格式如: lambda 参数名称 : 执行操作内容 , 参数内容

注意:匿名函数只能有一个表达式,不用写 return ,返回值就是该表达式的结果。
匿名函数的好处:没有名字所以不用担心函数名冲突,此外,匿名函数是个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用这个函数。

1
2
3
f = lambda x : x*x 
f # <function <lambda> at 0x101c6ef28>
f(5) # 25

同样,也可以把匿名函数作为返回值返回:

1
2
def build(x,y):
return lambda : x*x + y*y

4、装饰器

用于代码运行期间动态添加功能,称为 "装饰器(Decorator) "
由于函数是一个对象,而且函数对象可以被赋值给变量,所以变量也可以调用该函数。
本质上,Decorator 就是一个返回函数的高阶函数,所以,我们定一个个能打印日志的 Decorator 如下:

1
2
3
4
5
def log (func): 
def wrapper(*args,**kw):
print("call %s():" % function.__name__) # __name__ 是函数对象的一个属性,输出函数名称
return func(*args,**kw)
return wrapper

接下来借助 Python@ 语法,把 Decorator 置于函数的定义处:

1
2
3
4
5
6
@log
def now():
print("2017-11-30")
now()
# call now()
# "2017-11-30"

调用 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 参数,默认 10base 参数代表按照N进制转换:

1
2
int("12345",base = 8)  # 5349
int("12345",16) # 74565

通过下面方法实现同时转换大量二进制字符串:

1
2
3
4
def int2(x,base=2):
return int(x,base)
int2("1000000") # 64
int2("1010101") # 85

functool.partila 就是帮助我们创建一个偏函数的,不需要我们自定义 int2() ,如下:

1
2
3
4
import functools
int2 = functools.partial(int,base=2)
int2("1000000") # 64
int2("1010101") # 85

总结 functools.partial 的作用就是:设置函数默认值,返回一个新函数,调用新函数会更简单。
上面新的 int2 函数只是把 base 参数重新设置默认值为 2,但也可以传入其他值:

1
int2("1000000",base=10) # 1000000

实际上,创建偏函数时可以接收 3 个参数:函数对象*args**kw
当函数的参数个数太多,需要简化时,使用 functools.partial 可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。