Warning
本文最后更新于 April 25, 2020,文中内容可能已过时,请谨慎使用。
函数式编程是一种编程范式,常见的编程范式有 命令式编程(Imperative programming),函数式,逻辑式。在函数式编程中,函数是一等公民。函数可以在任何地方定义,可以作为参数,返回值等。下面从Python 或 Golang 为例介绍一下函数式编程。
闭包
介绍一下闭包(closure)。闭包和嵌套函数类似,不同的是,这里外部函数返回的是一个函数,而不是一个具体的值。返回的函数通常也被赋予一个变量。这个变量在后面会一直被执行调用。
外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象,这就是闭包的重要概念。
闭包在很多中语言中都可以使用,下面以Python 和 Golang 举例
Python 实现
举例,计算一个数的 n次冥
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| def nth_power(exponent):
def exponent_of(base):
return base ** exponent
return exponent_of
square = nth_power(2)
cube = nth_power(3)
print(square(3)) # 3 的平方
print(cube(2)) # 2 的立方
### 输出
9
8
|
如果我们不使用闭包,代码写出来长什么样子呢?
1
2
3
4
5
6
7
8
9
| def nth_power_rewrite(base, exponent):
return base ** exponent
print(nth_power_rewrite(3, 2)) # 3 的平方
print(nth_power_rewrite(2, 3)) # 2 的立方
### 输出
9
8
|
Golang 实现
举例, 斐波那契数列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| package main
import "fmt"
func fib() func() int {
a,b := 0,1
return func() int {
b = a + b
a = b
return b
}
}
func main() {
a := fib()
for i := 0; i < 10; i++ {
fmt.Printf("%d ", a())
}
}
### 输出
1 2 4 8 16 32 64 128 256 512
|
因为闭包的存在,我们仍然可以访问外部函数的变量对象。
装饰器
所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。最常见的使用场景有,登录状态检查,日志记录,程序执行时间记录。
如果多个函数在运行时都需要相同的检查、记录功能的话,就使用装饰器。
Python 在装饰器上有现成的使用语法,而 Golang 也可以按照其语法实现装饰器。
Python 实现
最简单的装饰器实现
1
2
3
4
5
6
7
8
9
10
11
12
| def my_decorator(func):
def wrapper():
print('wrapper of decorator')
func()
return wrapper
@my_decorator
def greet():
print('hello world')
if __name__ == '__main__':
greet()
|
带参数的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
| def my_decorator(func):
def wrapper(message):
print('wrapper of decorator')
func(message)
return wrapper
@my_decorator
def greet(name):
print('hello {}'.format(name))
if __name__ == '__main__':
greet('kiosk')
|
带自定义参数的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| def repeat(num):
def my_decorator(func):
def wrapper(message):
for i in range(num):
print('wrapper of decorator')
func(message)
return wrapper
return my_decorator
@repeat(2)
def greet(name):
print('hello {}'.format(name))
if __name__ == '__main__':
greet('kiosk')
|
类装饰器
类装饰器主要依赖于函数 _call_() ,每当你调用一个类的示例时,函数**__call__()** 就会被执行一次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| class Count(object):
def __init__(self, func):
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
print('num of calls is: {}'.format(self.call_count))
return self.func(*args, **kwargs)
@Count
def greet(name):
print('hello {}'.format(name))
if __name__ == '__main__':
greet('kiosk')
greet('007')
|
实际应用
检查是否登录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| import functools
def authenticate(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
request = args[0]
if check_user_logged_in(request): # 如果用户处于登录状态
return func(*args, **kwargs) # 执行函数post_comment()
else:
raise Exception('Authentication failed')
return wrapper
def check_user_logged_in(request):
pass
@authenticate
def post_comment(request):
pass
|
参数计算结果缓存
根据参数缓存每次函数调用结果,对于相同参数的,无需重新函数计算,直接返回之前缓存的返回值;比如计算 递归、动态规划 类的函数时,很多次的函数计算都是重复的。对于函数输入参数结相同的我们直接把计算结果可以缓存下来。
不带结果缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 多次调用递归会很慢
def fibonacci(n):
if n == 1 or n == 2:
return 1
else:
return fibonacci(n-1)+fibonacci(n-2)
@log_execution_time
def run_fib():
for i in range(1, 21):
print(i, ":", fibonacci(i))
if __name__ == '__main__':
run_fib()
###
...
19 : 4181
20 : 6765
run_fib took 5.2937360014766455 ms
|
带结果缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| import functools
# 加上 lru_cache 缓存输入参数相同的结果,直接返回结果
@functools.lru_cache()
def fibonacci(n):
if n == 1 or n == 2:
return 1
else:
return fibonacci(n-1)+fibonacci(n-2)
@log_execution_time
def run_fib():
for i in range(1, 21):
print(i, ":", fibonacci(i))
if __name__ == '__main__':
run_fib()
###
...
19 : 4181
20 : 6765
run_fib took 0.19091700232820585 ms
|
Golang 实现
最简单的装饰器实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| import "fmt"
func decorator(callFunc func()) func() {
return func(){
fmt.Println("wrapper of decorator")
callFunc()
}
}
func greet() {
fmt.Printf("hello wrold ")
}
func main() {
testFunc := decorator(greet)
testFunc()
}
|
http 示例
以下完全参考 左耳朵耗子 叔的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
| package main
import (
"fmt"
"log"
"net/http"
"strings"
)
func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("--->WithServerHeader()")
w.Header().Set("Server", "HelloServer v0.0.1")
h(w, r)
}
}
func WithAuthCookie(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("--->WithAuthCookie()")
cookie := &http.Cookie{Name: "Auth", Value: "Pass", Path: "/"}
http.SetCookie(w, cookie)
h(w, r)
}
}
func WithBasicAuth(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("--->WithBasicAuth()")
cookie, err := r.Cookie("Auth")
if err != nil || cookie.Value != "Pass" {
w.WriteHeader(http.StatusForbidden)
return
}
h(w, r)
}
}
func WithDebugLog(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("--->WithDebugLog")
_ = r.ParseForm()
log.Println(r.Form)
log.Println("path", r.URL.Path)
log.Println("scheme", r.URL.Scheme)
log.Println(r.Form["url_long"])
for k, v := range r.Form {
log.Println("key:", k)
log.Println("val:", strings.Join(v, ""))
}
h(w, r)
}
}
func hello(w http.ResponseWriter, r *http.Request) {
log.Printf("Recieved Request %s from %s\n", r.URL.Path, r.RemoteAddr)
_, _ = fmt.Fprintf(w, "Hello, World! "+r.URL.Path)
}
func main() {
http.HandleFunc("/v1/hello", WithServerHeader(WithAuthCookie(hello)))
http.HandleFunc("/v2/hello", WithServerHeader(WithBasicAuth(hello)))
http.HandleFunc("/v3/hello", WithServerHeader(WithBasicAuth(WithDebugLog(hello))))
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
|
多个装饰器的 Pipeline
上述的装饰器需要一个函数套一个函数,很不好看,可以写一个工具函数——用来遍历并调用各个 decorator
1
2
3
4
5
6
7
8
9
10
11
12
13
| type HttpHandlerDecorator func(http.HandlerFunc) http.HandlerFunc
func Handler(h http.HandlerFunc, decors ...HttpHandlerDecorator) http.HandlerFunc {
for i := range decors {
d := decors[len(decors)-1-i] // iterate in reverse
h = d(h)
}
return h
}
### 调用
http.HandleFunc("/v4/hello", Handler(hello,
WithServerHeader, WithBasicAuth, WithDebugLog))
|
泛型装饰器
和 Python 相比, Go 的装饰器没有办法做到泛型,也就是其代码耦合了需要被修饰的函数的接口类型,无法做到非常通用。
Go 语言是一个静态的语言,这意味着其类型需要在编译时就要搞定,否则无法编译。不过,Go 语言支持的最大的泛型是 interface{} 还有比较简单的 reflection 机制。可以实现泛型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| func Decorator(decoPtr, fn interface{}) (err error) {
var decoratedFunc, targetFunc reflect.Value
decoratedFunc = reflect.ValueOf(decoPtr).Elem()
targetFunc = reflect.ValueOf(fn)
v := reflect.MakeFunc(targetFunc.Type(),
func(in []reflect.Value) (out []reflect.Value) {
fmt.Println("before")
out = targetFunc.Call(in)
fmt.Println("after")
return
})
decoratedFunc.Set(v)
return
}
|
上面的代码动用了 reflect.MakeFunc() 函数制出了一个新的函数其中的 targetFunc.Call(in) 调用了被修饰的函数。
上面这个 Decorator() 需要两个参数。
- 第一个是出参 decoPtr ,就是完成修饰后的函数
- 第二个是入参 fn ,就是需要修饰的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| func bar(a, b string) string {
fmt.Printf("%s, %s \n", a, b)
return a + b
}
func main() {
mybar := bar
_ = Decorator(&mybar, bar)
mybar("hello", "world!")
}
###
before
hello, world!
after
|
More Info: