用Python进行基础的函数式编程的教程,python函数

日期:2020-05-07编辑作者:Web前端

时间: 2019-09-07阅读: 169标签: 编程:apple:译序

用Python进行基础的函数式编程的教程,python函数

许多函数式文章讲述的是组合,流水线和高阶函数这样的抽象函数式技术。本文不同,它展示了人们每天编写的命令式,非函数式代码示例,以及将这些示例转换为函数式风格。

文章的第一部分将一些短小的数据转换循环重写成函数式的maps和reduces。第二部分选取长一点的循环,把他们分解成单元,然后把每个单元改成函数式的。第三部分选取一个很长的连续数据转换循环,然后把它分解成函数式流水线。

示例都是用Python写的,因为很多人觉得Python易读。为了证明函数式技术对许多语言来说都相同,许多示例避免使用Python特有的语法:map,reduce,pipeline。
导引

当人们谈论函数式编程,他们会提到非常多的“函数式”特性。提到不可变数据1,第一类对象2以及尾调用优化3。这些是帮助函数式编程的语言特征。提到mapping(映射),reducing(归纳),piplining(管道),recursing(递归),currying4(科里化);以及高阶函数的使用。这些是用来写函数式代码的编程技术。提到并行5,惰性计算6以及确定性。这些是有利于函数式编程的属性。

忽略全部这些。可以用一句话来描述函数式代码的特征:避免副作用。它不会依赖也不会改变当前函数以外的数据。所有其他的“函数式”的东西都源于此。当你学习时把它当做指引。

这是一个非函数式方法:

a = 0
def increment1():
  global a
  a += 1

这是一个函数式的方法:

def increment2(a):
  return a + 1

不要在lists上迭代。使用map和reduce。 Map(映射)

Map接受一个方法和一个集合作为参数。它创建一个新的空集合,以每一个集合中的元素作为参数调用这个传入的方法,然后把返回值插入到新创建的集合中。最后返回那个新集合。

这是一个简单的map,接受一个存放名字的list,并且返回一个存放名字长度的list:

name_lengths = map(len, ["Mary", "Isla", "Sam"])

print name_lengths
# => [4, 4, 3]

接下来这个map将传入的collection中每个元素都做平方操作:

squares = map(lambda x: x * x, [0, 1, 2, 3, 4])

print squares
# => [0, 1, 4, 9, 16]

这个map并没有使用一个命名的方法。它是使用了一个匿名并且内联的用lambda定义的方法。lambda的参数定义在冒号左边。方法主体定义在冒号右边。返回值是方法体运行的结果。

下面的非函数式代码接受一个真名列表,然后用随机指定的代号来替换真名。

import random

names = ['Mary', 'Isla', 'Sam']
code_names = ['Mr. Pink', 'Mr. Orange', 'Mr. Blonde']

for i in range(len(names)):
  names[i] = random.choice(code_names)

print names
# => ['Mr. Blonde', 'Mr. Blonde', 'Mr. Blonde']

(正如你所见的,这个算法可能会给多个密探同一个秘密代号。希望不会在任务中混淆。)

这个可以用map重写:

import random

names = ['Mary', 'Isla', 'Sam']

secret_names = map(lambda x: random.choice(['Mr. Pink',
                      'Mr. Orange',
                      'Mr. Blonde']),
          names)

练习1.尝试用map重写下面的代码。它接受由真名组成的list作为参数,然后用一个更加稳定的策略产生一个代号来替换这些名字。

names = ['Mary', 'Isla', 'Sam']

for i in range(len(names)):
  names[i] = hash(names[i])

print names
# => [6306819796133686941, 8135353348168144921, -1228887169324443034]

(希望密探记忆力够好,不要在执行任务时把代号忘记了。)

我的解决方案:

names = ['Mary', 'Isla', 'Sam']

secret_names = map(hash, names)

Reduce(迭代)

Reduce 接受一个方法和一个集合做参数。返回通过这个方法迭代容器中所有元素产生的结果。

这是个简单的reduce。返回集合中所有元素的和。

sum = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4])

print sum
# => 10

x是迭代的当前元素。a是累加和也就是在之前的元素上执行lambda返回的值。reduce()遍历元素。每次迭代,在当前的a和x上执行lambda然后返回结果作为下一次迭代的a。

第一次迭代的a是什么?在这之前没有迭代结果传进来。reduce() 使用集合中的第一个元素作为第一次迭代的a,然后从第二个元素开始迭代。也就是说,第一个x是第二个元素。

这段代码记'Sam'这个词在字符串列表中出现的频率:

sentences = ['Mary read a story to Sam and Isla.',
       'Isla cuddled Sam.',
       'Sam chortled.']

sam_count = 0
for sentence in sentences:
  sam_count += sentence.count('Sam')

print sam_count
# => 3

下面这个是用reduce写的:

sentences = ['Mary read a story to Sam and Isla.',
       'Isla cuddled Sam.',
       'Sam chortled.']

sam_count = reduce(lambda a, x: a + x.count('Sam'),
          sentences,
          0)

这段代码如何初始化a?出现‘Sam'的起始点不能是'Mary read a story to Sam and Isla.' 初始的累加和由第三个参数来指定。这样就允许了集合中元素的类型可以与累加器不同。
为什么map和reduce更好?

首先,它们大多是一行代码。

二、迭代中最重要的部分:集合,操作和返回值,在所有的map和reduce中总是在相同的位置。

三、循环中的代码可能会改变之前定义的变量或之后要用到的变量。照例,map和reduce是函数式的。

四、map和reduce是元素操作。每次有人读到for循环,他们都要逐行读懂逻辑。几乎没有什么规律性的结构可以帮助理解代码。相反,map和reduce都是创建代码块来组织复杂的算法,并且读者也能非常快的理解元素并在脑海中抽象出来。“嗯,代码在转换集合中的每一个元素。然后结合处理的数据成一个输出。”

五、map和reduce有许多提供便利的“好朋友”,它们是基本行为的修订版。例如filter,all,any以及find。

练习2。尝试用map,reduce和filter重写下面的代码。Filter接受一个方法和一个集合。返回集合中使方法返回true的元素。

people = [{'name': 'Mary', 'height': 160},
     {'name': 'Isla', 'height': 80},
     {'name': 'Sam'}]

height_total = 0
height_count = 0
for person in people:
  if 'height' in person:
    height_total += person['height']
    height_count += 1

if height_count > 0:
  average_height = height_total / height_count

  print average_height
  # => 120

如果这个比较棘手,试着不要考虑数据上的操作。考虑下数据要经过的状态,从people字典列表到平均高度。不要尝试把多个转换捆绑在一起。把每一个放在独立的一行,并且把结果保存在命名良好的变量中。代码可以运行后,立刻凝练。

我的方案:

people = [{'name': 'Mary', 'height': 160},
     {'name': 'Isla', 'height': 80},
     {'name': 'Sam'}]

heights = map(lambda x: x['height'],
       filter(lambda x: 'height' in x, people))

if len(heights) > 0:
  from operator import add
  average_height = reduce(add, heights) / len(heights)

写声明式代码,而不是命令式

下面的程序演示三辆车比赛。每次移动时间,每辆车可能移动或者不动。每次移动时间程序会打印到目前为止所有车的路径。五次后,比赛结束。

下面是某一次的输出:

-
--
--

--
--
---

---
--
---

----
---
----

----
----
-----

这是程序:  

from random import random

time = 5
car_positions = [1, 1, 1]

while time:
  # decrease time
  time -= 1

  print ''
  for i in range(len(car_positions)):
    # move car
    if random() > 0.3:
      car_positions[i] += 1

    # draw car
    print '-' * car_positions[i]

代码是命令式的。一个函数式的版本应该是声明式的。应该描述要做什么,而不是怎么做。
使用方法

通过绑定代码片段到方法里,可以使程序更有声明式的味道。  

