2022年3月24日 星期四

Python 學習筆記 : 檢視物件成員與取得變數名稱字串的方法

在探索 Python 套件時, 常需要呼叫內建函式 dir() 來取得一個物件的所有成員串列, 透過走訪列印此串列元素即可一窺物件有哪些屬性與方法. 例如機器學習套件 Scikit-learn 有一個資料前處理模組  preprocessing, 在匯入後即可傳入 dir() 來觀察 :

>>> from sklearn import preprocessing   
>>> dir(preprocessing)      
['Binarizer', 'CategoricalEncoder', 'FunctionTransformer', 'Imputer', 'KBinsDiscretizer', 'KernelCenterer', 'LabelBinarizer', 'LabelEncoder', 'MaxAbsScaler', 'MinMaxScaler', 'MultiLabelBinarizer', 'Normalizer', 'OneHotEncoder', 'OrdinalEncoder', 'PolynomialFeatures', 'PowerTransformer', 'QuantileTransformer', 'RobustScaler', 'StandardScaler', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '_discretization', '_encoders', '_function_transformer', 'add_dummy_feature', 'base', 'binarize', 'data', 'imputation', 'label', 'label_binarize', 'maxabs_scale', 'minmax_scale', 'normalize', 'power_transform', 'quantile_transform', 'robust_scale', 'scale']

但是這個串列輸出很簡略且包山包海 (例如還有以雙底線 __ 開頭結尾的內部變數), 並無法了解這些成員究竟哪些是變數哪些是函式, 解決辦法是利用內建函式 eval() 與 type(), 並配合字串的 startswith() 方法來過濾, 例如 : 

>>> members=dir(preprocessing)     
>>> for mbr in members:                         # 走訪成員
     obj=eval('preprocessing.' + mbr)        # 用 eval() 求值取得成員之參考
     if not mbr.startswith('_'):                     # 走訪所有不是 "_" 開頭的成員
         print(mbr, type(obj))                        # 印出成員名稱與類型

Binarizer <class 'type'>
CategoricalEncoder <class 'type'>
FunctionTransformer <class 'type'>
Imputer <class 'type'>
KBinsDiscretizer <class 'type'>
KernelCenterer <class 'type'>
LabelBinarizer <class 'type'>
LabelEncoder <class 'type'>
MaxAbsScaler <class 'type'>
MinMaxScaler <class 'type'>
MultiLabelBinarizer <class 'type'>
Normalizer <class 'type'>
OneHotEncoder <class 'type'>
OrdinalEncoder <class 'type'>
PolynomialFeatures <class 'type'>
PowerTransformer <class 'type'>
QuantileTransformer <class 'type'>
RobustScaler <class 'type'>
StandardScaler <class 'type'>
add_dummy_feature <class 'function'>
base <class 'module'>
binarize <class 'function'>
data <class 'module'>
imputation <class 'module'>
label <class 'module'>
label_binarize <class 'function'>
maxabs_scale <class 'function'>
minmax_scale <class 'function'>
normalize <class 'function'>
power_transform <class 'function'>
quantile_transform <class 'function'>
robust_scale <class 'function'>
scale <class 'function'>

參考 :


其中的關鍵是在 eval() 中必須傳入物件或模組名稱與其成員之組合字串, 例如 'preprocessing.Binarizer', 這個在檢視個別物件或模組時沒有問題, 但如果要把上面的程式碼寫成函式就會遇到如何取得一個變數的名稱字串問題.

簡言之, 上面的程式碼我想寫成如下函式 :

def list_members(parent_obj):
    members=dir(parent_obj)
    parent_obj_name=?????     
    for mbr in members:
        child_obj=eval(parent_obj_name + '.' + mbr) 
        if not mbr.startswith('_'):
          print(mbr, type(child_obj))    

但要如何取得傳入的物件名稱呢? 這當然不能套用傳入的物件 parent_obj, 這個是物件本身, 而我要的是它的名稱字串 'parent_obj'. 在谷歌大神幫助下找到下面這篇文章 : 


其中 "étale-cohomology" 所給的回覆最簡單, 解決辦法是使用可以檢視物件結構的內建函式 inspect() 來檢視 Frame 執行容器物件, 利用掃描容器內的執行物件比對是否為所傳入之物件, 符合就傳回其 key (物件名稱字串), 從而取得物件名稱, 程式碼如下 :

import inspect 
def varname(x): 
    return [k for k,v in inspect.currentframe().f_back.f_locals.items() if v is x][0]

參考 :


例如 :

>>> import inspect    
>>> def varname(x):      
  return [k for k,v in inspect.currentframe().f_back.f_locals.items() if v is x][0]     

>>> list1=[1, 2, 3]    
>>> varname(list1)       
'list1'   
>>> from sklearn import preprocessing    
>>> varname(preprocessing)        
'preprocessing'        

可見此方法確實能傳回物件的名稱字串, 這樣就可以把這個 varname() 函式用在上面的 list_members() 函式了, 完整的程式碼如下 :

import inspect 
def varname(x): 
    return [k for k,v in inspect.currentframe().f_back.f_locals.items() if v is x][0]
def list_members(parent_obj):
    members=dir(parent_obj)
    parent_obj_name=varname(parent_obj)       
    for mbr in members:
        child_obj=eval(parent_obj_name + '.' + mbr) 
        if not mbr.startswith('_'):
            print(mbr, type(child_obj))

例如 : 

>>> def list_members(parent_obj):    
    members=dir(parent_obj)   
    parent_obj_name=varname(parent_obj)       
    for mbr in members:   
        child_obj=eval(parent_obj_name + '.' + mbr)       
        if not mbr.startswith('_'):     
            print(mbr, type(child_obj))      
            
>>> list_members(preprocessing)    
Binarizer <class 'type'>
CategoricalEncoder <class 'type'>
FunctionTransformer <class 'type'>
Imputer <class 'type'>
KBinsDiscretizer <class 'type'>
KernelCenterer <class 'type'>
LabelBinarizer <class 'type'>
LabelEncoder <class 'type'>
MaxAbsScaler <class 'type'>
MinMaxScaler <class 'type'>
MultiLabelBinarizer <class 'type'>
Normalizer <class 'type'>
OneHotEncoder <class 'type'>
OrdinalEncoder <class 'type'>
PolynomialFeatures <class 'type'>
PowerTransformer <class 'type'>
QuantileTransformer <class 'type'>
RobustScaler <class 'type'>
StandardScaler <class 'type'>
add_dummy_feature <class 'function'>
base <class 'module'>
binarize <class 'function'>
data <class 'module'>
imputation <class 'module'>
label <class 'module'>
label_binarize <class 'function'>
maxabs_scale <class 'function'>
minmax_scale <class 'function'>
normalize <class 'function'>
power_transform <class 'function'>
quantile_transform <class 'function'>
robust_scale <class 'function'>
scale <class 'function'>

Bingo! 收工! 

2022-07-08 補充 :

我把上面的程式命名為 members.py 模組, 放在 GitHub 儲存庫 :


下載到工作目錄後匯入此模組, 然後呼叫其 list_members() 函式並傳入物件或模組即可, 例如 :

>>> from PIL import ImageColor   
>>> import members as mbr   
>>> mbr.list_members(ImageColor)      
Image <class 'module'>
colormap <class 'dict'>
getcolor <class 'function'>
getrgb <class 'function'>
re <class 'module'>

可見 PIL 套件下的 ImageColor 模組有 5 個公開成員. 

沒有留言:

張貼留言