自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

妙用Hook來研究Python的Import機(jī)制

開發(fā) 開發(fā)工具
這兩天周末在家學(xué)習(xí)Python,我發(fā)現(xiàn)我們平常接觸最多的也就是import這條語句,這兩天在編寫一些程序的時(shí)候恰恰需要import hook去完成一些操作,借著這個(gè)周末在家閑著沒事兒通過import hook這個(gè)命令,把Python的import機(jī)制了解了一下。

[[181431]]

這兩天周末在家學(xué)習(xí)Python,我發(fā)現(xiàn)我們平常接觸最多的也就是import這條語句,這兩天在編寫一些程序的時(shí)候恰恰需要import hook去完成一些操作,借著這個(gè)周末在家閑著沒事兒通過import hook這個(gè)命令,把Python的import機(jī)制了解了一下。

0x00 Import機(jī)制概述

從名字上可以推斷出,import hook這個(gè)命令是和Python的導(dǎo)入機(jī)制有所關(guān)聯(lián)。再具體一點(diǎn)的話,import hook的作用是把我們自己寫的腳本直接注入到Python導(dǎo)入的例行操作里去。如果還要繼續(xù)往下說的話,那我們首先應(yīng)該來了解一下import默認(rèn)的時(shí)候是如何處理的。

對(duì)于我們來說的話,其實(shí)這個(gè)過程比較簡(jiǎn)單:當(dāng)Python的解釋器遇到import語句的時(shí)候,它回去查閱sys.path里面所有已經(jīng)儲(chǔ)存的目錄。這個(gè)列表初始化的時(shí)候,通常包含一些來自外部的庫(external libraries)或者是來自操作系統(tǒng)的一些庫,當(dāng)然也會(huì)有一些類似于dist-package的標(biāo)準(zhǔn)庫在里面。這些目錄通常是被按照順序或者是直接去搜索想要的:如果說他們當(dāng)中的一個(gè)包含有期望的package或者是module,這個(gè)package或者是module將會(huì)在整個(gè)過程結(jié)束的時(shí)候被直接提取出來。

我們可以寫一段代碼來演示一下ImportError,運(yùn)行下面的代碼的時(shí)候,我們會(huì)catch一個(gè)exception,在程序結(jié)束之前,它可能會(huì)嘗試多個(gè)imports。

  1. #!/usr/bin/env python 
  2. #coding=utf8 
  3. try: 
  4.     # Python 2.7-3.x 
  5.     import json 
  6. except ImportError: 
  7.     try: 
  8.         # Python 2.6 
  9.         import simplejson as json 
  10.     except ImportError: 
  11.         try: 
  12.              from django.utils import simplejson as json 
  13.          except ImportError: 
  14.              raise Exception("Requires a JSON package!"

雖然說這段sample寫的很不beautiful,但是他可以在一定程度上增加我們寫的程序或者package的可以執(zhí)行。慶幸的是我們僅僅需要用這種方式去處理極少數(shù)有價(jià)值的庫,比如說代碼中的Json庫。

0x01 關(guān)于__path__的更多細(xì)節(jié)

上文中提到的Python的Import流在大多數(shù)情況下是想描述一樣有用的,但是事實(shí)上遠(yuǎn)不止這些。他省略了一些我們可以根據(jù)需要調(diào)節(jié)的地方。

首先,__path__這個(gè)屬性是我們可以在__init__.py里面去定義的。你可以認(rèn)為他像一個(gè)sys.path的本地?cái)U(kuò)展并且只服務(wù)于我們導(dǎo)入的package的子模塊。換句話說,它包含目錄時(shí)應(yīng)該尋找一個(gè)package的子模塊被導(dǎo)入。默認(rèn)的情況下只有__init__.py的目錄,但是他可以擴(kuò)展到包含任何其他任何的路徑。

