dustman

 在上一篇文章中,我们介绍了 Python 的数据类型,现在我们介绍 Python 的函数式编程。

 查看上一篇文章请点击:https://www.cnblogs.com/dustman/p/9986318.html

函数式编程

函数式编程

函数式 (Functional Programming) 是一种以函数为基础的编程风格。
函数编程的一个关键部分是高阶函数 (Highter-order function)。在关于函数作为对象的文章中,我们简单地看到了这个想法的例子。高阶函数接受其他函数作为参数,甚至允许将函数作为结果返回。

def apply(func,msg):
 return func(func(msg))

def add(x):
 return x + 3

print(apply(add,20))

运行结果:

>>>
26
>>>
一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数,函数式编程就是指这种高度抽象的编程范式。

纯函数
函数式编程是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量。因此,任意一个函数只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。
这就是数学中函数的工作方式:例如,sin(x) 对于 x 的相同值,总是返回相同的结果。
纯函数:

def func(x,y):
temp = x*2 + y/4
return temp /(x/4 + y*2)

非纯函数:

my_list =[]
def impure_func(msg):
my_list.append(msg)
非纯函数是因为返回的结果是不固定的,上面的代码改变了列表 my_list。

纯函数的优点

1、 容易推理和测试。
2、 能够并行运算。
3、 更有效率。一旦对输入的函数进行了求值,对于给定的参数结果都是固定的,就可以存储起来并在下次需要该函数时引用,从而减少调用函数的次数。这叫记忆化,英文叫 Memoization
使用纯函数的主要缺点是,它们是 IO 的简单任务复杂化,这种任务本身就需要副作用。在一些情况下纯函数可能更难编写。

匿名函数

到目前为止,我们编写函数的方式是使用 def 来创建函数,并指派一个 def 定义的变量名来调用。

在某些情况下,我们不需要显式地定义函数。如果函数是使用 lambda 语法创建的,这种方式创建的函数称为匿名函数

这种方法在不需要显式地定义函数时最常用。

下列示例中将显式语法,它由 lambda 关键字、参数列表、冒号和要计算和返回的表达式组成。

def func(f,msg):
 return f(msg)

func(lambda x:4*x*x,5)

lambda 演算是 Alonzo Church 给出的一套图灵机等价的形式计算系统。lambda 演算系统的精妙之处在于处理递归,lambda 演算也叫 λ 演算。

Python 对匿名函数的支持有限,只有一些简单的情况下可以使用,通常是一个单行的代码来写一个表达式。

# named function
def func(x):
 return x + 5*x + 8

print(func(5))

#lambda
print((lambda x:x + 5*x + 8)(5))

运行结果:

>>>
38
38
>>>
用匿名函数的好处:由于函数没有名字,因此不必担心函数名冲突

匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。

f = lambda x:x + 5*x + 8

print(f(2))

运行结果:

>>>
20
>>>
为了使程序规范化,最好使用 def 来定义函数。我们也可以把匿名函数作为返回值返回。

map 和 filter 函数

Python 内建了 mapfilter 高阶函数,它们接收一个 iterable (可迭代 ) 对象,如:列表。

map 函数

map 函数接收两个参数,一个是函数,一个是 iterable, map将传入的函数依次作用到序列的每个元素,并把结果作为新的 iterator 返回。

比如我们有一个函数 func(x) = x + 1,要把这个函数作用在一个列表 [11,22,33,44,55]  上,可以使用 map 实现。

实现代码如下:

def add(x):
 return x + 1

my_list = [11,22,33,44,55]
result = list(map(add,my_list))
print(result)

运行结果:

>>>
[12, 23, 34, 45, 56]
>>>

也可以使用匿名函数来创建。

my_list = [11,22,33,44,55]
result = list(map(lambda x:x+1,my_list))
print(result)
map 函数返回的是一个 iterator,是一个惰性序列,需要用 list 函数强制转换成列表。

filter 函数

Python 内建的 filter 函数用于过滤序列,给定的函数返回 Boolean 值。根据返回值是 True 还是 False 决定保留还是丢弃该元素。

my_list = [11,22,33,44,55]
ret = list(filter(lambda x:x%3 == 0,my_list))
print(ret)

运行结果:

>>>
[33]
>>>
filter 函数返回的是一个 iterator需要用 list 函数强制转换成列表。

生成器
在前面,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。如果我们仅仅需要访问大列表里的几个列表,绝大多数元素占用的空间都白白浪费了。
如果列表元素可以按照某种算法推算出来,那么就有可能不用这么大的内存,在 Python 中,这种一边循环一边计算的机制,称为生成器,英文 generator生成器是一种像列表和元组的 iterable
写列表不同,它们不允许使用索引进行索引,但仍然可以使用 for 循环进行遍历。语法 yield 用来生成生成器。

def count():
 i = 0
 while i < 5:
  yield i
  i += 1

for i in count():
 print(i)

运行结果:

>>>
0
1
2
3
4
>>>
yield 语句用于定义生成器,将函数的返回值替换为向调用者提供结果而不破坏本地变量。

由于每次生成一个数据,生成器没有了列表的内存限制,可以无穷调用!

def func():
 while True:
  yield 1

for i in func():
 print(i)

运行结果:

>>>
1
1
1
1
...
>>>
简而言之,生成器允许声明一个类似迭代器的函数,它可以在 for 循环中使用。

生成器做为 list 函数的参数以使用它生成列表。

def func(x):
 for i in range(x):
  if i % 3 == 0:
   yield i

print(list(func(10)))

运行结果:

>>>
[0, 3, 6, 9]
>>>
由于具有延迟并按需生成值的特性,使用生成器可以提高性能,这也会降低内存的开销。另外,我们不需要等到所有元素生成之后才开始使用它们。

下面代码会输出什么结果?

def func():
 nums = ""
 for x in "123456":
  nums += x
  yield nums

print(list(func()))

运行结果:

>>>
['1', '12', '123', '1234', '12345', '123456']
>>>

装饰器
在代码运行期间动态增加功能的方式,称之为装饰器 (Decorator)。
当你需要扩展一个功能,但是不想修改代码,就可以用装饰器。

def decor(func):
 def wp():
  print("===============")
  func()
  print("===============")
 return wp

def text():
 print("I like Python!")

decorated = decor(text)
decorated()

上面例子定义了一个 decor 函数接收一个叫 func 的函数,在函数体 decor 里面我们定义了一个函数 wp。函数 wp 打印一些字符串并调用函数 func。函数 decor 返回函数 wp。我们使用 decorated 来装饰 text 函数,让其添加一些功能。
本质上,装饰器就是一个返回函数的高阶函数。所以,我们定义一个能获取运行时间的装饰器,可以定义如下:

import time

def timmer(func):
 def warpper(*args,**kwargs):
  start_time = time.time()
  func()
  stop_time = time.time()
  print('the func run time is %s' %(stop_time-start_time))
 return warpper()

一个装饰器接收一个函数作为参数,并返回一个函数。之前的例子我们使用装饰器修改了 text 函数。

def text():
 print("I like Python!")

decorated = decor(text)
decorated()

如果我们需要一个模式可以在任何时间使用装饰器修改一个函数,那么要借助 Python 的 @ 语法,把装饰器函数置于函数的定义处。

@decor
def text():
 print("I like Python!")

这个版本的代码是上一版本的简写。

一个函数可以有多个装饰器。

递归函数
递归函数是函数式编程里比较重要的概念。
在函数内部,可以调用其他函数。如果一个函数在内部调用自身,这个函数就是递归函数。
举个来计算阶乘的例子 recursion(n) = n! = 1 * 2 * 3 * … * (n-2) * (n-1) * n = (n - 1)! * n = recursion(n-1) * n
所以 recursion(n) 可以表示为 n * recursion(n-1),最后我们需要处理一下 1! = 1 的问题。于是代码就如下所示。

def recursion(n): #'定义递归函数实现求阶乘功能'
 if n==1:
  return 1
 else:
  return n*recursion(n-1)

print(recursion(5))

运行结果:

>>>
720
>>>

根据函数定义可以看到计算过程展开如下:

===> recursion(6)
===> 6 * recursion(5)
===> 6 * (5 * recursion(4))
===> 6 * (5 * (4 * recursion(3)))
===> 6 * (5 * (4 * (3 * recursion(2))))
===> 6 * (5 * (4 * (3 * (2 * recursion(1)))))
===> 6 * (5 * (4 * (3 * (2 * 1))))
===> 6 * (5 * (4 * (3 * 2)))
===> 6 * (5 * (4 * 6))
===> 6 * (5 * 24)
===> 6 * 120
===> 720
使用递归函数需要特别关注如何退出递归。

在计算机中,函数调用是通过栈 (stack) 这种数据结构实现的,每当进入一次函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小是无限的,所以,递归调用的次数过多,会导致栈溢出导致程序崩溃。下面的代码说明如果没有退出机制,程序因为内存不足而崩溃。

def recursion(n): #'定义递归函数实现求阶乘功能'
 return n * recursion(n-1)

print(recursion(6))

运行结果:

>>>
RecursionError: maximum recursion depth exceeded
>>>
解决递归调用栈溢出的方法可以通过尾递归优化。

尾递归是指在函数返回的时候调用自身,并且 return 语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
上面的函数由于引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中。

def recursion(num, product):
 return fune(num, product)

def fune(num, product):
 if num == 1:
  return product
 else:
  return fune(num - 1, num * product)

print(recursion(6, 1))

运行结果:

>>>
720
>>>

遗憾的是大多数编程语言没有针对尾递归做优化,Python 解释器也没有做优化。所以,即使上面的 recursion 函数改成尾递归方式,也会导致栈溢出。

集合

集合 (set) 是一种数据结构与字典类似,也是一组 key 的集合,但不存储 value

创建集合有两种方式:使用列表做为参数的 set 函数或或者语法 {} 创建。

nums = {1,2,3,4}
msg = set(["I","like","Python","!"])

print(1 in nums)
print("I" not in msg)

运行结果:

>>>
True
False
>>>
创建一个空 set 必须使用 set 函数,使用 {} 会创建字典。

集合与列表有几种不同之处,但是它们都可以使用 len 函数。

函数是无序的,这意味着它们不能被索引,在集合中也没有重复的 key

由于采用不同的数据结构,集合的 in 操作里比列表更快。使用 add 添加元素,使用 remove 删除特定的元素,pop 删除任意一个函数。

nums = {1,2,1,5,3,4}
print(nums)
nums.add(-5)
nums.remove(1)
print(nums)

运行结果:

>>>
{1, 2, 3, 4, 5}
{2, 3, 4, 5, -5}
>>>
集合的基本用途包括元素存在测试和消除重复元素。

集合能使用一些数学概念上的操作。

并集操作符 | 合并两个集合。

交集操作符 & 返回两个集合都存在的元素。

补集操作符 - 返回元素在第一个集合,但是不在第二个集合。

对称差操作符 ^ 返回不在两个集合中都存在的元素。

nums_1 = {9,8,7,6,5}
nums_2 = {5,4,3,2,1}

print(nums_1 | nums_2)
print(nums_1 & nums_2)
print(nums_1 - nums_2)
print(nums_2 - nums_1)
print(nums_1 ^ nums_2)

运行结果:

>>>
{1, 2, 3, 4, 5, 6, 7, 8, 9} {5} {8, 9, 6, 7} {1, 2, 3, 4} {1, 2, 3, 4, 6, 7, 8, 9}
>>>

数据结构

先前,我们看到 Python 拥有下面的数据结构:列表 (list) , 字典 (dictionary) , 元组 (tuple) , 集合 (set) 。

当你需要唯一的元素,使用集合。

当你需要数据不能被修改,使用元组。

当你需要一个简单的、可迭代的、经常修改的集合时,或需要有序访问,请尝试选择列表。

使用字典的情况:

- 需要一种 key-value 的这种逻辑。

- 需要一种通过 key 快速查找数据的方式。

- 需要经常修改数据。

很多时候,元组与字典会结合使用,例如,元组可能代表一个键,因为它是不可变的。

itertools 模块
Python 的内建模块 itertools 提供了在函数式编程里非常有用的用于操作迭代对象的函数。
无限迭代器:count 一个计数器,可以指定起始位置和步长。cycle 会把引入的一个序列 (列表或字符串) 无限重复下去。repeat 把一个元素无限重复下去,如果提供第二个参数就可以限定重复次数。

from itertools import count

for i in count(5):
 print(i)
 if i >= 10:
  break

运行结果:

>>>
5
6
7
8
9
10
>>>

itertools 模块包含很多函数处理 iterator 对象,就像 mapfilter 函数一样。
有限迭代器:
takewhile - 函数根据条件判断来获取序列。
chain - 把一组迭代对象串联起来,形成一个更大的迭代器。
accumulate - 迭代器返回累计求和结果。

from itertools import accumulate,takewhile

nums = list(accumulate(range(5)))
print(nums)
print(list(takewhile(lambda x:x<=3,nums)))

运行结果:

>>>
[0, 1, 3, 6, 10]
[0, 1, 3]
>>>

组合生成器:
product - 产生多个列表和迭代器的笛卡尔积。
permutation - 产生指定输煤

from itertools import product,permutations

msg = ("a","b")
print(list(product(msg,range(2))))
print(list(permutations(msg)))

运行结果:

>>>
[('a', 0), ('a', 1), ('b', 0), ('b', 1)]
[('a', 'b'), ('b', 'a')]
>>>

 

 

 

“我们不能用制造问题的思路去解决问题。”   --阿尔伯特·爱因斯坦

分类:

技术点:

相关文章:

  • 2021-10-07
  • 2018-12-04
  • 2018-11-23
  • 2021-06-13
  • 2020-11-10
  • 2021-11-01
  • 2021-08-09
猜你喜欢
  • 2018-12-02
  • 2018-11-20
  • 2021-04-20
  • 2018-12-05
  • 2021-06-22
  • 2021-10-24
  • 2020-02-16
相关资源
相似解决方案