1.6 高阶函数

90 阅读11分钟

泛化

这里就讲了如何进行泛化,什么时候可以进行泛化。这个例子关于的如何画边形形状的图案。这里就举出了三个形状的区域。然后通过观察他们之间的共性,找出通用的常亮和变量。这个例子中,r的平方是通用的。 找到共同的结构可以实现共同的实现

高阶函数增加了抽象程度。如何通过高阶函数实现画任意边形的图案。

# Generalizing patterns using arguments

from math import pi, sqrt

# 一个函数只做一件事情
def area_square(r):
    """返回边长为 R 的正方形的面积."""
    return area(5,1)

def area_circle(r):
    """返回半径为 R 的圆的面积."""
    return r * r * pi

def area_hexagon(r):
    """返回边长为 R 的正六边形的面积."""
    return r * r * 3 * sqrt(3) / 2


# 分解他们共同的部分。

def area(r, shape_constant):
    assert r > 0, 'A length must be positive'
    return r * r * shape_constant


area_square(5)
25

这个项目还是有用的,你不用在每个函数中处理异常,你写了一个函数统一了异常处理。这也算是一部分的泛化。 那你进一步想,还有那些地方可以泛化呢?你这个函数不好用啊。你的函数应该只需要一个参数,也就是半径。 而不是还需要一个函数参数。你要知道为什么他是一个参数,是因为他是一个集合是一个区间,他不是固定的。 就比如年龄,工资,他不是一个固定的常量所以我们需要添加一个参数。

通用函数不会处理某个具体的逻辑。比如在这个例子中,是正方形的平方。他只会处理通用的部分。比如统一的异常处理

这些函数的第一个问题是,接受到负数时会产生错误,这是要解决的第一个问题。 你需要思考的是,你会如何去用这个函数,怎么用是顺手的。

回顾他的思考过程,写完函数之后,就开始测试,然后在测试的过程中发现了一些问题,比如负数的情况下返回值不准确。然后使用Python中的,asset 关键字解决这个问题。在解决这个问题的过程中,就出现一个问题,难道我要在每个函数中都写一遍 assert,很明显这违背了软件工程中的一个原则,dry(不要重复你自己)。他的解决办法就是再写一个函数,进行抽象。

他的第二个抽象部分。

每个函数的共性。

  1. 都需要判断r的值是否合法,每个函数都写一遍太过于重复。
  2. 每个函数都存在 r * r 的计算,每个函数都写一遍太过于重复。
  3. 需要变化的是 r 的值,和 shape_constant 的值。

这种思维方式,可以在日常中进行练习。形成一种思维习惯。如何练习呢?通过对比两个类似的事物,寻找他们共同的特点,然后进行抽象。

def area(r, shape_constant):
    """Return the area of a shape from length measurement R."""
    assert r > 0, 'A length 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)

2. 泛化练习

第二个例子

这就是为什么要学习数学的原因。通过数学表达式我们就知道这个函数的定义域和值域是什么。相当于是我们把数学公式用函数进行描述。

def test(n, term):
    total ,k = 0, 1
    while k <= n:
        total = total + term(k)
        k += 1
    return total

def sum_naturalss(n):
    return n
    
test(5,sum_naturalss)

# 当前

15

你不认为你这两个函数,他们都处理了相同的逻辑吗?

def sum_naturals(n):
    """ 返回参数k的总和"""
    total, k = 0, 1
    while k <= n:
        total = total + k
        k = k + 1
    return total
def sum_cubes(n):
    total, k = 0, 1
    while k <= n:
        total = total + pow(k, 3)
        k = k + 1
    return print(total)

sum_cubes(5)
225
def cube(r):
    return pow(r, 3)

def identity(x):
    return x

def summation(n, term):
    total, k = 0, 1
    while k <= n:
        total = total + term(k)
        k += 1
    return total

summation(5, identity)
15