舉一個(gè)典型的例子就是把一些邏輯上的package分割成多個(gè)實(shí)際上的package,其實(shí)就是分割成多個(gè)distribution,一般情況下是不同的pypi包。舉個(gè)例子,讓我們假設(shè)構(gòu)造一個(gè)test.package,里面包含有test.client和test.server,他們?cè)趐ypi注冊(cè)的時(shí)候是按照兩個(gè)不同的distribution去注冊(cè)的,這樣的話用戶可以選擇其中的一個(gè)或多個(gè)distribution去安裝。我們需要設(shè)置test.__path__讓他們?nèi)ブ赶騮est.server和test.client的目錄(如果你只安裝了一個(gè)distribution的話只需要設(shè)置一個(gè))。聽上去好像有點(diǎn)復(fù)雜,實(shí)際上Python有一個(gè)模塊叫做pkgutil,這個(gè)模塊的作用就是讓我們很輕松的去實(shí)現(xiàn)上述的功能,你只需要在test/__init__.py下面添加一下兩行就可以了。

  1. import pkgutil  
  2. __path__ = pkgutil.extend_path(__path__, __name__) 

其實(shí)還有比這個(gè)還簡(jiǎn)單的方法,這里推薦一個(gè)文章給大家:http://doughellmann.com/PyMOTW/

0x02 真·鉤子:sys.meta_path和sys.path_hooks

讓我們繼續(xù),接著我們就會(huì)去分析import的過程,其實(shí)這部分正是這篇文章的重點(diǎn)。截下來說的比如說從zip文件或者是repo里面字節(jié)獲取模塊,或者是動(dòng)態(tài)的去用各種方法建立它們,比如說是web服務(wù)、dll或者是RESTful API等等幾乎你可以想到的任何的方法。我也會(huì)提到一些各個(gè)獨(dú)立模塊之間拿坑爹的交互性,比如說一個(gè)package檢測(cè)到自己被導(dǎo)入的時(shí)候,它能夠適應(yīng)和擴(kuò)展自己的接口。接著我們將會(huì)討論一下Python的安全增強(qiáng)沙箱,這個(gè)沙箱的作用是用來拒絕訪問某些模塊或者是改變其某些功能。

這些功能其實(shí)都可以通過import hooks來實(shí)現(xiàn)。有兩種不同的hook,一種叫做meta hook(sys.meta_path),另一種叫做path hook(sys.path_hooks)。盡管他們?cè)趦蓚€(gè)差不多的導(dǎo)入流的階段被調(diào)用,但是他們被創(chuàng)建的時(shí)候還是會(huì)取決于兩個(gè)東西,一個(gè)叫做模塊查找器(Module Finder),一個(gè)叫做模塊加載器(Module Loader)。

模塊查找器其實(shí)是一種簡(jiǎn)單的用來查找模塊的對(duì)象,他(find_module)的使用方法如下面所示:

  1. finder.find_module(fullname, path=None) 

他需要把一個(gè)完整的模塊的名字當(dāng)做參數(shù)傳進(jìn)去,path則為這個(gè)模塊的路徑。這個(gè)對(duì)象的可以完成以下三件事中的任意一件:

  • 拋出一個(gè)異常,然后完全取消所有的導(dǎo)入流程
  • 返回一個(gè)None,意思是被導(dǎo)入的這個(gè)模塊不能夠被這個(gè)查找器所找到。但是他仍然可以被導(dǎo)入流的下一個(gè)階段所找到,比如說一些自定義的查找器或者是Python的標(biāo)準(zhǔn)導(dǎo)入機(jī)制。
  • 返回一個(gè)加載器對(duì)象用來加載實(shí)際的模塊。

下一個(gè)就是模塊加載器,模塊加載器其實(shí)就是一個(gè)用來加載制定模塊的對(duì)象,它(load_module)的使用方法如下面的代碼所示:

  1. loader.load_module(fullname) 

這里需要在強(qiáng)調(diào)一次,fullname參數(shù)需要傳進(jìn)去一個(gè)我們想要加載的模塊的全名。返回值應(yīng)當(dāng)是一個(gè)模塊的對(duì)象,***的結(jié)果當(dāng)然就是完成導(dǎo)入對(duì)象的操作。需要注意的是,這些模塊可能已經(jīng)被導(dǎo)入了,或者是復(fù)制這些模塊的功能用來返回這些已經(jīng)存在的模塊。下面是這個(gè)函數(shù)的原型:

  1. def load_module(self, fullname): 
  2. if fullname in sys.modules: return sys.modules[fullname] 

如果在這一階段出現(xiàn)了任何錯(cuò)誤,模塊加載器應(yīng)該拋出一個(gè)ImportError的異常

0x03 自己構(gòu)造一個(gè)加載器:

上面這些僅僅是一些理論,其實(shí)吧PEP302標(biāo)準(zhǔn)里面都描述了這些。在實(shí)際當(dāng)中,其實(shí)模塊加載器和模塊查找器可以是同一個(gè)對(duì)象,也就是說find_module可以去return self。舉個(gè)例子,其實(shí)這個(gè)簡(jiǎn)單的hook可以去阻止任何特定的模塊被導(dǎo)入:

  1. #!/usr/bin/env python 
  2. #coding=utf8 
  3. import sys 
  4. class ImportBlocker(object): 
  5.     def __init__(self, *args): 
  6.         self.module_names = args 
  7.     def find_module(self, fullname, path=None): 
  8.         if fullname in self.module_names: 
  9.             return self 
  10.         return None 
  11.     def load_module(self, name): 
  12.         raise ImportError("%s is blocked and cannot be imported" % name
  13. sys.meta_path = [ImportBlocker('httplib')] 

一旦我們?cè)趕ys.meta_path中加載了這個(gè)hook,他就會(huì)去阻止任何導(dǎo)入的新模塊并且檢查他是否存在于我們的列表里。如果我們?nèi)ナ褂肦equest庫的時(shí)候,這個(gè)hook也會(huì)同樣起作用。

Import Request

執(zhí)行這條語句會(huì)失敗,因?yàn)閞equest是在urllib3內(nèi)部使用的,進(jìn)而去限制httplib的使用。但是一個(gè)hook要是沒事兒干總?cè)r截調(diào)用別的模塊似乎沒啥太大的意思,咱們換個(gè)別的玩法。如果說總是拒絕調(diào)用特定的模塊,我們?yōu)樯恫挥靡粋€(gè)warning去代替呢?這樣的話,這個(gè)hook就可以幫我們檢測(cè)被導(dǎo)入到項(xiàng)目當(dāng)中又被棄用的模塊。代碼如下:

  1. # !/usr/bin/env python 
  2. # coding=utf-8 
  3. import logging 
  4. import imp 
  5. import sys 
  6. class WarnOnImport(object): 
  7.     def __init__(self, *args): 
  8.         self.module_names = args 
  9.     def find_module(self, fullname, path=None): 
  10.         if fullname in self.module_names: 
  11.             self.path = path 
  12.             return self 
  13.         return None 
  14.     def load_module(self, name): 
  15.         if name in sys.modules: 
  16.             return sys.modules[name
  17.         module_info = imp.find_module(name, self.path) 
  18.         module = imp.load_module(name, *module_info) 
  19.         sys.modules[name] = module 
  20.         logging.warning("Imported deprecated module %s"name
  21.         return module 
  22. sys.meta_path = [WarnOnImport('getopt''optparse')] 

為了去訪問一個(gè)正常的導(dǎo)入機(jī)制,我們可以嘗試使用imp。它的find_module和load_module函數(shù)和我們要導(dǎo)入的hook具有相同的名字。但是imp提供的功能更強(qiáng)大,比如說還包括了load_source和load_compile這些功能甚至可以從頭來初始化一個(gè)模塊(new_module)。

責(zé)任編輯:武曉燕 來源: elknot
相關(guān)推薦

2024-12-30 08:02:40

2022-02-17 20:34:12

Python短路機(jī)制開發(fā)

2009-09-18 19:14:29

Hook機(jī)制

2021-03-16 21:45:59

Python Resize機(jī)制

2021-08-12 15:45:23

Pythonimport模塊

2024-05-28 12:25:33

Pythonglobals?函數(shù)

2024-12-19 09:00:00

字典視圖對(duì)象Python

2013-12-16 09:44:01

OpenDayLighSDN網(wǎng)絡(luò)轉(zhuǎn)發(fā)

2012-06-14 10:08:18

2022-06-07 08:59:58

hookuseRequestReact 項(xiàng)目

2020-09-25 10:14:54

漏洞

2023-01-03 10:37:22

CSS動(dòng)畫

2021-10-20 07:36:03

Python構(gòu)造方法

2022-04-01 07:14:13

模塊Pythonimport

2010-09-08 16:26:26

SQL循環(huán)語句

2023-08-01 09:46:57

虛擬鍵盤API

2010-09-26 09:50:36

SQL Where子句

2023-11-27 19:35:01

C++extern

2011-03-15 14:17:28

Oracle自主訪問

2010-03-10 17:57:54

Python編程語言
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)