Skip to content

Commit ba4669f

Browse files
committed
1.6
1 parent 9af6c60 commit ba4669f

File tree

4 files changed

+115
-1
lines changed

4 files changed

+115
-1
lines changed

1.5.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -305,5 +305,5 @@ while <expression>:
305305
python3 -m doctest <python_source_file>
306306
```
307307

308-
高效测试的关键是在实现新的函数之后(甚至是之前)立即编写(以及执行)测试。只调用一个函数的测试叫做单元测试。详尽的单元测试时良好程序设计的标志
308+
高效测试的关键是在实现新的函数之后(甚至是之前)立即编写(以及执行)测试。只调用一个函数的测试叫做单元测试。详尽的单元测试是良好程序设计的标志
309309

1.6.md

+114
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,117 @@ Lambda 的术语是一个历史的偶然结果,来源于手写的数学符号
314314
> -- Peter Norvig (norvig.com/lispy2.html)
315315
316316
尽管它的词源不同寻常,Lambda 表达式和函数调用相应的形式语言,以及 Lambda 演算都成为了计算机科学概念的基础,并在 Python 编程社区广泛传播。当我们学习解释器的设计时,我们将会在第三章中重新碰到这个话题。
317+
318+
## 1.6.6 示例:牛顿法
319+
320+
最后的扩展示例展示了函数值、局部定义和 Lambda 表达式如何一起工作来简明地表达通常的概念。
321+
322+
牛顿法是一个传统的迭代方法,由于寻找数学函数返回值为零的参数。这些值叫做一元数学函数的根。寻找一个函数的根通常等价于求解一个相关的数学方程。
323+
324+
+ 16 的平方根是满足`square(x) - 16 = 0``x`值。
325+
+ 以 2 为底 32 的对数(例如 2 与某个指数的幂为 32)是满足`pow(2, x) - 32 = 0``x`值。
326+
327+
所以,求根的通用方法会向我们提供算大来计算平方根和对数。而且,我们想要计算根式的等式只包含简单操作:乘法和乘方。
328+
329+
在我们继续之前有个注解:我们直到如何计算平方根和对数的事实,很容易当做自然的事情。并不只是 Python,你的手机和计算机,可能甚至你的手表都可以为你做这件事。但是,学习计算机科学的一部分是弄懂这些数如何计算,而且,这里展示的通用方法可以用于求解大量等式,而不仅仅是内建于 Python 的东西。
330+
331+
在开始理解牛顿方法之前,我们可以开始编程了。这就是函数抽象的威力。我们简单地将之前的语句翻译成代码:
332+
333+
```py
334+
>>> def square_root(a):
335+
return find_root(lambda x: square(x) - a)
336+
>>> def logarithm(a, base=2):
337+
return find_root(lambda x: pow(base, x) - a)
338+
```
339+
340+
当然,我们现在还不能调用任何函数,直到我们定义了`find_root`,所以我们需要理解牛顿法如何工作。
341+
342+
牛顿法也是一个迭代改进算法:它会改进任何可导函数的根的推测值。要注意我们感兴趣的两个函数都是平滑的。对于
343+
344+
+ `f(x) = square(x) - 16`(细线)
345+
+ `f(x) = pow(2, x) - 32`(粗线)
346+
347+
在二维平面上画出`x``f(x)`的图像,它展示了两个函数都产生了光滑的曲线,它们在某个点穿过了 0。
348+
349+
![](img/curves.png)
350+
351+
由于它们是光滑的(可导的),这些曲线可以通过任何点上的直线来近似。牛顿法根据这些线性的近似值来寻找函数的根。
352+
353+
想象经过点`(x, f(x))`的一条直线,它与函数`f(x)`的曲线在这一点的斜率相同。这样的直线叫做切线,它的斜率叫做`f``x`上的导数。
354+
355+
这条直线的斜率是函数值改变量与函数参数改变量的比值。所以,按照`f(x)`除以这个斜率来平移`x`,就会得到切线达到 0 时的`x`值。
356+
357+
![](img/newton.png)
358+
359+
我们的牛顿更新操作表达了跟随这条斜线到零的计算过程。我们通过在非常小的区间上计算函数斜率来近似得到函数的导数。
360+
361+
```py
362+
>>> def approx_derivative(f, x, delta=1e-5):
363+
df = f(x + delta) - f(x)
364+
return df/delta
365+
>>> def newton_update(f):
366+
def update(x):
367+
return x - f(x) / approx_derivative(f, x)
368+
return update
369+
```
370+
371+
最后,我们可以定义基于`newton_update`(我们的迭代改进算法)的`find_root`函数,以及一个测试来观察`f(x)`是否接近于 0。我们提供了一个较大的初始推测是来提升`logarithm`的性能。
372+
373+
```py
374+
>>> def find_root(f, initial_guess=10):
375+
def test(x):
376+
return approx_eq(f(x), 0)
377+
return iter_improve(newton_update(f), test, initial_guess)
378+
>>> square_root(16)
379+
4.000000000026422
380+
>>> logarithm(32, 2)
381+
5.000000094858201
382+
```
383+
384+
当你实验牛顿法时,要注意它不总是收敛的。`iter_improve`的初始推测值必须足够接近于根,而且函数必须满足各种条件。虽然具有这些缺陷,牛顿法是一个用于解决微分方程的强大的通用计算方法。实际上,对数的非常快速的算法和大整数除法也采用这个技巧的变体。
385+
386+
## 1.6.7 抽象和一等函数
387+
388+
我们以观察用户定义函数作为关键的抽象技巧来开始了这一节,因为它们让我们能够将计算的通用方法表达为编程语言中明显的元素。现在我们已经看到了高阶函数如何让我们操作这些通用方法来进一步创建抽象。
389+
390+
作为程序员,我们应该留意识别程序中低级抽象的机会,在它们之上构建,并泛化它们来创建更加强大的抽象。这并不是说,一个人应该总是以尽可能最抽象的方式来编程;专家级程序员直到如何选择合适于他们任务的抽象级别。但是能够基于这些抽象来思考,以便我们在新的上下文中能使用它们十分重要。高阶函数的重要性是,它允许我们更加明显地将这些抽象表达为编程语言中的元素,使它们能够处理其它的计算元素。
391+
392+
通常,编程语言会限制操作计算元素的途径。带有最少限制的元素被称为具有一等地位。一些一等元素的“权利和特权”是:
393+
394+
1. 它们可以绑定到名称。
395+
2. 它们可以作为参数向函数传递。
396+
3. 它们可以作为函数的返回值返回。
397+
4. 它们可以包含在好素具结构中。
398+
399+
Python 总是给予函数一等地位,所产生的表现力的收益是巨大的。另一方面,控制结构不能做到:你不能像使用`sum`那样将`if`传给一个函数中。
400+
401+
## 1.6.8 函数装饰器
402+
403+
Python 提供了特殊的语法,将高阶函数用作执行`def`语句的一部分,叫做装饰器。
404+
405+
406+
```py
407+
Python provides special syntax to apply higher-order functions as part of executing a def statement, called a decorator. Perhaps the most common example is a trace.
408+
409+
>>> def trace1(fn):
410+
def wrapped(x):
411+
print('-> ', fn, '(', x, ')')
412+
return fn(x)
413+
return wrapped
414+
>>> @trace1
415+
def triple(x):
416+
return 3 * x
417+
>>> triple(12)
418+
-> <function triple at 0x102a39848> ( 12 )
419+
36
420+
```
421+
422+
这个例子中,定义了高阶函数`trace1`,它返回一个函数,这个函数在调用它的参数之前执行`print`语句来输出参数。`triple``def`语句拥有一个注解,`@trace1`,它会影响`def`的执行规则。像通常一样,函数`triple`被创建了,但是,`triple`的名称并没有绑定到这个函数上,而是绑定到了在新定义的函数`triple`上调用`trace1`的返回函数值上。在代码中,这个装饰器等价于:
423+
424+
```py
425+
>>> def triple(x):
426+
return 3 * x
427+
>>> triple = trace1(triple)
428+
```
429+
430+
**附加部分:**实际规则是,装饰器符号`@`可以跟随在表达式后面(`@trace1`仅仅是一个简单的表达式,由单一名称组成)。任何产生合适的值的表达式都可以。例如,使用合适的值,你可以定义装饰器`check_range`,使用`@check_range(1, 10)`来装饰函数定义,这会检查函数的结果来确保它们是 1 到 10 的整数。调用`check_range(1,10)`会返回一个函数,它之后会用在新定义的函数上,在它绑定到`def`语句中的名称之前。感兴趣的同学可以阅读 Ariel Ortiz 编写的[一篇装饰器的简短教程](http://programmingbits.pythonblogs.com/27_programmingbits/archive/50_function_decorators.html)来了解更多的例子。

img/curves.png

19.6 KB
Loading

img/newton.png

24.5 KB
Loading

0 commit comments

Comments
 (0)