用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、只有当每次重复都能得出相同的结果,才能说处理是确定性的。

(0)

相关推荐

  • 利用Fn.py库在Python中进行函数式编程

    尽管Python事实上并不是一门纯函数式编程语言,但它本身是一门多范型语言,并给了你足够的自由利用函数式编程的便利.函数式风格有着各种理论与实际上的好处(你可以在Python的文档中找到这个列表): 形式上可证 模块性 组合性 易于调试及测试 虽然这份列表已经描述得够清楚了,但我还是很喜欢Michael O.Church在他的文章"函数式程序极少腐坏(Functional programs rarely rot)"中对函数式编程的优点所作的描述.我在PyCon UA 2012期间的讲座

  • Python函数式编程指南(四):生成器详解

    4. 生成器(generator) 4.1. 生成器简介 首先请确信,生成器就是一种迭代器.生成器拥有next方法并且行为与迭代器完全相同,这意味着生成器也可以用于Python的for循环中.另外,对于生成器的特殊语法支持使得编写一个生成器比自定义一个常规的迭代器要简单不少,所以生成器也是最常用到的特性之一. 从Python 2.5开始,[PEP 342:通过增强生成器实现协同程序]的实现为生成器加入了更多的特性,这意味着生成器还可以完成更多的工作.这部分我们会在稍后的部分介绍. 4.2. 生成

  • Python函数式编程指南(二):从函数开始

    2. 从函数开始 2.1. 定义一个函数 如下定义了一个求和函数: 复制代码 代码如下: def add(x, y):     return x + y 关于参数和返回值的语法细节可以参考其他文档,这里就略过了. 使用lambda可以定义简单的单行匿名函数.lambda的语法是: 复制代码 代码如下: lambda args: expression 参数(args)的语法与普通函数一样,同时表达式(expression)的值就是匿名函数调用的返回值:而lambda表达式返回这个匿名函数.如果我们

  • Python函数式编程指南(三):迭代器详解

    3. 迭代器 3.1. 迭代器(Iterator)概述 迭代器是访问集合内元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束. 迭代器不能回退,只能往前进行迭代.这并不是什么很大的缺点,因为人们几乎不需要在迭代途中进行回退操作. 迭代器也不是线程安全的,在多线程环境中对可变集合使用迭代器是一个危险的操作.但如果小心谨慎,或者干脆贯彻函数式思想坚持使用不可变的集合,那这也不是什么大问题. 对于原生支持随机访问的数据结构(如tuple.list),迭代器和经典fo

  • Python函数式编程指南(一):函数式编程概述

    1. 函数式编程概述 1.1. 什么是函数式编程? 函数式编程使用一系列的函数解决问题.函数仅接受输入并产生输出,不包含任何能影响产生输出的内部状态.任何情况下,使用相同的参数调用函数始终能产生同样的结果. 在一个函数式的程序中,输入的数据"流过"一系列的函数,每一个函数根据它的输入产生输出.函数式风格避免编写有"边界效应"(side effects)的函数:修改内部状态,或者是其他无法反应在输出上的变化.完全没有边界效应的函数被称为"纯函数式的"

  • Python装饰器的函数式编程详解

    Python的装饰器的英文名叫Decorator,当你看到这个英文名的时候,你可能会把其跟Design Pattern里的Decorator搞混了,其实这是完全不同的两个东西.虽然好像,他们要干的事都很相似--都是想要对一个已有的模块做一些"修饰工作",所谓修饰工作就是想给现有的模块加上一些小装饰(一些小功能,这些小功能可能好多模块都会用到),但又不让这个小装饰(小功能)侵入到原有的模块中的代码里去.但是OO的Decorator简直就是一场恶梦,不信你就去看看wikipedia上的词条

  • Python函数式编程

    主要内容 1.函数基本语法及特性 2.参数与局部变 3.返回值 4.递归 5.名函数 6.函数式编程介绍 7.阶函数 8.内置函数 函数基本语法及特性 定义 数学函数定义:一般的,在一个变化过程中,如果有两个变量x和y,并且对于x的每一 个确定的值,y都有唯一确定的值与其对应,那么我们就把x称为自变量,把y称为因变 量,y是x的函数.自变量x的取值范围叫做这个函数的定义域. 但编程中的「函数」概念,与数学中的函数是有很 同的 函数是逻辑结构化和过程化的一种编程方法 函数的优点 减少重复代码 使程

  • 实例讲解python函数式编程

    函数式编程是使用一系列函数去解决问题,按照一般编程思维,面对问题时我们的思考方式是"怎么干",而函数函数式编程的思考方式是我要"干什么". 至于函数式编程的特点暂不总结,我们直接拿例子来体会什么是函数式编程. lambda表达式(匿名函数): 普通函数与匿名函数的定义方式: 复制代码 代码如下: #普通函数def add(a,b):    return a + b print add(2,3) #匿名函数add = lambda a,b : a + bprint a

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

    许多函数式文章讲述的是组合,流水线和高阶函数这样的抽象函数式技术.本文不同,它展示了人们每天编写的命令式,非函数式代码示例,以及将这些示例转换为函数式风格. 文章的第一部分将一些短小的数据转换循环重写成函数式的maps和reduces.第二部分选取长一点的循环,把他们分解成单元,然后把每个单元改成函数式的.第三部分选取一个很长的连续数据转换循环,然后把它分解成函数式流水线. 示例都是用Python写的,因为很多人觉得Python易读.为了证明函数式技术对许多语言来说都相同,许多示例避免使用Pyt

  • ​​​​​​​Python 入门学习之函数式编程

    目录 前言 把函数作为对象 把对象作为函数 数据结构内的函数 把函数作为参数和返回值 嵌套函数 单表达式函数(Lambda 表达式) Map.Filter 和 Reduce Map Filter Reduce 前言 本文对 Python 中的函数式编程技术进行了简单的入门介绍. 在 Python 中,函数是「头等公民」(first-class).也就是说,函数与其他数据类型(如 int)处于平等地位. 因而,我们可以将函数赋值给变量,也可以将其作为参数传入其他函数,将它们存储在其他数据结构(如

  • Python中基础的socket编程实战攻略

    在网络通信中socket几乎无处不在,它可以看成是应用层与TCP/IP协议簇通信的中间软件抽象层,是两个应用程序彼此进行通信的接口,并且把复杂的TCP/IP协议细节隐藏在接口之后.Python提供了socket模块,可以非常方便的进行socket编程. 创建一个server socket 使用socket方法创建一个新的socket,通常提供两个参数,第一个参数是address family, 第二个是socket type. #create an INET, STREAMing socket

  • 用Python生成器实现微线程编程的教程

    微线程领域(至少在 Python 中)一直都是 Stackless Python 才能涉及的特殊增强部分.关于 Stackless 的话题以及最近它经历的变化,可能本身就值得开辟一个专栏了.但其中简单的道理就是,在"新的 Stackless"下,延续(continuation)显然是不合时宜的,但微线程还是这个项目 存在的理由.这一点很复杂-- 刚开始,我们还是先来回顾一些内容.那么,什么是微线程呢? 微线程基本上可以说是只需要很少的内部资源就可以运行的进程 ― 并且是在 Python

  • 在Python下进行UDP网络编程的教程

    TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据.相对TCP,UDP则是面向无连接的协议. 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包.但是,能不能到达就不知道了. 虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议. 我们来看看如何通过UDP协议传输数据.和TCP类似,使用UDP的通信双方也分为客户端和服务器.服务器首先需要绑定端口: s = socket.socket(socket.

  • Python 函数基础知识汇总

    一.函数基础 简单地说,一个函数就是一组Python语句的组合,它们可以在程序中运行一次或多次运行.Python中的函数在其他语言中也叫做过程或子例程,那么这些被包装起来的语句通过一个函数名称来调用. 有了函数,我们可以在很大程度上减少复制及粘贴代码的次数了(相信很多人在刚开始时都有这样的体验).我们可以把相同的代码可以提炼出来做成一个函数,在需要的地方只需要调用即可.那么,这样就提高了代码的复用率了,整体代码看起来比较简练,没有那么臃肿了. 函数在Python中是最基本的程序结构,用来最大化地

  • Python函数式编程之面向过程面向对象及函数式简析

    目录 Python 函数式编程 同一案例的不同写法,展示函数式编程 面向过程的写法 面向对象的写法 接下来进入正题,函数式编程的落地实现 Python 函数式编程的特点 纯函数 Python 函数式编程 Python 不是纯粹的函数式语言,但你可以使用 Python 进行函数式编程 典型的听君一席话,如听一席话,说白了就是 Python 具备函数式编程的特性, so,可以借用函数式语言的设计模式和编程技术,把代码写成函数式编程的样子 一般此时我会吹嘘一下,函数式代码比较简洁和优雅~ 好了,已经吹

  • Scala函数式编程专题--函数思想介绍

    为什么我们需要学习函数式编程?或者说函数式编程有什么优势?这个系列中我会用 scala 给你讲述函数式编程中的优势,以及一些函数式的哲学.不懂 scala 也没关系,scala 和 java 是类似的,在每篇的开头我也会先说明这节中用到的 scala 语法. 为什么函数式编程这几年火起来 如 Python 一样,函数式编程(FP,即Functional Programming)也是近几年才逐渐为人们所知,但它并不是一个多么新的概念.它拥有和面向对象编程(OOP)几乎等长的历史.但纵观每件事的脉络

  • Python函数式编程实例详解

    本文实例讲述了Python函数式编程.分享给大家供大家参考,具体如下: 函数式编程就是一种抽象程度很高的编程范式,从计算机硬件->汇编语言->C语言->Python抽象程度越高.越贴近于计算,但执行效率也越低.纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用.而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的.函数式编程的一个特点就是,允许把函数

随机推荐