在10分钟内学习Python中的函数式编程

图片
照片: 克里斯·里德

在本文中,您将了解什么是函数范例以及如何在Python中使用函数编程。 您还将了解列表抽象和其他列表理解。

功能范式


在命令式范例中,您可以通过指定一系列稍后执行的动作来编写程序。 此时,状态( 大约Translator:变量,数组等 )发生变化。 例如,让变量A存储值5,然后更改此变量的值。 您使用变量,以便其值更改。

在功能范式中,您不告诉计算机要做什么,而是指定动作本身的性质。 一个数的最大公约数是什么,从1到n的计算结果等。

因此,变量不变。 一旦变量被初始化,其值将被永久保存(请注意,在纯函数语言中,它们甚至都不被称为变量)。 因此,在功能范式中,功能没有副作用 。 副作用可以定义为功能更改超出其限制的瞬间。 看一个例子:

a = 3 def some_func(): global a a = 5 some_func() print(a) 

该代码的执行结果为5。在函数式编程中,禁止更改变量,并且禁止更改超出其范围的功能。 该功能所能做的就是计算/处理某些东西并返回结果。

现在,您可能会想:“没有变量,没有副作用吗? 为什么这么好?” 真的很好的问题。

如果使用相同的参数调用了两次函数,则显然它将返回相同的结果。 如果您已经学习了一些有关数学函数的知识 ,那么您将感激不尽。 这称为链接透明或引用透明。 由于函数没有副作用,因此,如果要开发计算程序,则可以加快执行过程。 如果程序知道func(2)为3,我们可以记住这一点。 这样可以防止在我们已经知道结果的情况下再次调用该函数。

通常,在函数式编程中,不使用循环。 使用递归。 递归是一个数学概念,实际上,它的意思是“给自己喂食”。 在递归函数中,函数本身称为子函数的角色。 这是Python中的递归函数示例:

 def factorial_recursive(n): # Base case: 1! = 1 #   if n == 1: return 1 # Recursive case: n! = n * (n-1)! #    else: return n * factorial_recursive(n-1) 

一些编程语言是惰性的 。 这意味着他们正在最后一刻进行计算。 假设代码应执行2 + 2,则功能程序仅在需要结果时才计算结果。 稍后我们将了解Python惰性。

地图


要了解地图,您必须首先处理可迭代的容器。 这是一个您可以“遍历”的容器。 这些通常是列表或数组,但是Python中有很多这样的容器。 您甚至可以通过引入魔术方法来创建自己的容器。 这些方法(如API)可以帮助对象变得更加Pythonic。 有两种使对象可迭代的方法:

 class Counter: def __init__(self, low, high): # set class attributes inside the magic method __init__ # for "inistalise" #      self.current = low self.high = high def __iter__(self): # first magic method to make this object iterable #    return self def __next__(self): # second magic method #    if self.current > self.high: raise StopIteration else: self.current += 1 return self.current - 1 

第一个魔术方法是“ ___iter__”或dunder(下划线双下划线),iter返回一个可迭代的对象,通常在循环开始时使用。 Dunder next(__next__)返回下一个对象。

检查一下:

 for c in Counter(3, 8): print(c) 

执行结果:

3
4
5
6
7
8


在Python中,迭代器是仅具有__iter__方法的对象。 这意味着您可以访问对象(容器)的单元格的位置,但是不能“遍历”它们。 有些对象只有美妙的__next__方法,而没有设置神奇的__iter__方法(稍后会详细介绍)。 在本文中,我们将介绍与可迭代对象有关的所有内容。

现在我们知道了一个可迭代对象是什么,让我们回到map函数。 此函数使我们可以将其他函数的操作应用于迭代容器中的每个元素。 我们想对列表中的每个元素应用一个函数,这对于几乎所有可迭代的容器都是可行的。 Map有两个参数:要应用的函数和容器(列表等)。

 map(function, iterable) 

假设我们有一个包含以下元素的列表:

[1, 2, 3, 4, 5]

我们想要对每个元素求平方,可以像这样完成:

 x = [1, 2, 3, 4, 5] def square(num): return num*num print(list(map(square, x))) 

Python中的功能函数是惰性的。 如果我们不添加“ list()”,该函数将存储容器(列表)的描述,而不是列表本身。 我们直接需要告诉Python将其转换为列表。

突然从非懒惰的定义过渡到懒惰的定义有点奇怪。 如果您以功能性的方式而不是命令性的方式思考,您将习惯它。

编写函数(例如“ square(num)”)是正常的,但并不完全正确。 我们是否需要声明整个函数以仅在map中使用它? 可以通过引入(匿名)lambda函数来简化此过程。

Lambda表达式


Lambda表达式是一行中的函数,例如,这是一个将结果数平方的lambda表达式:

 square = lambda x: x * x 

然后运行:

>>> square(3)
9


我能听到你的声音。 “布兰登,争论在哪里?” 这是怎么回事? 这不像一个功能。”

是的,这可能会造成混淆,但可以解释。 在这一行中,我们为变量“ square”分配了一些东西。 这部分:

 lambda x: x * x 

告诉Python我们正在使用lambda函数,输入名为x。 冒号之后的所有内容都是输入将要发生的事情,稍后我们将自动获得结果。

以单行形式出现在我们的程序中,您需要执行以下操作:

 x = [1, 2, 3, 4, 5] print(list(map(lambda num: num * num, x))) 

因此,在lambda表达式中,参数在左侧,而对它们的操作在右侧。 这有点不整洁,没有人否认。 事实是,其中有些东西要编写这样的功能代码。 另外,将函数转换为单行也很酷。

减少


Reduce是一种将可迭代容器变成一件事的功能。 即,进行了将列表变成单个数字的计算。 看起来像这样:

 reduce(function, list) 

我们可以(并且经常会)使用lambda函数作为函数参数。

如果我们想将列表中的所有数字相乘,可以这样进行:

 product = 1 x = [1, 2, 3, 4] for num in x: product = product * num 

并与减少它看起来像这样:

 from functools import reduce product = reduce((lambda x, y: x * y),[1, 2, 3, 4]) 

结果将是相同的,但是代码更短,并且具有函数式编程的知识,可以更准确地使用它。

筛选条件


过滤器函数采用一个可迭代的容器,并根据给定规则(也是一个函数)对其进行过滤。

通常,它需要一个函数和一个列表作为输入。 稍后,它将函数应用到列表中的每个元素,如果函数返回True,则什么也没有发生,如果函数返回False,则从列表中删除该元素。

语法:

 filter(function, list) 

让我们看一个不使用过滤器的示例:

 x = range(-5, 5) new_list = [] for num in x: if num < 0: new_list.append(num) 

连同过滤器:

 x = range(-5, 5) all_less_than_zero = list(filter(lambda num: num < 0, x)) 

高阶函数


高阶函数可以将函数作为参数并返回它们。 一个简单的示例如下所示:

 def summation(nums): return sum(nums) def action(func, numbers): return func(numbers) print(action(summation, [1, 2, 3])) # Output is 6 #  6 

或更简单的例子:

 def rtnBrandon(): return "brandon" def rtnJohn(): return "john" def rtnPerson(): age = int(input("What's your age?")) if age == 21: return rtnBrandon() else: return rtnJohn() 

记得之前我曾说过,真正的函数式编程不使用变量。 高阶函数使之成为可能。 如果要通过长函数的“隧道”传递信息,则无需将变量保存在某处。

Python中的所有函数都是一流的对象。 这样定义第一类的对象,该对象对应于以下一个或多个参数:

  • 创建占空比
  • 分配给数据结构中的变量或项目
  • 作为函数参数传递
  • 由于函数执行而返回

因此,Python中的所有函数都是一流的对象,可以用作高阶函数。

部分申请


部分使用(也可以打断)有点奇怪,但是很酷。 您可以在不使用所有给定参数的情况下调用该函数。 让我们来看一个例子。 我们想要创建一个函数,该函数接受2个参数,即底数和度数,然后将底数返回给幂,如下所示:

 def power(base, exponent): return base ** exponent 

现在我们需要创建一个单独的平方函数,并使用幂函数进行计算:

 def square(base): return power(base, 2) 

它可以工作,但是如果我们想对数字求立方呢? 还是四年级? 您应该永远编写此类功能吗? 当然可以 但是程序员很懒。 如果多次重复同一件事,可能有一种方法可以更快地完成并停止重复。 在此可以使用部分应用。 让我们看一下使用部分应用的幂函数的示例:

 from functools import partial square = partial(power, exponent=2) print(square(2)) # output is 4 #  4 

是不是很酷? 我们可以调用一个仅需使用1个参数并需要2个参数的函数,然后指定第二个参数将自己运行。