from random import random

def move_cars():
  for i, _ in enumerate(car_positions):
    if random() > 0.3:
      car_positions[i] += 1

def draw_car(car_position):
  print '-' * car_position

def run_step_of_race():
  global time
  time -= 1
  move_cars()

def draw():
  print ''
  for car_position in car_positions:
    draw_car(car_position)

time = 5
car_positions = [1, 1, 1]

while time:
  run_step_of_race()
  draw()

想要理解这段代码,读者只需要看主循环。”如果time不为0,运行下run_step_of_race和draw,在检查下time。“如果读者想更多的理解这段代码中的run_step_of_race或draw,可以读方法里的代码。

注释没有了。代码是自描述的。

把代码分解提炼进方法里是非常好且十分简单的提高代码可读性的方法。

这个技术用到了方法,但是只是当做常规的子方法使用,只是简单地将代码打包。根据指导,这些代码不是函数式的。代码中的方法使用了状态,而不是传入参数。方法通过改变外部变量影响了附近的代码,而不是通过返回值。为了搞清楚方法做了什么,读者必须仔细阅读每行。如果发现一个外部变量,必须找他它的出处,找到有哪些方法修改了它。
移除状态

下面是函数式的版本:  

from random import random

def move_cars(car_positions):
  return map(lambda x: x + 1 if random() > 0.3 else x,
        car_positions)

def output_car(car_position):
  return '-' * car_position

def run_step_of_race(state):
  return {'time': state['time'] - 1,
      'car_positions': move_cars(state['car_positions'])}

def draw(state):
  print ''
  print 'n'.join(map(output_car, state['car_positions']))

def race(state):
  draw(state)
  if state['time']:
    race(run_step_of_race(state))

race({'time': 5,
   'car_positions': [1, 1, 1]})

代码仍然是分割提炼进方法中,但是这个方法是函数式的。函数式方法有三个标志。首先,没有共享变量。time和car_positions直接传进方法race中。第二,方法接受参数。第三,方法里没有实例化变量。所有的数据变化都在返回值中完成。rece() 使用run_step_of_race() 的结果进行递归。每次一个步骤会产生一个状态,这个状态会直接传进下一步中。

现在,有两个方法,zero() 和 one():  

def zero(s):
  if s[0] == "0":
    return s[1:]

def one(s):
  if s[0] == "1":
    return s[1:]

zero() 接受一个字符串 s 作为参数,如果第一个字符是'0′ ,方法返回字符串的其他部分。如果不是,返回None,Python的默认返回值。one() 做的事情相同,除了第一个字符要求是'1′。

想象下一个叫做rule_sequence()的方法。接受一个string和一个用于存放zero()和one()模式的规则方法的list。在string上调用第一个规则。除非返回None,不然它会继续接受返回值并且在string上调用第二个规则。除非返回None,不然它会接受返回值,并且调用第三个规则。等等。如果有哪一个规则返回None,rule_sequence()方法停止,并返回None。不然,返回最后一个规则方法的返回值。

下面是一个示例输出:  

print rule_sequence('0101', [zero, one, zero])
# => 1

print rule_sequence('0101', [zero, zero])
# => None

This is the imperative version of rule_sequence():
这是一个命令式的版本:  

def rule_sequence(s, rules):
  for rule in rules:
    s = rule(s)
    if s == None:
      break

  return s

练习3。上面的代码用循环来完成功能。用递归重写使它更有声明式的味道。

我的方案:  

def rule_sequence(s, rules):
  if s == None or not rules:
    return s
  else:
    return rule_sequence(rules[0](s), rules[1:])

使用流水线

在之前的章节,一些命令式的循环被重写成递归的形式,并被用以调用辅助方法。在本节中,会用pipline技术重写另一种类型的命令式循环。

下面有个存放三个子典型数据的list,每个字典存放一个乐队相关的三个键值对:姓名,不准确的国籍和激活状态。format_bands方法循环处理这个list。  

