跳转至

1 使用函数构造抽象

1.1 第一个例子

from urllib.request import urlopen
shakespeare = urlopen('https://www.composingprograms.com/shakespeare.txt')
words = set(shakespeare.read().decode().split())
print({w for w in words if len(w) == 6 and w[::-1] in words})
# {'drawer', 'redder', 'diaper', 'reward', 'repaid'}
  • 语句和表达式:Python 代码由表达式和语句组成,从广义上讲,计算机程序由以下指令组成 1. 计算一些值 2. 执行一些操作

  • 函数:函数封装了操作数据的逻辑。urlopen 就是一个函数,而网址是一个数据,莎士比亚的戏剧是另一个数据。从前者到后者的转换过程可能会很复杂,但我们可以将这种复杂性隐藏在一个函数中,从而能够使用一个简单的表达式来跳过该过程。函数是本章的主题。

  • 对象set 就是一种对象,支持如计算交集和集合关系(membership)等运算。对象无缝整合了数据以及用于操作该数据的逻辑,并隐藏了二者的复杂性。对象是第二章的主题。
  • 解释器:复合表达式的求解需要以一个可预测的方式来精确解释代码的过程。实现这样的过程,用于计算复合表达式的程序就称为解释器。解释器的设计和实现是第三章的主题。

1.2 编程要素

这样,当我们描述一种语言时,就需要特别注意该语言所提供的能够将简单思想组合成复杂思想的工具。每一种强大的语言都有这样三种机制:

  • 原始表达式和语句:语言所关心的最简单的个体
  • 组合方法:由简单元素组合构建复合元素
  • 抽象方法:命名复合元素,并将其作为单元进行操作

1.2.1 函数类型

  • 纯函数(Pure functions):函数有一些输入(参数)并返回一些输出(调用返回结果)。

  • 非纯函数(Non-pure functions):除了返回值外,调用一个非纯函数还会产生其他改变解释器和计算机的状态的副作用(side effect)。一个常见的副作用就是使用 print 函数产生(非返回值的)额外输出。

如何理解环境:

  • 求解表达式的环境由 序列组成,它们可以被描述为一些盒子。每个帧都包含了一些 绑定,它们将名称与对应的值相关联。全局 帧(global frame)只有一个。赋值和导入语句会将条目添加到当前环境的第一帧。目前,我们的环境仅由全局帧组成。

  • 函数名称重复两次,一次在环境帧中,另一次是作为函数定义的一部分。函数定义中出现的名称叫做 内在名称(intrinsic name),帧中的名称叫做 绑定名称(bound name)。两者之间有一个区别:不同的名称可能指的是同一个函数,但该函数本身只有一个内在名称

  • 这里的“帧”可以理解为C语言提到的局部变量与全局变量

函数是所有程序(无论大小)的基本组成部分,并且是我们使用编程语言来表达计算过程的主要媒介。

  • 每个函数应该只负责一个任务,且该任务要用一个简短的名称来识别,并在一行文本中进行描述。按顺序执行多个任务的函数应该分为多个函数。
  • 不要重复自己(Don't repeat yourself)是软件工程的核心原则。这个所谓的 DRY 原则指出,多个代码片段不应该描述重复的逻辑。相反,逻辑应该只实现一次,为其指定一个名称后多次使用。如果你发现自己正在复制粘贴一段代码,那么你可能已经找到了进行函数抽象的机会。
  • 定义通用的函数。比如作为 pow 函数的一个特例的平方函数就不在 Python 库中,因为 pow 函数可以将数字计算为任意次方。

1.3 函数构造

Higher-Order Function

def area_square(r):
    return r * r
def area_circle(r):
    return r * r * pi
def area_hexagon(r):
    return r * r * 3 * sqrt(3)/2
  • 我们对这三个函数进行整合
def area(r, shape_constant):
    assert r > 0, 'A lenght must be positive'
    return r * r * shape_constant

def area_square(r):
    return area(r,1)

def area_circle(r):
    return area(r,pi)

def area_hexagon(r):
    return area(r, 3 * sqrt(3) / 2)

1.4 Function as Return Values

def make_adder(n):
    def adder(k):
        return k + n
    return adder
  • Functions defined within other function bodies are bound to names in a local frame
>>> make_adder(2000)(13)
2013
>>> f = make_adder(2000)
>>> f(13)
2013

1.5 Lambda 表达式

square = lambda x: x * x
  • square = x * x : An expression: this one evaluates to a number
  • square = lambda x: x * x: Also an expression: evaluates to a function

  • def square(x):similar to lambda

  1. both create a function with the same domain, range, and behavior
  2. both bind that function to the square
  3. both functions have as their parent the frame in which they were defined
  4. only the def statement gives the function an intrinsic(固定的) name

1.6 Return函数返回

def search(f):
    x = 0
    while True:
        if f(x):
            return x
        x += 1

def is_three(x):
    return x == 3

def square(x):
    return x*x

def inverse(f):
    return lambda y:search(lambda x: f(x) == y)

1.7 分支控制

def if_(c,f,t):
    if c:
        f
    else:
        t

这样是行不通的,因为在传入的时候,if_会对这些进行评估,ft如果是invalid的就会报错。

1.8 Self-Reference

def print_all(x)
    print(x)
    return print_all
def print_sums(x):
    print(x)
    def next_sum(y):
        return print_sums(x+y)
    return next_sum

1.9 Function Currying

我们可以使用高阶函数将一个接受多个参数的函数转换为一个函数链,每个函数接受一个参数。更具体地说,给定一个函数 f(x, y),我们可以定义另一个函数 g 使得 g(x)(y) 等价于 f(x, y)。在这里,g 是一个高阶函数,它接受单个参数 x 并返回另一个接受单个参数 y 的函数。这种转换称为柯里化(Curring)。

例如,我们可以定义 pow 函数的柯里化版本:

>>> def curried_pow(x):
        def h(y):
            return pow(x, y)
        return h
>>> curried_pow(2)(3)
8

1.10 函数装饰器 Decorator

def trace1(fn):
    def traced(x):
        print('Calling',fn,'on argument',x)
        return fn(x)
    return traced

@trace1
def square(x):
    return x*x

@trace1
def sum_squares_up_to(n):
    k = 1
    total = 0
    while k <= n:
        total, k = total + square(k), k+1
    return total

1.11 断言测试

断言(Assertions):程序员使用 assert 语句来验证是否符合预期,例如验证被测试函数的输出。assert 语句在布尔上下文中有一个表达式,后面是一个带引号的文本行(单引号或双引号都可以,但要保持一致),如果表达式的计算结果为假值,则显示该行

>>> def fib_test():
        assert fib(2) == 1, '第二个斐波那契数应该是 1'
        assert fib(3) == 1, '第三个斐波那契数应该是 1'
        assert fib(50) == 7778742049, '在第五十个斐波那契数发生 Error'

评论