这两个函数都有相同的模式,如何通过函数抽象出去?除了total值不一样,其他程序的结构都是一模一样的。

你想如何用?给一个自然数,给一个cube他返回。根据一个函数只作一件事情,我们可以编写一个函数,函数就返回结果本身。他不处理,循环过程。

梳理思考过程。首先并不是直接进行抽象。而是先编写普通的函数,然后发现几个函数中,存在共同的模式。这个时候才进行抽象与拆分函数。 2. 就是我们需要思考,我们会怎么用这个函数,这个函数怎么用是最顺手的。比如这个例子。

# Local function definitions; returning functions

def make_adder(n):
    """Return a function that takes one argument K and returns K + N.

    >>> add_three = make_adder(3)
    >>> add_three(4)
    7
    """
    def adder(k):
        return k + n
    return adder

make_adder(2000)(20)
2020

Lambda 表达式

x = 10
square = x * x
square = lambda x: x * x
square(4)

# Return 
16

Lambda 表达式 与 def定义的区别。

  1. Both create a function with the same domain, range, and behavior
  2. 只有 def 语句为函数提供了一个内部名称,该名称显示在 环境图,但不影响执行(除非打印函数)
def end(n, d):
    """Print the final digits of N in reverse order until D is found.    

    >>> end(34567, 5)
    7
    6
    5
    """
    while n > 0:
        last, n = n % 10, n // 10
        print(last)
        if d == last:
            return None

# end(34567,5)
end(1234567,2)
12356 % 10
7
6
5
4
3
2





6
def end(n, d):
    """返回xx集,从参数末尾开始,从d结束

    >>> end(34567, 5)
    7
    6
    5
    """
    while n > 0:
        last, n = n % 10, n // 10
        print(last)
        if d == last:
            return None

def is_prime(n):
    """Return True if n is a prime number, False otherwise."""
    if n < 2:
        return False
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            return False
    return True


def search(f):
    """Return the smallest non-negative integer x for which f(x) is a true value."""
    x = 0
    while True:
        if f(x):
            return x
        x += 1

# 使用 search 函数来找到最小的非负素数
result = search(is_prime)
print(result)  # 输出应该是 2,因为 2 是最小的素数


函数返回值与高阶函数的搭配

search是一个高阶函数。他接受一个函数作为参数。初始化变量x=0. 无限循环,只要函数参数的返回值为真,我们就返回参数x。否则就使x + 1. 值得注意的是,这里的判断条件跟以往遇到的不相同,他不是表达式了,而是根据某个函数的返回值对应的布尔值进行判断。所以我们的函数,他的返回值一定是要返回布尔值的。否则函数会一直进行。

使用高阶函数search的具体例子

def is_three(x):
    """Return whether x is three.

    >>> search(is_three)
    3
    """
    return x == 3

search(is_three)


第二个例子

def square(x):
    """ 返回x的平方"""
    return x * x

def positive(x):
    """A function that is 0 until square(x)-100 is positive.
    
    >>> search(positive)
    11
    """
    return max(0, square(x) - 100)


search(positive)


max(0, square(2) - 100)

这里例子,巧妙的应用到了0作为false的特性。

inverse 反函数

def inverse(f):
    """Return a function g(y) that returns x such that f(x) == y.

    >>> sqrt = inverse(square)
    >>> sqrt(16)
    4
    """
    return lambda y: search(lambda x: f(x) == y)
# 使用 inverse 函数来找到 square 函数的反函数
sqrt = inverse(square)
print(sqrt(16))  # 输出应该是 4,因为 4 的平方是 16

函数 inverse 的用途是创建一个函数的反函数。在某些数学和计算问题中,能够找到一个函数的逆是非常有用的,因为这意味着你可以逆转原始函数的效果。