bands = [{'name': 'sunset rubdown', 'country': 'UK', 'active': False},
     {'name': 'women', 'country': 'Germany', 'active': False},
     {'name': 'a silver mt. zion', 'country': 'Spain', 'active': True}]

def format_bands(bands):
  for band in bands:
    band['country'] = 'Canada'
    band['name'] = band['name'].replace('.', '')
    band['name'] = band['name'].title()

format_bands(bands)

print bands
# => [{'name': 'Sunset Rubdown', 'active': False, 'country': 'Canada'},
#   {'name': 'Women', 'active': False, 'country': 'Canada' },
#   {'name': 'A Silver Mt Zion', 'active': True, 'country': 'Canada'}]

担心源于方法的名称。”format” 是一个很模糊的词。仔细查看代码,这些担心就变成抓狂了。循环中做三件事。键值为'country'的值被设置为'Canada'。名称中的标点符号被移除了。名称首字母改成了大写。但是很难看出这段代码的目的是什么,是否做了它看上去所做的。并且代码难以重用,难以测试和并行。

和下面这段代码比较一下:

print pipeline_each(bands, [set_canada_as_country,
              strip_punctuation_from_name,
              capitalize_names])

这段代码很容易理解。它去除了副作用,辅助方法是函数式的,因为它们看上去是链在一起的。上次的输出构成下个方法的输入。如果这些方法是函数式的,那么就很容易核实。它们很容易重用,测试并且也很容易并行。

pipeline_each()的工作是传递bands,一次传一个,传到如set_cannada_as_country()这样的转换方法中。当所有的bands都调用过这个方法之后,pipeline_each()将转换后的bands收集起来。然后再依次传入下一个方法中。

我们来看看转换方法。  

def assoc(_d, key, value):
  from copy import deepcopy
  d = deepcopy(_d)
  d[key] = value
  return d

def set_canada_as_country(band):
  return assoc(band, 'country', "Canada")

def strip_punctuation_from_name(band):
  return assoc(band, 'name', band['name'].replace('.', ''))

def capitalize_names(band):
  return assoc(band, 'name', band['name'].title())

每一个都将band的一个key联系到一个新的value上。在不改变原值的情况下是很难做到的。assoc()通过使用deepcopy()根据传入的dictionary产生一个拷贝来解决这个问题。每个转换方法修改这个拷贝,然后将这个拷贝返回。

似乎这样就很好了。原始Band字典不再担心因为某个键值需要关联新的值而被改变。但是上面的代码有两个潜在的副作用。在方法strip_punctuation_from_name()中,未加标点的名称是通过在原值上调用replace()方法产生的。在capitalize_names()方法中,将名称的首字母大写是通过在原值上调用title()产生的。如果replace()和title()不是函数式的,strip_punctuation_from_name()和capitalize_names()也就不是函数式的。

幸运的是,replace() 和 title()并不改变它们所操作的string。因为Python中的strings是不可变的。例如,当replace()操作band的名称字符串时,是先拷贝原字符串,然后对拷贝的字符串做修改。啧啧。

Python中string和dictionaries的可变性比较阐述了类似Clojure这类语言的吸引力。程序员永远不用担心数据是否可变。数据是不可变的。

练习4。试着重写pipeline_each方法。考虑操作的顺序。每次从数组中拿出一个bands传给第一个转换方法。然后类似的再传给第二个方法。等等。

My solution:
我的方案:  

def pipeline_each(data, fns):
  return reduce(lambda a, x: map(x, a),
         fns,
         data)

所有的三个转换方法归结于对传入的band的特定字段进行更改。call()可以用来抽取这个功能。call接受一个方法做参数来调用,以及一个值的键用来当这个方法的参数。  

set_canada_as_country = call(lambda x: 'Canada', 'country')
strip_punctuation_from_name = call(lambda x: x.replace('.', ''), 'name')
capitalize_names = call(str.title, 'name')

print pipeline_each(bands, [set_canada_as_country,
              strip_punctuation_from_name,
              capitalize_names])

