python中__call__的用法

看用python实现一个简单的web框架时,看到了call的用法,用来处理路由问题,貌似Flask里面也是用这个来实现路由Routing。
这玩意儿挺绕的,想清楚就赶快写下来,以后备用。其实跟函数装饰器很相似,也完全可以当成函数装饰器来用。
先讲一下call比较简单的一种用法,就是让类的实例变成可调用对象。

1
2
3
4
5
6
7
8
9
10
11
class Request:
def __init__(self):
self._a = 2

def __call__(self, x, y):
return x+y
req = Request()
req(1,2)

#output
#>>>3

接下来讲用call实现装饰器:

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
# 在用它完成web框架路由设置的时候,我们是把每个路径对应的响应函数存进route,这样每次来请求,我们就知道用那个函数生成响应
class Route:
def __init__(self, pattern):
self._pattern = pattern
self._check_list = []
def __call__(self, callback):
self._check_list.append((self._pattern, callback))

@Route(r'/hello/(.*)/$') # 0. hello=Route(r'...):首先先执行Route(r'/hello/(.*)/$'),也就是生成了一个self._pattern为括号内内容的实例也叫hello,
# 不是函数名称hello,是__call__,它的地址指向__call__
# 1. 然后是 @ , 功能是hello(hello)括号内才是函数名称hello,也就是说把函数名称hello带入了__call__内执行(等同于__call__(hello))
# ,所以会直接把pattern和callback(也就是hello)append进checklist。
# 这整个过程并不需要别的执行语句,一个@就自动执行完成了。所以当我们想调用hello(request,name)这个函数的时候,会报错,因为hello的地址指向了__call__,外部是访问不到的
# 这点是跟函数装饰器有不同,函数装饰器还是可以调用hello
def hello(request,name):
return Response('<h1>Hi,{}</h1>'.format(name))

@Route(r'/seeyou/(.*)/$')
def bye(request,name):
return Response('<h1>Bye,{}</h1>'.format(name))

# 既然前面说了__call__可以使实例变成可调用对象,那么我们可不可以先实列化,再拿实例当构造函数,装饰我们的响应函数实现同样功能呢,下面展示如何实现
class Route:
def __init__(self):
self._check_list = []
def __call__(self, pattern):
def inside(callback):
self._check_list.append((pattern, callback))
return inside
re = Route() #完成类的实例化,现在re是一个可调用对象__call__, re(pattern)==__call__(pattern)
@re(r'/seeyou/(.*)/$') # 0. 记住re就是__call__,先执行re(r'/seeyou/(.*)/$'),返回inside
# 1. @起作用了,抓取了bye上来,执行bye=inside(bye),也就是__call__的inside函数,
# 2. 所以现在我们有了pattern,也有了callback可以正常append了

def bye(request,name):
return Response('<h1>Bye,{}</h1>'.format(name))