例如,假设你有一个函数 f,它将输入 x 转换为输出 y。如果你想要将输出 y 转换回原始的输入 x,你需要 f 的反函数。这不是总是可能的,因为并非所有函数都有反函数(例如,如果函数不是一一对应的,它可能没有反函数)。但是,当函数是一一对应的时,你可以使用 inverse 函数来创建它的反函数。

在提供的代码示例中,square 函数是一个简单的例子,它将一个数 x 转换为它的平方 x^2。inverse 函数用于创建 square 的反函数,即给定一个数 y,找到 x 使得 x^2 = y。在这个例子中,如果 y 是一个正数,那么 x 就是 y 的平方根。

这个概念可以应用于更复杂的函数,只要它们是可逆的。例如,如果你有一个函数 f,它对输入数据进行编码,你可以使用 inverse 函数来创建一个解码函数,将编码后的数据转换回原始数据。

在实际应用中,这种技术可以用于各种问题,包括加密和解密、数据转换、数学建模等。通过创建反函数,你可以逆转特定的计算过程,这在解决需要逆向工程的问题时非常有用。

这个函数理解起来真的有难度,而且我又不知道他有什么用?遇到这种问题我应该怎么办呢?我是先放着嘛?你不觉得你每一个知识点所花费的时间太多了嘛。

在什么情况下可以写高阶函数呢?高阶函数有什么用呢?如何把高阶函数应用在实际的项目中呢?这都是我需要解决的问题。先记录着把。先看后面的。

流程控制

此小节,讲授一些流程控制的高阶用法。

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




函数接受三个参数,ctf。如果c为真,t,否则f。这种形式的函数头一次见,那么这个函数有什么用呢?

from math import sqrt
def real_sqrt(x):
    """Return the real part of the square root of x.

    >>> real_sqrt(4)
    2.0
    >>> real_sqrt(-4)
    0.0
    """
    if x > 0:
        return sqrt(x)
    else:
        return 0.0
    # if_(x > 0, sqrt(x), 0.0)

# 只要不是负数就返回参数x的平方数。比如16的平方数为4.

2.8284271247461903

1.6.5 返回自身的函数

一个函数如果返回自身会发生什么?

def print_all(x):
    print(x)
    return print_all
    
    
print_all(1)(3)(5)

存在几个参数就调用几次,不会存在无限运行。

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

print_sums(1)

# 1 4 8 13 函数的值被保存了。
1





<function __main__.print_sums.<locals>.next_sum(y)>
  1. 1 作为参数传递给了 print_sums。
  2. 在函数内部定义了一个子函数,然后 print_sums 的返回值是返回这个子函数。
  3. 第二个值被作为 next _sum 函数的参数,next_sum 函数体共享了父函数的作用域有 x = 1这个值,所以子函数的输出结果为1 + 4.

疑问:父子函数中的参数是不是可以共享。

与上一个例子不同的区别在于。我们在父函数中定义了一个子函数。子函数函数父函数,父函数返回子函数。用相同的方法进行调用时,除了输出结果之外,还返回 next_sum 函数。

1.6.6 柯里化

它指的是将一个接受多个参数的函数转换为一系列使用一个参数的函数。举个例子。

想到一个比较好的练习方法。把写过的函数用柯里化进行转换。

我们可以使用高阶函数将一个接受多个参数的函数转换为一个函数链,每个函数接受一个参数。更具体地说,给定一个函数 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)

# 普通函数版本
def pows(x,y):
      return pow(x,y)

pows(2,3)
8

def add(x, y):
    return x + y

# 使用柯里化
def curry_add(x):
    def add_y(y):
        return x + y
    return add_y

# 使用柯里化后的函数
curry_add(3)(4)
add(3,9)


12

第二个例子。函数内部定义两个子函数。

from operator import add, mul

def curry2(f):
    """Curry a two-argument function.

    >>> m = curry2(add)
    >>> add_three = m(3)
    >>> add_three(4)
    7
    >>> m(2)(1)
    3
    """
    def g(x):
        def h(y):
            return f(x, y)
        return h
    return g