或者,如果我们希望能满足简洁方面的可读性,那么就:  

print pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
call(lambda x: x.replace('.', ''), 'name'),
call(str.title, 'name')])

call()的代码:  

def assoc(_d, key, value):
  from copy import deepcopy
  d = deepcopy(_d)
  d[key] = value
  return d

def call(fn, key):
  def apply_fn(record):
    return assoc(record, key, fn(record.get(key)))
  return apply_fn

There is a lot going on here. Let's take it piece by piece.

这段代码做了很多事。让我们一点一点的看。

一、call() 是一个高阶函数。高阶函数接受一个函数作为参数,或者返回一个函数。或者像call(),两者都有。

二、apply_fn() 看起来很像那三个转换函数。它接受一个record(一个band),查找在record[key]位置的值,以这个值为参数调用fn,指定fn的结果返回到record的拷贝中,然后返回这个拷贝。

三、call() 没有做任何实际的工作。当call被调用时,apply_fn()会做实际的工作。上面使用pipeline_each()的例子中,一个apply_fn()的实例会将传入的band的country值改为”Canada“。另一个实例会将传入的band的名称首字母大写。

四、当一个apply_fn() 实例运行时,fn和key将不再作用域中。它们既不是apply_fn()的参数,也不是其中的本地变量。但是它们仍然可以被访问。当一个方法被定义时,方法会保存方法所包含的变量的引用:那些定义在方法的作用域外,却在方法中使用的变量。当方法运行并且代码引用一个变量时,Python会查找本地和参数中的变量。如果没找到,就会去找闭包内保存的变量。那就是找到fn和key的地方。

五、在call()代码中没有提到bands。因为不管主题是什么,call()都可以为任何程序生成pipeline。函数式编程部分目的就是构建一个通用,可重用,可组合的函数库。

干的漂亮。闭包,高阶函数和变量作用域都被包含在段落里。喝杯柠檬水。

还需要在band上做一点处理。就是移除band上除了name和country之外的东西。extract_name_and_country()能拉去这样的信息。  

def extract_name_and_country(band):
  plucked_band = {}
  plucked_band['name'] = band['name']
  plucked_band['country'] = band['country']
  return plucked_band

print pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
              call(lambda x: x.replace('.', ''), 'name'),
              call(str.title, 'name'),
              extract_name_and_country])

# => [{'name': 'Sunset Rubdown', 'country': 'Canada'},
#   {'name': 'Women', 'country': 'Canada'},
#   {'name': 'A Silver Mt Zion', 'country': 'Canada'}]

extract_name_and_country() 可以写成叫做pluck()的通用函数。pluck()可以这样使用:  

print pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
              call(lambda x: x.replace('.', ''), 'name'),
              call(str.title, 'name'),
              pluck(['name', 'country'])])

练习5。pluck()接受一系列的键值,根据这些键值去record中抽取数据。试着写写。需要用到高阶函数。

我的方案:  

def pluck(keys):
  def pluck_fn(record):
    return reduce(lambda a, x: assoc(a, x, record[x]),
           keys,
           {})
  return pluck_fn

What now?
还有什么要做的吗?

函数式代码可以很好的和其他风格的代码配合使用。文章中的转换器可以用任何语言实现。试试用你的代码实现它。

想想Mary,Isla 和 Sam。将对list的迭代,转成maps和reduces操作吧。

想想汽车竞赛。将代码分解成方法。把那些方法改成函数式的。把循环处理转成递归。

想想乐队。将一系列的操作改写成pipeline。

标注:

1、一块不可变数据是指不能被改变的数据。一些语言像Clojure的语言,默认所有的值都是不可变的。任何的可变操作都是拷贝值,并对拷贝的值做修改并返回。这样就消除了程序中对未完成状态访问所造成的bugs。

2、支持一等函数的语言允许像处理其他类型的值那样处理函数。意味着方法可以被创建,传给其他方法,从方法中返回以及存储在其他数据结构里。

