Fork me on GitHub

Function Programming in Python: 1. (Avoiding) Flow Control

0. whatever


“Function Programming in Python” Chapter 1:避免流控制

在传统的imperative式编程中,一段代码通常包括一些循环(whilefor)、变量的声明和数据的修改(dict,list,set等)、分支语句(if/elif/else/, try/except/finally)。所有这些看起来都很自然,很好理解。这些状态变量和可修改的数据结构通常从现实世界中抽象而来,但是它们会带来一些副作用,随意给定程序的一个断点,我们很难靠人脑快速精确地推断出这些数据里面的值,所以我们经常需要依靠编译器的帮助来查看这些数据里面的值是不是我们想要的那样。

一个解决方案是使我们的代码专注于描述数据是什么(what),而不是如何构建数据(how).

1. Encapsulation (封装)


最先想到的专注于“what”而不是“how”的方法就是:重构代码,把数据构建的操作放在一个独立的地方。考虑改写如下代码:

# configure the data to start with
collection = get_initial_state()
state_var = None
for datum in data_set:
    if condition(state_var):
        state_var = calculate_from(datum)
        new = modify(datum, state_var)
        collection.add_to(new)
    else:
        new = modify_differently(datum)
        collection.add_to(new)

# Now actually work with the data
for thing in collection:
process(thing)

简单地把如何构建数据的过程放进一个独立的函数中:

# tuck away construction of data
def make_collection(data_set):
    collection = get_initial_state()
    state_var = None
    for datum in data_set:
    if condition(state_var):
        state_var = calculate_from(datum, state_var)
        new = modify(datum, state_var)
        collection.add_to(new)
    else:
        new = modify_differently(datum)
        collection.add_to(new)
    return collection

# Now actually work with the data
for thing in make_collection(data_set):
process(thing)

没有改变程序逻辑,但是我们已经把关注点从“如何构建数据”转移到“make_collection()创建了什么”上。

2. Comprehensions (推导式)


使用推导式既可以使代码精悍小巧,又能帮助我们将重心从“how”转移到“what”上。例如,如果我们的原代码如下:

collection = list()
for datum in data_set:
    if condition(datum):
        collection.append(datum)
    else:
        new = modify(datum)
        collection.append(new)

我们可以紧凑地写:

collection = [d if condition(d) else modify(d) for d in data_set]

这样写我们永远都不用考虑或者debug:在循环中的某个断点,collection里面是什么。上例是一个列表推导式,对于dicset类似:

>>> {i:chr(65+i) for i in range(6)}
{0: 'A', 1: 'B', 2: 'C', 3: 'D', 4: 'E', 5: 'F'}
>>> {chr(65+i) for i in range(6)}
{'A', 'B', 'C', 'D', 'E', 'F'}

2.1 Generators (生成器)


生成器是Python中的一个内建类型,可以当作迭代器来使用,它功能的实现依赖于关键字yield,简单举个例子:

def gen(num):
    while num:
        yield num
        num -= 1

for i in gen(3):
    print i
# output: 
# 3
# 2
# 1

这是imperative版本的“构造一个generator”,同样有函数编程风格的:

In [60]: g = (i for i in range(1, 4)[::-1])

In [61]: type(g)
Out[61]: generator

In [62]: for i in g:
    ...:     print i
    ...:     
3
2
1

可以看出generator comprehensionlist comprehension很相似,区别仅在于前者用小括号(),后者用方括号[]

3. Eliminating Loops (消灭循环)


3.1 for循环

循环是命令式编程(imperative programming)中很常见的,比如我们要在for循环中调用函数func(e)

for e in it  # statement-based loop
    func(e)

下面是对应的函数式版本,没有重复的变量e的参与,因此也就没有中间状态:

map(func, it)   # map()-based "loop"

在命令式编程中,经常会看到类似“先执行这个动作,再执行那个动作,然后再执行另外一个动作”的过程。如果把这些独立的动作分别分装进函数中,然后再调用map()函数,:

def f1():
    # do first thing 

def f2():
    # do second thing 

def f3():
    # do third thing 

do_it = lambda f, *args: f(*args)
# do_it作为执行函数
map(do_it, [f1, f2, f3])

我们还可以给函数传参:

In [14]: hello = lambda first, last: ("Hello," + ' '.join([first, last]))
    ...: bye = lambda first, last: ("Bye," + ' '.join([first, last]))
    ...: do_it = lambda f, *args: f(*args)
    ...: map(do_it, [hello, bye], ['david', 'johnson'], ['electon', 'mili'])
    ...: 
Out[14]: ['Hello,david electon', 'Bye,johnson mili']

上例中map()函数接受了四个参数:第一个参数是执行函数,后三个是长度相等的listmap会同时迭代这三个list,将这三个list中的每一组元素([hello, 'david', 'electon'][bye, 'johnson', 'mili'])传递给do_it。 从输出可以看到map()将两个函数hello,byereturn结果放进list作为map()return结果。 如果不是要分开传参,而是将所有参数传给每个function呢?

# use list comprehension 
In [3]: do_all_funcs = lambda fns, *args: [list(map(fn, *args)) for fn in fns]
In [4]: do_all_funcs([hello, bye], ['david', 'johnson'], ['electon', 'mili'])
Out[4]: 
[['Hello,david electon', 'Hello,johnson mili'],
 ['Bye,david electon', 'Bye,johnson mili']]

3.2 while循环

通常来说,我们一整个main程序原则上都可以使用一个map()表达式完成。

将while循环转换成函数式要稍微复杂一点,我们可以直接使用递归来实现。 下面是基于语句的while循环:

# statement-based while loop
while <cond>:
    <pre-suite>
    if <break_condition>:
        break
    else:
        <suite>

函数式while循环:

# FP-style recursive while loop
def while_block():
    <pre-suite>
    if <break_condition>:
        return 1
    else:
        <suite>
        return 0
while_FP = lambda: (<cond> and while_block()) or while_FP()
while_FP()

上述代码中仍然需要一个while_block()函数,这个函数可能不仅仅包含表达式,还包括一些其他语句,当然我们可以把这些过程语句用上一小节提到的map()方法改写成函数式,这个过程虽然会好玩,但是结果可能就不那么好看了。 看一个例子:

# imperative version of "echo()"
def echo_IMP():
    while 1:
        x = raw_input("input something:")
        if x == 'quit':
            break
        else:
            print(x)
# FP version of "echo()"
def identity_print(x):
    print(x)
    return x

echo_FP = lambda: identity_print(raw_input("input something:")) == 'quit' or echo_FP()

参考

  1. “Function Programming in Python”,David Mertz

转载请注明出处:BackNode

My zhiFuBao

Buy me a cup of coffee

blogroll

social