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 源码中看到以下经典的方法:
class Storage(dict): """ A Storage object is like a dictionary except obj.foo can be used in addition to obj['foo']. >>> o = storage(a=1) >>> o.a 1 >>> o['a'] 1 >>> o.a = 2 >>> o['a'] 2 >>> del o.a >>> o.a Traceback (most recent call last): ... AttributeError: 'a' """ def __getattr__(self, key): try: return self[key] except KeyError, k: raise AttributeError, k def __setattr__(self, key, value): self[key] = value def __delattr__(self, key): try: del self[key] except KeyError, k: raise AttributeError, k def __repr__(self): 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。