2019.5.9_获取关于参数的信息(一)

192 阅读3分钟

获取关于参数的信息(一)

HTTP微框架Bobo中有个使用函数内省的好例子。下例是对Bobo教程中"Hello world"应用的改编,说明了内省怎么使用。

import bobo
@bobo.query('/')
def hello(person):
    return 'Hello %s!' % person

bobo.query装饰器把一个普通的函数(如hello)与框架的请求处理机制集成起来了。装饰器以后会写到,这不是此例子的关键。关键是,bobo会内省hello函数,发现他需要一个名为person的参数,然后从请求中获取那个名称对应的参数,将其传给hello函数,因而程序员根本不用触碰请求对象。

安装bobo然后启动开发服务器,执行上例中的脚本,(例如,bobo -f hello.py)。访问http://localhost:8080/看到的消息是“Missing from variable person”,HTTP 状态码是403。这是因为,Bobo知道调用hello函数必须传入person这个参数,但是在请求中找不到同名参数。下例,在shell回话中使用curl展示了这个行为。

# 如果请求中缺少函数的参数,Bobo返回403 forbidden 响应;curl -i的作用是把首部转储到标准输出
$ curl -i http://localhost:8080/
HTTP/1.0 403 Forbidden
Data: Thu, 16 May 2019 10:21:40 GMT
Server:WSGServer/0.2 Cpython/3.7.1
Content-Type: text/html; charset=UTF-8
Content-Length:103

<html>
<head><title> Missing parameter </title></head>
<body>Missing form variable person</body>
</html>

然而,如果访问http://localhost:8080/?person=Jim,响应会变成字符串'Hello Jim!',如下例所示:

# 传入所需的person参数才能得到OK响应
$ curl -i http://localhost:8080/?perdon=Jim
HTTP/1.0 200 OK
Data: Thu, 16 May 2019 10:36:40 GMT
Server:WSGServer/0.2 Cpython/3.7.1
Content-Type: text/html; charset=UTF-8
Content-Length:10

Hello Jim!

Bobo是怎么知道函数需要哪个参数的呢?它又是怎么知道参数有没有默认值呢?

函数对象有个__defaults__属性,它的值是一个元组,里面保存这定位参数和关键字参数的默认值。仅限关键字参数的默认值在__kwdefaults__属性中。然而,参数的名称在__code__属性中,它的值是一个code对象引用,自身也有很多属性。

为了说明这些属性的用途,下面clip.py模块中定义clip函数,如下例所示,然后审查它。

def clip(text, max_len=80):
    '''
    在max_len前面或后面的第一个空格处截断文本
    '''
    end = None
    if len(text) > max_len:
        space_before = text.rfind(' ', 0, max_len)
        if space_before >= 0:
            end = space_before
        else:
            space_after = text.rfind(' ', max_len)
            if space_after >= 0:
                end = space_after
    if end is None: # 没有找到空格
        end = len(text)
    return text[:end].rstrip()

审查上例子中定义的clip函数,查看__defaults__、__code__.co_varnames和__code__.co_argcount的值。

>>> from clip import clip
>>> clip.__default__
(80, )
>>> clip.__code__ # doctest: +ELLIPSIS
<code object clip at 0x...>
>>> clip.__code__.co_varnames
('text', 'max_len', 'end', 'space_before', 'spaec_after')
>>>clip.__code__.co_argcount
2

可以看出,这种组织方式并不是最便利的。参数名称在__code__.co_varnames中,不过里面还有函数定义体中创建的局部变量。因此,参数名称是前N个字符串,N的值由__code__.co_argcount确定。顺便说一下,这里不包含前缀为*或**的变长参数。参数的默认值只能通过它们在__defaults__元组中的位置确定,因此要从后向前扫描才能把参数和默认值对应起来。在这个例子中,clip函数有两个参数,textmax_len,其中一个有默认值,即80,因此它必然属于最后一个参数,即max_len。这有违常理。

幸好,有更好的方式——使用inspect模块。