查找Python包的依賴包(語句)


Window 10家庭中文版,Python 3.6.4,

 

今天看完了urllib3的官文(官方文檔),因為沒有具體使用過,所以,仍然是一知半解,但是,突然想知道 urllib3以及前面學習過的requests模塊都依賴了什么其它模塊。

於是,就有了一段200來行的程序和本文了。

 

功能名稱:

查找Python包的依賴包(語句)

功能介紹:

找到Python包(包括子目錄)中所有Python語句中的from、import語句,from語句獲取import前面的部分,import語句獲取整行。

使用方法:

使用包的絕對路徑建立類ModuleYilai(模塊依賴)的實例,然后調用實例方法yilais就可以獲得Python包的依賴包呢,以列表形式返回。

程序介紹:

class ModuleYilai

查找包依賴類;

def ispackage(dirpath)

檢查文件夾是否是Python包,判斷是否含有__init__.py文件;

def get_all_dirs(dirpath, level=True)

獲取給定dirpath目錄及其子目錄的絕對路徑的列表,level為True時包含目錄本身;用到了遞歸,內部使用時,level為False;

def get_all_pyfiles(dirpath)

獲取目錄下的所有Python文件(*.py);

def get_pyfile_yilais(pyfile)

獲取Python文件中所有的from子句、import子句;

需要說明的是,在建立正則表達時是,會忽略了from子句、import子句位於文件開頭的情況,故程序中對from子句、import子句分別使用了兩次正則匹配;

當然,from子句、import子句的規則還是挺多了,目前程序未能完美(100%)匹配,因此,尚有改進空間,很大;

 

代碼如下(就不添加行號了,方便大家復制):

'''
獲取一個Python包的依賴包(語句列表)

2018-06-24 1625:第一版,並不完善,短時間內也不准備完善了
'''
import os
import logging
import re

zllog = logging.getLogger("zl.yilai")

class ModuleYilai:
    '''
    找到模塊依賴的模塊:
    
    '''
    __module_path = ''
    __module_name = ''
    __yilai_modules = set()
    
    def __init__(self, module_path):
        '''
        輸入模塊的安裝路徑:絕對路徑,不能為根目路“/”
        '''
        zllog.debug('ModuleYilai.__init__')
        
        # 檢查module_path是否符合要求
        if not isinstance(module_path, str):
            print('moule_path (%s) is not str' % type(module_path))
            return
        
        if len(module_path) == 1:
            print('the length of module_path (%s) is 1' % module_path)
            return
        
        if not os.path.isabs(module_path):
            print('module_path (%s) is not an absolute path' % module_path)
            return
        
        if not os.path.isdir(module_path):
            print('module_path (%s) is not a directory' % module_path)
            return
        
        if not os.path.exists(module_path):
            print('module path (%s) does not exist' % module_path)
            return
        
        if module_path.endswith('//') or module_path.endswith('\\\\'):
            print('too many forward slashes or back slashes in the end of the module_path (%s)' % module_path)
            return
        
        # 目錄下是否有__init__.py文件
        # 存在此文件,那么,這是一個package
        dl = os.listdir(module_path)
        try:
            dl.index('__init__.py')
        except:
            print('module_path (%s) is not a package: there is no __init__.py' % module_path)
            return
        
        # 檢查完畢后,設置內部_module_path
        self.__module_path = module_path
        
        # 找出模塊名稱
        temp_path = module_path
        if temp_path.endswith('/') or temp_path.endswith('\\'):
            print('module_path processing...')
            temp_path = temp_path[:len(temp_path) - 1]
        
        last_slash_index = temp_path.rfind('/')
        if last_slash_index < 0:
            last_slash_index = temp_path.rfind('\\')
        
        self.__module_name = temp_path[last_slash_index + 1:]
        
        # 尋找模塊依賴,並將找打的依賴模塊存放到_yilai_modules中
        self._search_yilais()
    
    # 尋找模塊依賴
    def _search_yilais(self):
        if self.__module_path == '':
            return
        
        # 1.找到模塊下每一個目錄(包括目錄本身)
        dirlist = get_all_dirs(self.__module_path)
        zllog.debug('length of dirlist: ', len(dirlist))
        
        # 2.找到模塊下每一個模塊文件(*.py),將其絕對路徑存入列表中
        pyfiles = []
        for item in dirlist:
            pyfiles.extend(get_all_pyfiles(item))
        zllog.debug('length of pyfiles: ', len(pyfiles))
        
        # 3.找到每一個模塊文件的依賴模塊
        fileyilais = []
        for item in pyfiles:
            fileyilais.extend(get_pyfile_yilais(item))
        zllog.debug('length of fileyilais: ', len(fileyilais))
        
        # 4.將fileyilais轉換為set並將其存入實例的_yilai_modules中
        self.__yilai_modules = set(fileyilais)
        zllog.debug('length of self.__yilai_modules: ', len(self.__yilai_modules))
    
    # 獲取模塊名稱
    def mod_name(self):
        return self.__module_name
    
    # 獲取依賴的包的列表
    def yilais(self):
        return list(self.__yilai_modules)