3、尾调用优化是一个编程语言特性。每次方法递归,会创建一个栈。栈用来存储当前方法需要使用的参数和本地值。如果一个方法递归次数非常多,很可能会让编译器或解释器消耗掉所有的内存。有尾调用优化的语言会通过重用同一个栈来支持整个递归调用的序列。像Python这样的语言不支持尾调用优化的通常都限制方法递归的数量在千次级别。在race()方法中,只有5次,所以很安全。

4、Currying意即分解一个接受多个参数的方法成一个只接受第一个参数并且返回一个接受下一个参数的方法的方法,直到接受完所有参数。

5、并行意即在不同步的情况下同时运行同一段代码。这些并发操作常常运行在不同的处理器上。

6、惰性计算是编译器的技术,为了避免在需要结果之前就运行代码。

7、只有当每次重复都能得出相同的结果,才能说处理是确定性的。

http://www.bkjia.com/Pythonjc/976749.htmlwww.bkjia.comtruehttp://www.bkjia.com/Pythonjc/976749.htmlTechArticle用Python进行基础的函数式编程的教程,python函数 许多函数式文章讲述的是组合,流水线和高阶函数这样的抽象函数式技术。本文不同,它展...

本文是一篇手把手的函数式编程入门介绍,借助代码示例讲解细腻。但又不乏洞见,第一节中列举和点评了函数式种种让眼花缭乱的特质,给出了『理解函数式特质的指南针:函数式代码的核心特质就一条,无副作用』,相信这个指南针对于有积极学过挖过函数式的同学看来更是有相知恨晚的感觉。

希望看了这篇文章之后,能在学习和使用函数式编程的旅途中不迷路哦,兄die~

PS:本人是在《Functional Programming, Simplified(Scala edition)》这本书了解到这篇文章。这本书由浅入深循序渐进地对FP做了体系讲解,力荐!书的豆瓣链接。

手把手介绍函数式编程:从命令式重构到函数式

有很多函数式编程文章讲解了抽象的函数式技术,也就是组合(composition)、管道(pipelining)、高阶函数(higher order function)。本文希望以另辟蹊径的方式来讲解函数式:首先展示我们平常编写的命令式而非函数式的代码示例,然后将这些示例重构成函数式风格。

本文的第一部分选用了简短的数据转换循环,将它们重构成函数式的map和reduce。第二部分则对更长的循环代码,将它们分解成多个单元,然后重构各个单元成函数式的。第三部分选用的是有一系列连续的数据转换循环代码,将其拆解成为一个函数式管道(functional pipeline)。

示例代码用的是Python语言,因为多数人都觉得Python易于阅读。示例代码避免使用Python范的(pythonic)代码,以便展示出各个语言通用的函数式技术:map、reduce和管道。所有示例都用的是Python 2。

理解函数式特质的指南针不要迭代列表,使用map和reduce声明方式编写代码,而非命令式现在开始我们可以做什么?理解函数式特质的指南针

当人们谈论函数式编程时,提到了多到令人迷路的『函数式』特质(characteristics):

人们会提到不可变数据(immutable data)、一等公民的函数(first class function)和尾调用优化(tail call optimisation)。这些是有助于函数式编程的语言特性。人们也会提到map、reduce、管道、递归(recursing)、柯里化(currying)以及高阶函数的使用。这些是用于编写函数式代码的编程技术。人们还会提到并行化(parallelization)、惰性求值(lazy evaluation)和确定性(determinism)。这些是函数式程序的优点

无视这一切。函数式代码的核心特质就一条:无副作用(side effect)。即代码逻辑不依赖于当前函数之外的数据,并且也不会更改当前函数之外的数据。所有其他的『函数式』特质都可以从这一条派生出来。在你学习过程中,请以此作为指南针。不要再迷路哦,兄die~

这是一个非函数式的函数:

a = 0def increment(): global a a += 1

而这是一个函数式的函数:

def increment(a): return a + 1

