Python 自省指南

Table of Contents

1. 自省 && 反射

自省:知道自身有哪些属性

反射:知道别人有哪些属性,动态调用,它的好处就是便于扩展/维护

每个对象有很多自己的属性,如:

__doc__
__name__

一个简单的例子:

import __main__

def t_func1():
    print("func1")

def t_func2():
    print("func2")

# 1、知道自己是怎么被调用的
if __name__ == '__main__':
    # 2、知道自己有哪些函数
    print(u'我有这些函数:')
    print(dir(__main__))

    # 3、调用t_开头的函数
    for func in dir(__main__):
        if func.startswith('t_'):
            getattr(__main__, func)()

2. getattr 和 setattr

在 web.py 源码中看到以下经典的方法:

52 class Storage(dict):
53     """
54     A Storage object is like a dictionary except obj.foo can be used
55     in addition to obj['foo'].
56
57         >>> o = storage(a=1)
58         >>> o.a
59         1
60         >>> o['a']
61         1
62         >>> o.a = 2
63         >>> o['a']
64         2
65         >>> del o.a
66         >>> o.a
67         Traceback (most recent call last):
68             ...
69         AttributeError: 'a'
70
71     """
72     def __getattr__(self, key):
73         try:
74             return self[key]
75         except KeyError, k:
76             raise AttributeError, k
77
78     def __setattr__(self, key, value):
79         self[key] = value
80
81     def __delattr__(self, key):
82         try:
83             del self[key]
84         except KeyError, k:
85             raise AttributeError, k
86
87     def __repr__(self):
88         return '<Storage ' + dict.__repr__(self) + '>'

Storage 类从 dict 类集成下来的。以前我们是这样访问字典:

d = dict()
d['name'] = 'lu4nx'

Storage 类对象可以这样来访问:

d = Storage()
d.name = 'lu4nx'
print(d.name)

全是因为 __getattr____setattr__ 的作用。当调用 d.name = 'lu4nx',就靠 __setattr__ 了;调用 print(d.name) 就依靠 __getattr__

不过我发现 __getattr____setattr____init__ 之间似乎有点冲突,先看下面代码:

class Test(object):
    def __init__(self):
        print('__init__')

    def __setattr__(self,key,value):
        print('__setattr__')

    def __getattr__(self,key):
        print('__getattr__')

t = Test()

执行后,结果如下:

$ python class.py
__init__

修改下脚本:

class Test(object):
    def __init__(self):
        print('__init__')

    def __setattr__(self,key,value):
        print('__setattr__')

    def __getattr__(self,key):
        print('__getattr__')

t = Test()
t.name = 'lu4nx'
print(t.name)

执行结果如下:

$ python class.py
__init__
__setattr__
__getattr__
None

说明 __setattr____getattr__ 只有在使用时才会被调用,这是废话。__getattr__ 会返回一个值,这也是废话。

再看下面的代码:

class Test(object):
    def __init__(self):
        self.d = dict()
        print('__init__')

    def __setattr__(self,key,value):
        print('__setattr__')

    def __getattr__(self,key):
        print('__getattr__')

t = Test()
#t.name = 'lu4nx'
#print(t.name)

执行后如下:

$ python class.py
__setattr__
__init__

看见没,我在 __init__ 中如果有值初始化,__setattr__ 就会先被调用。所以想正常实现下方代码,就要换个思路了:

class Test(object):
    def __init__(self):
        self.d = dict()
        print('__init__')

    def __setattr__(self,key,value):
        self.d[key] = value
        print('__setattr__')

    def __getattr__(self,key):
            return self.d[key]
        print('__getattr__')

t = Test()
t.name = 'lu4nx'
print(t.name)

上面的代码如果执行会报异常的,只有把 self.d = dict() 放在__init__1外面了:

class Test(object):
    d = dict()

    def __init__(self):
        print('__init__')

    def __setattr__(self,key,value):
        self.d[key] = value
        print('__setattr__')

    def __getattr__(self,key):
            return self.d[key]
        print('__getattr__')

t = Test()
t.name = 'lu4nx'
print(t.name)

这样就不会出错了:

$ python class.py
__init__
__setattr__
lu4nx

3. __main__ 自省

首先得导入 main:

import main

接下来就可以用 dir、getattr 和 setattr 了:

dir(main)
getattr(main, "xx")
setattr(main, "xx")

函数获得自己的名字:

方法1:

import sys
sys._getframe().f_code.co_name

方法2:

由于方法1不能定义成单独函数,可以根据调用栈回溯来知道调用者的名字:

import traceback
caller_name = traceback.extract_stack()[-2][2]

4. __import__

如果要 import 的库名由字符串指定,可以使用 __import__ 函数,该函数返回一个库对象,如:

sys = __import__('sys')

这时就可以通过 sys 变量访问 sys 库的内容了。

模块方法自省

根据字符串名调用模块中同名函数:

import sys

urls = ('lu4nx')

def lu4nx():
    print('hello world')

for i in dir(sys.modules['__main__']):
    if i in urls:
        getattr(sys.modules['__main__'],i)()

5. 命名空间

Python 根据命名空间跟踪变量名、函数和类的作用域。比如:

def test():
    a = 1

变量 a 的作用域仅限于 test 函数中。

Python 中可用 locals 和 globals 分别获得当前局部作用域和全局作用域的符号信息。

locals 示例:

def hello(a=0):
    msg = "hello world"
    print(locals())

调用 hello 后输出:

{'msg': 'hello world', 'a': 0}

locals 保存了当前局部作用域中的变量、函数和类的信息,如上所示,输出了局部的 msg 和参数 n 的信息。globals 则是输出当前模块全部的,示例:

class TestClass(object):
    pass


def hello():
    pass


n = 0

print(globals())

输出:

{'__spec__': None, '__builtins__': <module 'builtins' (built-in)>, '__package__': None, '__file__': 'test.py', '__cached__': None, 'hello': <function hello at 0x7ff4bdb61f28>, '__name__': '__main__', 'n': 0, '__doc__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7ff4bdaa52b0>, 'TestClass': <class '__main__.TestClass'>}

输出中也可见定义的 TestClass 类、hello 函数和全局变量 n。