# 判斷一個文件夾是否是Python包
def ispackage(dirpath):
    try:
        dl = os.listdir(dirpath)
        dl.index('__init__.py')
        return True
    except:
        return False

# 找到dirpath下所有目錄(包括目錄本身),以列表形式返回
# 遞歸算法
# level為True時,添加目錄本身,否則,不添加(查找子目錄下的目錄時不添加)
def get_all_dirs(dirpath, level=True):
    # 統一使用UNIX樣式路徑分隔符(/)
    # 替換后,Windows下也可以運行
    dirpath = dirpath.replace('\\', '/')
    
    dirlist = []
    
    # 添加目錄自身
    if level:
        dirlist.append(dirpath)
    
    dl = os.listdir(dirpath)
    
    # 排除其中的__pycache__和test文件夾
    try:
        dl.remove('__pycache__')
        dl.remove('test')
    except:
        pass
    
    for item in dl:
        itempath = dirpath + '/' + item
        if os.path.isdir(itempath):
            # 將目錄添加到返回列表中
            dirlist.append(itempath)
            
            # 執行get_all_dirs獲取其下的目錄並添加到dirlist中!
            dirlist.extend(get_all_dirs(itempath, level=False))
    
    return dirlist

# 找到diapath下所有Python模塊(*.py文件),以列表形式返回
# dirpath為絕對路徑
def get_all_pyfiles(dirpath):
    # 統一使用UNIX樣式路徑分隔符(/)
    # 替換后,Windows下也可以運行
    dirpath = dirpath.replace('\\', '/')
    
    rs = []
    
    if not os.path.isdir(dirpath):
        return
    
    dl = os.listdir(dirpath)
    for item in dl:
        itempath = dirpath + '/' + item
        # 檢查是否是文件,是否要是py文件
        if os.path.isfile(itempath) and item.endswith('.py'):
            rs.append(itempath)
    
    return rs

