Python自省指南

Table of Contents

1 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

2 __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]

3 __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)()

4 命名空间

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。