不要迭代列表,使用map和reducemap

map输入一个函数和一个集合,创建一个新的空集合,在原来集合的每个元素上运行该函数,并将各个返回值插入到新集合中,然后返回新的集合。

这是一个简单的map,它接受一个名字列表并返回这些名字的长度列表:

name_lengths = map(len, ["Mary", "Isla", "Sam"])print name_lengths# = [4, 4, 3]

这是一个map,对传递的集合中的每个数字进行平方:

squares = map(lambda x: x * x, [0, 1, 2, 3, 4])print squares# = [0, 1, 4, 9, 16]

这个map没有输入命名函数,而是一个匿名的内联函数,用lambda关键字来定义。lambda的参数定义在冒号的左侧。函数体定义在冒号的右侧。(隐式)返回的是函数体的运行结果。

下面的非函数式代码输入一个真实名字的列表,替换成随机分配的代号。

import randomnames = ['Mary', 'Isla', 'Sam']code_names = ['Mr. Pink', 'Mr. Orange', 'Mr. Blonde']for i in range(len(names)): names[i] = random.choice(code_names)print names# = ['Mr. Blonde', 'Mr. Blonde', 'Mr. Blonde']

(如你所见,这个算法可能会为多个秘密特工分配相同的秘密代号,希望这不会因此导致混淆了秘密任务。)

可以用map重写成:

import randomnames = ['Mary', 'Isla', 'Sam']secret_names = map(lambda x: random.choice(['Mr. Pink','Mr. Orange','Mr. Blonde']),names)

练习1:尝试将下面的代码重写为map,输入一个真实名字列表,替换成用更可靠策略生成的代号。

names = ['Mary', 'Isla', 'Sam']for i in range(len(names)): names[i] = hash(names[i])print names# = [6306819796133686941, 8135353348168144921, -1228887169324443034]

(希望特工会留下美好的回忆,在秘密任务期间能记得住搭档的秘密代号。)

我的实现方案:

names = ['Mary', 'Isla', 'Sam']secret_names = map(hash, names)

reduce

reduce输入一个函数和一个集合,返回通过合并集合元素所创建的值。

这是一个简单的reduce,返回集合所有元素的总和。

sum = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4])print sum# = 10

x是迭代的当前元素。a是累加器(accumulator),它是在前一个元素上执行lambda的返回值。reduce()遍历所有集合元素。对于每一个元素,运行以当前的a和x为参数运行lambda,返回结果作为下一次迭代的a。

在第一次迭代时,a是什么值?并没有前一个的迭代结果可以传递。reduce()使用集合中的第一个元素作为第一次迭代中的a值,从集合的第二个元素开始迭代。也就是说,第一个x是集合的第二个元素。

下面的代码计算单词'Sam'在字符串列表中出现的次数:

sentences = ['Mary read a story to Sam and Isla.', 'Isla cuddled Sam.', 'Sam chortled.']sam_count = 0for sentence in sentences: sam_count += sentence.count('Sam')print sam_count# = 3

这与下面使用reduce的代码相同:

sentences = ['Mary read a story to Sam and Isla.', 'Isla cuddled Sam.','Sam chortled.']sam_count = reduce(lambda a, x: a + x.count('Sam'),sentences,0)

这段代码是如何产生初始的a值?'Sam'出现次数的初始值不能是'Mary read a story to Sam and Isla.'。初始累加器用reduce()的第三个参数指定。这样就允许使用与集合元素不同类型的值。

为什么map和reduce更好?

这样的做法通常会是一行简洁的代码。迭代的重要部分 —— 集合、操作和返回值 —— 以map和reduce方式总是在相同的位置。循环中的代码可能会影响在它之前定义的变量或在它之后运行的代码。按照约定,map和reduce都是函数式的。map和reduce是基本原子操作。阅读for循环时,必须一行一行地才能理解整体逻辑。往往没有什么规则能保证以一个固定结构来明确代码的表义。相比之下,map和reduce则是一目了然表现出了可以组合出复杂算法的构建块(building block)及其相关的元素,代码阅读者可以迅速理解并抓住整体脉络。『哦~这段代码正在转换每个集合元素;丢弃了一些转换结果;然后将剩下的元素合并成单个输出结果。』map和reduce有很多朋友,提供有用的、对基本行为微整的版本。比如:filter、all、any和find。