您还可以使用循环来模拟幂函数,该函数将与功率高达千分之一的多维数据集一起使用。

 from functools import partial powers = [] for x in range(2, 1001): powers.append(partial(power, exponent = x)) print(powers[0](3)) # output is 9 #  9 

函数式编程与pythonic规范不匹配


您可能已经注意到,我们在函数式编程中要做的许多事情都围绕列表进行。 除了reduce函数和部分应用程序之外,您看到的所有函数都会生成列表。 Guido(Python`a的创建者)不喜欢Python`e中的功能,因为Python有自己的创建列表的方法。

如果您在控制台中编写“ import this”,您将获得:
>>> import this

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one — and preferably only one — obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let's do more of those!


这是Python Zen。 这是关于成为pythonist的含义的诗句。 我们感兴趣的部分是:

应该有一种-最好只有一种-显而易见的方法。

做某事应该只有一种-最好只有一种-显而易见的方式。

在Python中,地图和过滤器的作用与列表抽象( link )相同。 这违反了Python-Zen的规则之一,因此函数式编程的这一部分不是“ pythonic”的。

接下来要讨论的是lambda函数。 在Python中,lambda函数是常规函数。 实际上是语法糖。 这两个部分都做同样的事情:

 foo = lambda a: 2 def foo(a): return 2 

标准函数可能仍与lambda函数相同,反之亦然。 Lambda函数不能与常规函数相同。

这只是关于为什么函数式编程不太适合python意识形态的一句话。 之前,我提到了列表的抽象( 也包括列表包含 ),现在让我们谈谈它。

列表抽象


我已经说过,使用map和filter可以完成的所有事情都可以使用列表抽象来完成。 在这一部分中,我们将讨论它。

列表抽象是在Python中创建列表的一种方法。 语法:

 [function for item in iterable] 

让我们平方列表中的每个项目,例如:

 print([x * x for x in [1, 2, 3, 4]]) 

好的,我们可以看到如何将函数应用于列表的每个元素。 我们如何绕过过滤器? 看一下这段代码:

 x = range(-5, 5) all_less_than_zero = list(filter(lambda num: num < 0, x)) print(all_less_than_zero) 

现在使用列表抽象:

 x = range(-5, 5) all_less_than_zero = [num for num in x if num < 0] 

列表抽象支持这种条件表达式。 您不再需要使用一百万个函数来获取某些东西。 实际上,如果您尝试对列表进行操作,则可能更容易使用列表抽象来实现。

如果我们想对列表中小于零的每个元素求平方,该怎么办。 使用lambda函数,映射和过滤器,它将如下所示:

 x = range(-5, 5) all_less_than_zero = list(map(lambda num: num * num, list(filter(lambda num: num < 0, x)))) 

此项输入不合理,也不是很简单。 使用列表抽象,它将如下所示:

 x = range(-5, 5) all_less_than_zero = [num * num for num in x if num < 0] 

奇怪的是,列表抽象仅对列表而言是好的。 映射和筛选器可用于每个可迭代容器,所以怎么了?..是的,您可以对遇到的每个可迭代容器使用抽象。

其他抽象


您可以对每个可迭代容器应用抽象。

可以使用抽象来创建每个可迭代的容器。 从2.7版开始,您甚至可以创建字典(哈希表)。

如果某物是一个可迭代的容器,则可以生成某物。 让我们看一下使用set的最后一个示例。 如果您不知道该设置什么,那么也请参阅我撰写的这篇文章 。 简而言之:

  • Set是元素的容器;其中的元素不会重复
  • 顺序并不重要

 # taken from page 87, chapter 3 of Fluent Python by Luciano Ramalho #    Fluent Python, . 87, . 3 >>> from unicodedata import name >>> {chr(i) for i in range(32, 256) if 'SIGN' in name(chr(i), '')} {'×', '¥', '°', '£', '', '#', '¬', '%', 'µ', '>', '¤', '±', '¶', '§', '<', '=', '', '$', '÷', '¢', '+'} 

您可能已经注意到,set像字典一样使用花括号。 Python真的很聪明。 他会根据是否为字典指定其他参数来猜测您是使用字典抽象还是集合抽象。 如果您想了解更多有关抽象的知识,请阅读。 如果是关于抽象和生成的,那么这个

总结


函数式编程很棒。 功能代码可以是干净的,也可以不是很干净。 一些顽固的python主义者不接受Python中的功能范式。 您必须使用自己想要的和适合自己的东西。

作者页面

Source: https://habr.com/ru/post/zh-CN456814/


All Articles