国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Python: 受限制的 "函數(shù)調(diào)用"

Mr_houzi / 3470人閱讀

摘要:需求背景最近在工作上遇到了一個比較特殊的需求為了安全設(shè)計一個函數(shù)或者裝飾器然后用戶在定義調(diào)用函數(shù)時只能訪問到我們允許的內(nèi)置變量和全局變量通過例子來這解釋下上面的需求輸出函數(shù)功能簡單明了對于結(jié)果大家應(yīng)該也不會有太大的異議分別是取得全局命名空間

需求背景

最近在工作上, 遇到了一個比較特殊的需求:

   為了安全, 設(shè)計一個函數(shù)或者裝飾器, 然后用戶在 "定義/調(diào)用" 函數(shù)時, 只能訪問到我們允許的內(nèi)置變量和全局變量

通過例子來這解釋下上面的需求:

a = 123
def func():
    print  a
    print id(a)

func()   

# 輸出
123
32081168

函數(shù)功能簡單明了, 對于結(jié)果, 大家應(yīng)該也不會有太大的異議:func分別是取得全局命名空間a的值和使用內(nèi)置命名空間中的函數(shù)id獲取了a的地址. 熟悉Python的童鞋, 對于LEGB肯定也是不陌生的,也正是因?yàn)?b>LEGB才讓函數(shù)func輸出正確的結(jié)果. 但是這個只是一個常規(guī)例子, 只是用來拋磚引玉而已. 我們真正想要討論的是下面的例子:

# 裝飾函數(shù)
def wrap(f):
    # 調(diào)用用戶傳入的函數(shù)
    f()

a = 123

# 用戶自定義函數(shù)
def func():
    import os
    print os.listdir(".")

wrap(func)
# 輸出
["1.yml", "2.py", "2.txt", "2.yml", "ftp", "ftp.rar", "test", "tmp", "__init__.py"]
潛在危險因素

在上面的例子可以看出, 如果在func中, 引入別的模塊, 然后再執(zhí)行模塊中的方法, 也是可行的! 而且這還是一個非常方便的功能! 但是除了方便, 更多的是一種潛在的危險.在日常使用, 或許我們不會考慮這些, 但是如果在模塊模塊之間的協(xié)同作用時, 特別是多人參與的情況下, 這種危險的因素, 就不得不讓我們認(rèn)真對待!

或許有很多同學(xué)會覺得這些擔(dān)憂是過多的, 是沒必要的, 但是請思考一種場景: 我們有個主模塊, 暫時稱為main.py, 它允許用戶動態(tài)加載模塊, 也就是說只要用戶將對應(yīng)的模塊放到對應(yīng)的目錄, 然后利用消息機(jī)制去通知main.py, 告訴它應(yīng)該加載新模塊了, 并且執(zhí)行新模塊里面的b函數(shù), 那在這種情況下, main.py肯定不能直接傻傻的就去執(zhí)行, 因?yàn)槲覀儾荒芟嘈琶總€用戶都是誠實(shí)善良的, 也不能相信每個用戶編寫的模塊或者函數(shù)是符合我們的行為標(biāo)準(zhǔn)規(guī)范. 所以我們得有些措施去防范這些事情, 我們能做的大概也就下面幾種方式:

1.在用戶通知`main.py`時有新模塊加入并且要求執(zhí)行函數(shù)時, 先對模塊的代碼做檢查, 不符合標(biāo)準(zhǔn)或者帶有危險代碼的拒絕加載.
2.控制好`內(nèi)置命名空間`和`全局命名空間`, 使其只能用允許使用的內(nèi)容

在方案1, 其實(shí)也是我們最容易想到的方法, 但是這個方法的成本還是比較高, 因?yàn)槲覀冃枰獙⒖赡艹霈F(xiàn)的錯誤代碼或者關(guān)鍵詞,全部寫成一套規(guī)則, 而且這套規(guī)則還很大可能會誤傷, 不過也可能業(yè)界已經(jīng)有類似的成熟的方案, 只是我還沒接觸到而已.
所以我們只能用方案2的方法, 這種方法在我們看來, 是成本比較低的, 也比較容易控制的, 因?yàn)檫@就和防火墻一樣, 我們只放行我們允許的事物.

具體實(shí)現(xiàn)

實(shí)現(xiàn)方案2最大的問題就是, 如何控制內(nèi)置命名空間全局命名空間
我們第一個想法肯定就是覆蓋它們, 因?yàn)槲覀兌贾啦还苁?b>內(nèi)置命名空間還是全局命名空間, 都是通過字典的形式在維護(hù):