# 獲取一個Python模塊(.py文件)導入的包
# 結果以列表形式返回
# 
# 可能的形式:
# 1.import sys
# 2.from __future__ import absolute_import
# 3.from socket import error as SocketError, timeout as SocketTimeout
# 4.
# from .connection import (
#     port_by_scheme,
#     DummyConnection,
#     HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection,
#     HTTPException, BaseSSLError,
# )
# 5.
# if six.PY2:
#     # Queue is imported for side effects on MS Windows
#     import Queue as _unused_module_Queue  # noqa: F401
# 6.import mod1, mod2, mod3
# 7....
def get_pyfile_yilais(pyfile):
    '''
    格式很多,尚未完善!!
    '''
    rs = []
    
    if not os.path.isfile(pyfile):
        print('[get_pyfile_yilais] pyfile (%s) is not a file.' % pyfile)
        return rs
    
    with open(pyfile, 'r', encoding='utf-8') as f:
        content = f.read()
        
        #rs1 = re.findall('\n(from\s+.+)\s', content)
        # from可以在文件的開頭,或者一行的開頭,或者注釋中,需要前面兩種
        rs1 = re.findall('^(from\s+[\_\.0-9a-zA-Z]+)\s', content)
        rs2 = re.findall('\n(from\s+[\_\.0-9a-zA-Z]+)\s', content)
        #print('rs1 = ', rs1)
        #print('rs2 = ', rs2)
        
        rs3 = re.findall('^(import\s+.+)', content)
        rs4 = re.findall('\n(import\s+.+)', content)
        #print('rs3 = ', rs3)
        #print('rs4 = ', rs4)
        rs = rs1 + rs2 + rs3 + rs4
    
    return rs

if __name__ == '__main__':
    # 一些測試
    #m1 = ModuleYilai('C:\\Python36\\Lib\\sqlite3')
    #m2 = ModuleYilai('C:\\Python36\\Lib\\sqlite3\\')
    #m3 = ModuleYilai('C:\\Python36\\Lib')
    #m4 = ModuleYilai('C:\\Python36\\Lib\\')
    #m5 = ModuleYilai('C:/Python36/Lib/sqlite3')
    #m6 = ModuleYilai('C:/Python36/Lib/sqlite3/')
    #m7 = ModuleYilai('C:/Python36/Lib/sqlite3//')
    #m8 = ModuleYilai('C')
    #m9 = ModuleYilai('/')
    #m10 = ModuleYilai('\\')
    
    # 測試get_pyfile_yilais
    #get_pyfile_yilais('C:\\Python36\\Lib\\sqlite3\\dbapi2.py')
    #print()
    #get_pyfile_yilais('C:\\Python36\\Lib\\site-packages\\urllib3\\connectionpool.py')
    
    # 測試get_all_dirs
    #retlist = get_all_dirs('C:\\Python36\\Lib\\site-packages\\urllib3')
    #for item in retlist:
    #    print(item)
    
    # Test urllib3 module
    m1 = ModuleYilai('C:\\Python36\\Lib\\site-packages\\urllib3')
    print('module name: ', m1.mod_name())
    print('length: ', len(m1.yilais()))
    print(m1.yilais())
    print()
    
    # Test of requests module
    m2 = ModuleYilai('C:\\Python36\\Lib\\site-packages\\requests')
    print('module name: ', m2.mod_name())
    print('length: ', len(m2.yilais()))
    print(m2.yilais())
    print()
    
    # Test of D:\\Users\\log
    m3 = ModuleYilai('D:\\Users\\log')
    print('module name: ', m3.mod_name())
    print('length: ', len(m3.yilais()))
    print(m3.yilais())
mdeps.py

 

測試結果:

 

改進方式:

本想在程序中使用logging記錄日志,發現它和print混用時在Console輸出的消息是混亂的;

使用setLevel函數設置了日志優先級,可是,沒能輸出想要的調試信息,還需繼續研究;

程序使用自己的雜亂的方法進行了測試,更高質量的測試工具unittest、pytest沒能用到;

對於from子句、import的抓取,目前只使用了兩個簡單的規則,其它的尚需進一步完善,但孤可能不會繼續了;

對模塊以一個點、兩個點開頭的方式沒有做處理;

代碼中的def ispackage(dirpath)函數沒有用到,本想檢測目錄是否是Python包的,可自行添加;

 

本次收獲:

寫了一個Python類了,還用到了兩個下划線開頭的私有變量;

用了一些list、set、str的方法呢;

練習了正則表達式的使用,不過,還是不很熟練,需提高;

輕車熟路地使用了with語句;

今天添加了幾個小時的認真時間;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM