inspect – 探測 Python 物件內容
The
inspect
module provides several useful functions to help get information about live objects such as modules, classes, methods, functions, tracebacks, frame objects, and code objects. For example, it can help you examine the contents of a class, retrieve the source code of a method, extract and format the argument list for a function, or get all the information you need to display a detailed traceback.
Source code: Lib/inspect.py
在 Python 界,人人都能朗朗上口的一句話大概就是「Python 裏什麼東西都是物件」,既然如此,有沒有辦法在 runtime 得到這些資訊呢?答案是可以的,透過 inspect
函式庫,我們可以探測幾乎任何的東西 (凡舉模組、類別、函式、traceback、frame 甚至是 code 的資訊!)。inspect
主要提供四大功能:型態檢查、獲取程式碼、探測類別與函式、檢查直譯器堆疊。
01. Quickstart Tutorial
首先我們來使用 inspect
檢查物件的型態!
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> import inspect >>> inspect.ismodule(inspect) # 檢查 inspect 是否為模組 True >>> inspect.ismethod(inspect) # 檢查 inspect 是否為物件方法 False >>> inspect.isfunction(len) # 檢查 len 是否為函式 True >>> inspect.isbuiltin(len) # 檢查 len 是否為內建函式 True >>> inspect.isawaitable(inspect) # 檢查 inspect 是否可用於 await expression False >>> |
我們也可以透過 inspect
取得物件內的資訊,例如說物件的 docstring、comment 甚至是 source code:
1 2 3 4 5 6 7 |
>>> inspect.getdoc(len) # 取得 len 的 docstring ‘Return the number of items in a container.’ >>> inspect.getsource(inspect) # 取得 inspect 的 source code ‘”””Get useful information from live Python objects.\n\n This module encapsulates the interface provided by the internal special\nattributes (co_*, im_*, tb_*, etc.) in …’ >>> |
透過 inspect.getmembers
,我們可以取得一個物件裏面所有的 member:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
>>> inspect.getmembers(len) [(‘__call__’, <method–wrapper ‘__call__’ of builtin_function_or_method object at 0x7f85af1491c0>), (‘__class__’, <class ‘builtin_function_or_method’>), (‘__delattr__’, ..., (‘__setattr__’, <method–wrapper ‘__setattr__’ of builtin_function_or_method object at 0x7f85af1491c0>), (‘__sizeof__’, <built–in method __sizeof__ of builtin_function_or_method object at 0x7f85af1491c0>), (‘__str__’, <method–wrapper ‘__str__’ of builtin_function_or_method object at 0x7f85af1491c0>), (‘__subclasshook__’, <built–in method __subclasshook__ of type object at 0x899760>), (‘__text_signature__’, ‘($module, obj, /)’)] >>> import tabnanny >>> inspect.getmembers(tabnanny, inspect.isfunction) # get only function [(‘check’, <function check at 0x7f85acbccbd8>), (‘errprint’, <function errprint at 0x7f85acbcca68>), (‘format_witnesses’, <function format_witnesses at 0x7f85acbccf70>), (‘main’, <function main at 0x7f85acbccb20>), (‘process_tokens’, <function process_tokens at 0x7f85acb57560>)] |
不過最有趣以及有用的,還是這個 inspect.signature
,可以獲得 callable 的 Signature 物件:
1 2 3 4 5 6 7 8 9 10 |
>>> def foo(name, a: int, b: float): ... pass ... >>> sig = inspect.signature(foo) >>> sig <Signature (name, a:int, b:float)> >>> str(sig) ‘(name, a:int, b:float)’ >>> sig.parameters mappingproxy(OrderedDict([(‘name’, <Parameter “name”>), (‘a’, <Parameter “a:int”>), (‘b’, <Parameter “b:float”>)])) |
透過 Signature.bind
,我們可以把參數給綁定到 signature 上:
1 2 3 4 5 6 7 8 9 10 11 |
>>> args = (‘foobar’, 10) >>> kwargs = {‘b’: 23.4} >>> bound = sig.bind(*args, **kwargs) # bind args to signature parameters >>> bound <BoundArguments (name=‘foobar’, a=10, b=23.4)> >>> bound.arguments[‘name’] ‘foobar’ >>> bound.arguments[‘a’] 10 >>> bound.arguments[‘b’] 23.4 |
02. HOW-TO Guides
透過 signature.bind
以及 annotation 來做型態檢查
前面提到的 signature.bind
看似無用,但是卻可以用在型態檢查上面。在 PEP 484 中,提到了如何在 Python 裏面使用型態提示:
1 2 3 4 5 6 7 |
>>> def gcd(a: int, b: int) -> int: ... while b: ... a, b = b, a % b ... return b ... >>> gcd(27, 36) 9 |
上面的 gcd
函式中,我們為參數 a 以及參數 b 加上型態提示為 int,而函式回傳值的型態提示為 int。只是這個很廢只有提示功能,我們可以嘗試丟入 float 數值看看:
1 2 |
>>> gcd(2.7, 3.6) 4.440892098500626e–16 |
Python 會很開心的忽略 annotation,回傳一個神奇的 float 數值給你。
要解決這個問題,我們可以手動的使用 assert
來檢查型態:
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> def gcd(a: int, b: int) -> int: ... assert isinstance(a, int), ‘Expected int’ ... assert isinstance(b, int), ‘Expected int’ ... while b: ... a, b = b, a % b ... return a ... >>> gcd(2.7, 3.6) Traceback (most recent call last): File “<stdin>”, line 1, in <module> File “<stdin>”, line 2, in gcd AssertionError: Expected int |
但是這根本沒有發揮 type hint 的效果啊!
額外加上一個 wrapper 來解決這個問題:
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 |
>>> from functools import wraps >>> def checked(func): ... ann = func.__annotations__ ... sig = inspect.signature(func) ... @wraps(func) ... def wrapper(*args, **kwargs): ... bound = sig.bind(*args, **kwargs) ... for name, val in bound.arguments.items(): ... if name in ann: ... assert isinstance(val, ann[name]), \ ... f‘Expected {ann[name]}’ ... return func(*args, **kwargs) ... return wrapper ... >>> @checked ... def gcd(a: int, b: int) -> int: ... while b: ... a, b = b, a % b ... return a ... >>> gcd(2.7, 3.6) Traceback (most recent call last): File “<stdin>”, line 1, in <module> File “<stdin>”, line 9, in wrapper AssertionError: Expected <class ‘int’> >>> gcd(27, 36) 9 >>> |
如此一來就能活用 inspect 來達到使用 annotation 檢查型態的功能了!
03. Discussions
暫時沒有。
1 2 |
04. References
- 29.12. inspect — Inspect live objects
- PEP 362 – Function Signature Object
- The Fun of Reinvention (Screencast) – inspect.signature example
Leave a Reply