print globals()
print globals()["__builtins__"].__dict__

# 輸出
# 全局命名空間
{"__builtins__": , "__name__": "__main__", "__file__": "D:/Python_project/ftp/2.py", "__doc__": None, "__package__": None}

#內(nèi)置命名空間
{"bytearray": , "IndexError": 

注: globals函數(shù) 是用來打印當(dāng)前全局命名空間的函數(shù), 同樣, 也能通過修改這個函數(shù)返回的字典對應(yīng)的key, 實(shí)現(xiàn)全局命名空間的修改.例如:

s = globals()
print s
s["a"] = 3
print s
print a

# 輸出
{"__builtins__": , "__file__": "D:/Python_project/ftp/2.py", "__package__": None, "s": {...}, "__name__": "__main__", "__doc__": None}
{"a": 3, "__builtins__": , "__file__": "D:/Python_project/ftp/2.py", "__package__": None, "s": {...}, "__name__": "__main__", "__doc__": None}
3

可以看出, 我們并沒有定義變量a, 只是在globals的返回值上面增加了key-value, 就變相實(shí)現(xiàn)了我們定義的操作, 這其實(shí)也能用于很多希望能夠動態(tài)賦值的需求場景! 比如說, 我不確定有多少個變量, 希望通過一個變量名列表, 動態(tài)生成這些變量, 在這種情況下, 就能參考這種方法, 不過還是希望謹(jǐn)慎使用, 因?yàn)樾薷牧诉@個, 就是就修改了全局命名空間.

好了, 回歸到本文, 我們已經(jīng)知道通過globals函數(shù)能夠代表全局命名空間, 但是為什么內(nèi)置命名空間要用globals()["__builtins__"].__dict__來表示? 其實(shí)這個和python自身的機(jī)制有關(guān), 因?yàn)槟K在編譯和初始化的過程中, 內(nèi)置命名空間就是以這種形式,寄放在全局命名空間:

static void
initmain(void)
{
    PyObject *m, *d;
    m = PyImport_AddModule("__main__");
    if (m == NULL)
        Py_FatalError("can"t create __main__ module");
    d = PyModule_GetDict(m);
    if (PyDict_GetItemString(d, "__builtins__") == NULL) {
        PyObject *bimod = PyImport_ImportModule("__builtin__");
        if (bimod == NULL ||
            PyDict_SetItemString(d, "__builtins__", bimod) != 0)
            Py_FatalError("can"t add __builtins__ to __main__");
        Py_XDECREF(bimod);
    }
}

從上面代碼可以看出, 在初始化__main__時, 會有一個獲取__builtins__的動作, 如果這個結(jié)果是NULL, 那么就會用之前初始化好的__builtin__去存進(jìn)去, 這些代碼具體可以看Pythonrun.c, 在這不詳細(xì)展開了.

既然內(nèi)置命名空間(__builtins__)全局命名空間(globals())都已經(jīng)找到對應(yīng)對象了, 那我們下一步就應(yīng)該是想法將這兩個空間替換成我們想要的.

# coding: utf8
# 修改全局命名空間
test_var = 123  # 測試變量

tmp = globals().keys()
print globals()
print test_var
for i in tmp:
    del globals()[i]
print globals()
print test_var
print id(2)

# 輸出

{"tmp": ["__builtins__", "__file__", "__package__", "test_var", "__name__", "__doc__"], "__builtins__": , "__file__": "D:/Python_project/ftp/2.py", "__package__": None, "test_var": 123, "__name__": "__main__", "__doc__": None}
123
{"tmp": ["__builtins__", "__file__", "__package__", "test_var", "__name__", "__doc__"], "i": "__doc__"}
Traceback (most recent call last):
  File "D:/Python_project/ftp/2.py", line 10, in 
    print test_var
NameError: name "test_var" is not defined

在上面的輸出可以看到, 在刪除前后, 通過print globals()可以看到全局命名空間確實(shí)已經(jīng)被修改了, 因?yàn)?b>test_var已經(jīng)無法打印了, 觸發(fā)了NameError, 這樣的話, 就有辦法能夠限制全局命令空間了:

# 偽代碼

# 裝飾函數(shù)
def wrap(f):
    # 調(diào)用用戶傳入的函數(shù)
    .... 修改全局命名空間
    f()
    .... 還原全局命名空間

a = 123

# 用戶自定義函數(shù)
def func():
    import os
    print os.listdir(".")

wrap(func)

為什么我只寫偽代碼, 因?yàn)槲野l(fā)現(xiàn)這個功能實(shí)現(xiàn)起來是非常蛋疼! 原因就是, 在實(shí)現(xiàn)之前, 我們必須要解決幾個問題:

1.全局命名空間對應(yīng)了一個字典, 所以如果我們想要修改, 只能從修改這個字典本身, 于是先清空再定義成我們約束的, 調(diào)用完之后, 又得反過來恢復(fù), 這些操作是十分之蛋疼.
2.涉及到共享的問題, 如果這個用戶函數(shù)處理很久, 而且是多線程的, 那么整個模塊都會變得很不穩(wěn)定, 甚至稱為"污染"

那就先撇開不講, 講講內(nèi)置命名空間, 剛才我們已經(jīng)找到了能代表內(nèi)置命名空間的對象, 很幸運(yùn)的是, 這個是"真的能夠摸得到"的, 那我們試下直接就賦值個空字典, 看會怎樣:

s = globals()
print s["__builtins__"]  # __builtins__檢查是否存在
s["__builtins__"] = {}
print s["__builtins__"]  # __builtins__檢查是否存在
print id(3)              # 試下內(nèi)置函數(shù)能否使用
print globals()

# 輸出

{}
32602360
{"__builtins__": {}, "__file__": "D:/Python_project/ftp/2.py", "__package__": None, "s": {...}, "__name__": "__main__", "__doc__": None}

結(jié)果有點(diǎn)尷尬, 似乎沒啥用, 但是其實(shí)這個__builtins__只是一個表現(xiàn), 真正的內(nèi)置命名空間是在它所指向的字典對象, 也就是: globals()["__builtins__"].__dict__!

print globals()["__builtins__"].__dict__

# 輸出
{"bytearray": , "IndexError": ....} # 省略

所以我們真正要覆蓋的, 是這個字典才對, 所以上面的代碼要改成:

s = globals()
s["__builtins__"].__dict__ = {}   # 覆蓋真正的內(nèi)置命名空間
print s["__builtins__"].__dict__  # __builtins__檢查是否存在

# 輸出
Traceback (most recent call last):
  File "D:/Python_project/ftp/2.py", line 3, in 
    s["__builtins__"].__dict__ = {}
TypeError: readonly attribute

失敗了...原來這個內(nèi)置命名空間是只讀的, 所以我們上面的方法都失敗了..那難道真的沒法解決了嗎? 一般這樣問, 通常都有解決方案滴~

完美方案

這個解決方法, 需要一個庫的幫忙~, 那就是inspect庫, 這個庫是干嘛呢? 簡單來說就是用來自省. 它提供四種用處:

1.對是否是模塊,框架,函數(shù)等進(jìn)行類型檢查。
2.獲取源碼
3.獲取類或函數(shù)的參數(shù)的信息
4.解析堆棧

在這里, 我們需要用到第二個功能, 其余的功能, 感興趣的童鞋可以去谷歌學(xué)習(xí)哦, 也可以參考: https://my.oschina.net/taisha...
除了inspect, 我們還需要用到exec, 這也是一大殺器, 可以先參考這個學(xué)習(xí)下: http://www.mojidong.com/pytho...

方法大致的過程就是以下幾步:

1.根據(jù)用戶傳入的func對象, 利用inspect取出對應(yīng)的源碼
2.通過exec利用源碼并且傳入全局命名空間, 重新編譯

代碼:

# coding: utf8
import inspect