练习2:尝试使用map、reduce和filter重写下面的代码。filter需要一个函数和一个集合,返回结果是函数返回True的所有集合元素。

people = [{'name': 'Mary', 'height': 160}, {'name': 'Isla', 'height': 80}, {'name': 'Sam'}]height_total = 0height_count = 0for person in people: if 'height' in person: height_total += person['height'] height_count += 1if height_count  0: average_height = height_total / height_count print average_height # = 120

如果上面这段代码看起来有些烧脑,我们试试不以在数据上操作为中心的思考方式。而是想一想数据所经历的状态:从人字典的列表转换成平均身高。不要将多个转换混在一起。每个转换放在一个单独的行上,并将结果分配一个有描述性命名的变量。代码工作之后,再合并缩减代码。

我的实现方案:

people = [{'name': 'Mary', 'height': 160}, {'name': 'Isla', 'height': 80}, {'name': 'Sam'}]heights = map(lambda x: x['height'], filter(lambda x: 'height' in x, people))if len(heights)  0: from operator import add average_height = reduce(add, heights) / len(heights)

声明方式编写代码,而非命令式

下面的程序演示三辆赛车的比赛。每过一段时间,赛车可能向前跑了,也可能抛锚而原地不动。在每个时间段,程序打印出目前为止的赛车路径。五个时间段后比赛结束。

这是个示例输出:

--------------------------------------------

这是程序实现:

from random import randomtime = 5car_positions = [1, 1, 1]while time: # decrease time time -= 1 print '' for i in range(len(car_positions)): # move car if random()  0.3: car_positions[i] += 1 # draw car print '-' * car_positions[i]

这份代码是命令式的。函数式版本则是声明性的,描述要做什么,而不是如何做。

使用函数

通过将代码片段打包到函数中,程序可以更具声明性。

from random import randomdef move_cars(): for i, _ in enumerate(car_positions): if random()  0.3: car_positions[i] += 1def draw_car(car_position): print '-' * car_positiondef run_step_of_race(): global time time -= 1 move_cars()def draw(): print '' for car_position in car_positions: draw_car(car_position)time = 5car_positions = [1, 1, 1]while time: run_step_of_race() draw()

要理解这个程序,读者只需读一下主循环。『如果还剩下时间,请跑一步,然后画出线图。再次检查时间。』如果读者想要了解更多关于比赛步骤或画图的含义,可以阅读对应函数的代码。

没什么要再说明的了。代码是自描述的。

拆分代码成函数是一种很好的、简单易行的方法,能使代码更具可读性。

本文由www.129028.com金沙发布于Web前端,转载请注明出处:用Python进行基础的函数式编程的教程,python函数

关键词:

HTTPS为什么是安全的?【www.129028.com金沙】

时间: 2019-09-07阅读: 202标签: 安全 二、HTTP与HTTPS的区别 1、https协议需要到ca申请证书 2、http是吵文本传输协议,信息是...

详细>>

CSRF跨站请求伪造www.129028.com金沙

时间: 2019-09-08阅读: 827标签: 属性 时间: 2019-10-26阅读: 92标签: csrfCSRF 介绍 Chrome 51 开始,浏览器的 Cookie 新增加了一个...

详细>>

为什么云端会不断泄漏数据?

服务提供商的局限性 1.明了你要负责什么 然而,云服务提供商仍然使用自己的密钥管理解决方案,这就使情况进一步...

详细>>

自己动手实现一个axioswww.129028.com金沙:

有很多同学看了本系列的前几篇之后建议我暂时先不用TS,于是小肆之后将把TS换成JS继续下面的文章。今天给大家带...

详细>>