Files
ailearning/docs/da/071.md
2020-10-19 21:08:55 +08:00

535 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 函数进阶参数传递高阶函数lambda 匿名函数global 变量,递归
## 函数是基本类型
`Python` 中,函数是一种基本类型的对象,这意味着
* 可以将函数作为参数传给另一个函数
* 将函数作为字典的值储存
* 将函数作为另一个函数的返回值
In [1]:
```py
def square(x):
"""Square of x."""
return x*x
def cube(x):
"""Cube of x."""
return x*x*x
```
作为字典的值:
In [2]:
```py
funcs = {
'square': square,
'cube': cube,
}
```
例子:
In [3]:
```py
x = 2
print square(x)
print cube(x)
for func in sorted(funcs):
print func, funcs[func](x)
```
```py
4
8
cube 8
square 4
```
## 函数参数
### 引用传递
`Python` 中的函数传递方式是 `call by reference` 即引用传递,例如,对于这样的用法:
```py
x = [10, 11, 12]
f(x)
```
传递给函数 `f` 的是一个指向 `x` 所包含内容的引用,如果我们修改了这个引用所指向内容的值(例如 `x[0]=999`),那么外面的 `x` 的值也会被改变。不过如果我们在函数中赋给 `x` 一个新的值(例如另一个列表),那么在函数外面的 `x` 的值不会改变:
In [4]:
```py
def mod_f(x):
x[0] = 999
return x
x = [1, 2, 3]
print x
print mod_f(x)
print x
```
```py
[1, 2, 3]
[999, 2, 3]
[999, 2, 3]
```
In [5]:
```py
def no_mod_f(x):
x = [4, 5, 6]
return x
x = [1,2,3]
print x
print no_mod_f(x)
print x
```
```py
[1, 2, 3]
[4, 5, 6]
[1, 2, 3]
```
### 默认参数是可变的!
函数可以传递默认参数,默认参数的绑定发生在函数定义的时候,以后每次调用默认参数时都会使用同一个引用。
这样的机制会导致这种情况的发生:
In [6]:
```py
def f(x = []):
x.append(1)
return x
```
理论上说,我们希望调用 `f()` 时返回的是 `[1]` 但事实上:
In [7]:
```py
print f()
print f()
print f()
print f(x = [9,9,9])
print f()
print f()
```
```py
[1]
[1, 1]
[1, 1, 1]
[9, 9, 9, 1]
[1, 1, 1, 1]
[1, 1, 1, 1, 1]
```
而我们希望看到的应该是这样:
In [8]:
```py
def f(x = None):
if x is None:
x = []
x.append(1)
return x
print f()
print f()
print f()
print f(x = [9,9,9])
print f()
print f()
```
```py
[1]
[1]
[1]
[9, 9, 9, 1]
[1]
[1]
```
## 高阶函数
以函数作为参数,或者返回一个函数的函数是高阶函数,常用的例子有 `map``filter` 函数:
`map(f, sq)` 函数将 `f` 作用到 `sq` 的每个元素上去,并返回结果组成的列表,相当于:
```py
[f(s) for s in sq]
```
In [9]:
```py
map(square, range(5))
```
Out[9]:
```py
[0, 1, 4, 9, 16]
```
`filter(f, sq)` 函数的作用相当于,对于 `sq` 的每个元素 `s`,返回所有 `f(s)``True``s` 组成的列表,相当于:
```py
[s for s in sq if f(s)]
```
In [10]:
```py
def is_even(x):
return x % 2 == 0
filter(is_even, range(5))
```
Out[10]:
```py
[0, 2, 4]
```
一起使用:
In [11]:
```py
map(square, filter(is_even, range(5)))
```
Out[11]:
```py
[0, 4, 16]
```
`reduce(f, sq)` 函数接受一个二元操作函数 `f(x,y)`,并对于序列 `sq` 每次合并两个元素:
In [12]:
```py
def my_add(x, y):
return x + y
reduce(my_add, [1,2,3,4,5])
```
Out[12]:
```py
15
```
传入加法函数,相当于对序列求和。
返回一个函数:
In [13]:
```py
def make_logger(target):
def logger(data):
with open(target, 'a') as f:
f.write(data + '\n')
return logger
foo_logger = make_logger('foo.txt')
foo_logger('Hello')
foo_logger('World')
```
In [14]:
```py
!cat foo.txt
```
```py
Hello
World
```
In [15]:
```py
import os
os.remove('foo.txt')
```
## 匿名函数
在使用 `map` `filter``reduce` 等函数的时候,为了方便,对一些简单的函数,我们通常使用匿名函数的方式进行处理,其基本形式是:
```py
lambda <variables>: <expression>
```
例如,我们可以将这个:
In [16]:
```py
print map(square, range(5))
```
```py
[0, 1, 4, 9, 16]
```
用匿名函数替换为:
In [17]:
```py
print map(lambda x: x * x, range(5))
```
```py
[0, 1, 4, 9, 16]
```
匿名函数虽然写起来比较方便(省去了定义函数的烦恼),但是有时候会比较难于阅读:
In [18]:
```py
s1 = reduce(lambda x, y: x+y, map(lambda x: x**2, range(1,10)))
print(s1)
```
```py
285
```
当然,更简单地,我们可以写成这样:
In [19]:
```py
s2 = sum(x**2 for x in range(1, 10))
print s2
```
```py
285
```
# global 变量
一般来说,函数中是可以直接使用全局变量的值的:
In [20]:
```py
x = 15
def print_x():
print x
print_x()
```
```py
15
```
但是要在函数中修改全局变量的值,需要加上 `global` 关键字:
In [21]:
```py
x = 15
def print_newx():
global x
x = 18
print x
print_newx()
print x
```
```py
18
18
```
如果不加上这句 `global` 那么全局变量的值不会改变:
In [22]:
```py
x = 15
def print_newx():
x = 18
print x
print_newx()
print x
```
```py
18
15
```
## 递归
递归是指函数在执行的过程中调用了本身,一般用于分治法,不过在 `Python` 中这样的用法十分地小,所以一般不怎么使用:
Fibocacci 数列:
In [23]:
```py
def fib1(n):
"""Fib with recursion."""
# base case
if n==0 or n==1:
return 1
# recurssive caae
else:
return fib1(n-1) + fib1(n-2)
print [fib1(i) for i in range(10)]
```
```py
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
```
一个更高效的非递归版本:
In [24]:
```py
def fib2(n):
"""Fib without recursion."""
a, b = 0, 1
for i in range(1, n+1):
a, b = b, a+b
return b
print [fib2(i) for i in range(10)]
```
```py
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
```
速度比较:
In [25]:
```py
%timeit fib1(20)
%timeit fib2(20)
```
```py
100 loops, best of 3: 5.35 ms per loop
100000 loops, best of 3: 2.2 µs per loop
```
对于第一个递归函数来说,调用 `fib(n+2)` 的时候计算 `fib(n+1), fib(n)`,调用 `fib(n+1)` 的时候也计算了一次 `fib(n)`,这样造成了重复计算。
使用缓存机制的递归版本,这里利用了默认参数可变的性质,构造了一个缓存:
In [26]:
```py
def fib3(n, cache={0: 1, 1: 1}):
"""Fib with recursion and caching."""
try:
return cache[n]
except KeyError:
cache[n] = fib3(n-1) + fib3(n-2)
return cache[n]
print [fib3(i) for i in range(10)]
%timeit fib1(20)
%timeit fib2(20)
%timeit fib3(20)
```
```py
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
100 loops, best of 3: 5.37 ms per loop
100000 loops, best of 3: 2.19 µs per loop
The slowest run took 150.16 times longer than the fastest. This could mean that an intermediate result is being cached
1000000 loops, best of 3: 230 ns per loop
```