# 裝飾函數(shù)
def wrap(f):
    # 調(diào)用用戶傳入的函數(shù)
    source = inspect.getsource(f)   # 獲取源碼
    exec("%s 
%s()" % (source,  f.func_name), {"a": "this is inspect", "__builtins__": {}})  # 重新編譯, 并且重新構(gòu)造全局命名空間


a = 123

# 用戶自定義函數(shù)
def func():
    print a
    import os
    print os.listdir(".")

wrap(func)

# 輸出
this is inspect
Traceback (most recent call last):
  File "D:/Python_project/ftp/2.py", line 19, in 
    wrap(func)
  File "D:/Python_project/ftp/2.py", line 8, in wrap
    exec("%s 
func()" % source, {"a": "this is inspect", "__builtins__": {}})
  File "", line 6, in 
  File "", line 3, in func
ImportError: __import__ not found

雖然上面報錯了, 但那不就我們求之不得結(jié)果嗎? 我們可以正確的輸出a的值this is inspe, 而且當(dāng)funcimport時, 直接報錯! 這樣就能滿足我們的{{BANNED}}欲望了~ 嘿嘿!,

關(guān)于代碼運(yùn)行原理, 其實(shí)在關(guān)鍵部位的代碼, 都已經(jīng)加了注釋, 可能在exec那部分會比較迷惑, 但其實(shí)大家將對應(yīng)的變量代入字符串就能懂了, 替換之后, 其實(shí)也就是函數(shù)的定義+執(zhí)行, 可以通過print "%s %s()" % (source, f.func_name)幫助理解.而后面的字典, 也就是我們一直很糾結(jié)的全局命名空間, 其中內(nèi)置命名空間也被人為定義了, 所以能夠達(dá)到我們想要的效果了!

這種只是一種拋磚引玉, 讓有類似場景需求的童鞋, 有個參考的方向, 也歡迎分享你們實(shí)現(xiàn)的方案, 嘿嘿!

歡迎各位大神指點(diǎn)交流,轉(zhuǎn)載請注明來源: https://segmentfault.com/a/11...

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/38621.html

相關(guān)文章

  • 處理python遞歸函數(shù)及遞歸算法頻次限制難題

      本文關(guān)鍵闡述了處理python遞歸函數(shù)及遞歸算法頻次受限制難題,具有非常好的實(shí)用價值,希望能幫助到大家。如有誤或者未考慮到真正的地區(qū),望鼎力相助  遞歸函數(shù)及遞歸算法頻次受限制  一個函數(shù)在外部啟用自身,那么這樣的函數(shù)是遞歸函數(shù)。遞歸算法要反復(fù)應(yīng)用自身,每遞歸算法一回,越近最后的值。如果一個難題需要由很多類似小問題處理,可以選擇應(yīng)用遞歸函數(shù)。伴隨著遞歸算法的深層次,難題經(jīng)營規(guī)模對比之前都應(yīng)該所...

    89542767 評論0 收藏0
  • pythonGUI多做輸入文本Text完成

      文章主要是詳細(xì)介紹了pythonGUI多做輸入文本Text的控制方式,具有非常好的實(shí)用價值,希望能幫助到大家。如有誤或者未考慮到真正的地區(qū),望鼎力相助  Text的屬性wrap  fromtkinterimport*   root=Tk()   root.geometry('200x300')   te=Text(root,height=20,width=15)   #將多做輸...

    89542767 評論0 收藏0
  • pythonGUI多列輸入文本Text完成

      此篇文章主要是詳細(xì)介紹了pythonGUI多列輸入文本Text的控制方式,具有非常好的實(shí)用價值,希望能幫助到大家。如有誤或者未考慮到真正的地區(qū),望鼎力相助  Text的屬性wrap  fromtkinterimport*   root=Tk()   root.geometry('200x300')   te=Text(root,height=20,width=15)   #將多...

    89542767 評論0 收藏0
  • 文章徹底搞懂Python類屬性和方法開啟

      對python調(diào)用類特性方式詳細(xì)描述檢驗(yàn)前提下類開啟也經(jīng)常需要用到的,下面文中重要給大家介紹了相關(guān)Python類屬性和方法的開啟的相關(guān)資料,從文中根據(jù)實(shí)例編號介紹的非常詳細(xì),務(wù)必的朋友可以參考一下  Python從技術(shù)的時候就已經(jīng)已是一類面向?qū)ο笳Z言表述,也正因?yàn)槿绱耍赑ython中打造一個類和對象是非常簡單的。  一、類、總體目標(biāo)概述  在C語言程序設(shè)計中,把數(shù)據(jù)和信息以及對業(yè)務(wù)操作流程封...

    89542767 評論0 收藏0
  • Python標(biāo)準(zhǔn)庫sys庫常用功能相關(guān)解答

      小編寫這篇文章的主要目的,就是給大家介紹關(guān)于Python標(biāo)準(zhǔn)庫sys常用功能的一些介紹,這樣對我們以后的工作也是很有幫助的,具體的介紹,下面就給大家詳細(xì)解答下。  1、查看版本信息  #coding:utf-8   importsys   #獲取Python版本信息   print(sys.version)   #獲取解釋器中C的API版本   print(sys.api_version)  ...

    89542767 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<