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

資訊專欄INFORMATION COLUMN

Python: 受限制的 "函數調用"

Mr_houzi / 3480人閱讀

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

需求背景

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

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

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

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

func()   

# 輸出
123
32081168

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

# 裝飾函數
def wrap(f):
    # 調用用戶傳入的函數
    f()

a = 123

# 用戶自定義函數
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中, 引入別的模塊, 然后再執行模塊中的方法, 也是可行的! 而且這還是一個非常方便的功能! 但是除了方便, 更多的是一種潛在的危險.在日常使用, 或許我們不會考慮這些, 但是如果在模塊模塊之間的協同作用時, 特別是多人參與的情況下, 這種危險的因素, 就不得不讓我們認真對待!

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

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

在方案1, 其實也是我們最容易想到的方法, 但是這個方法的成本還是比較高, 因為我們需要將可能出現的錯誤代碼或者關鍵詞,全部寫成一套規則, 而且這套規則還很大可能會誤傷, 不過也可能業界已經有類似的成熟的方案, 只是我還沒接觸到而已.
所以我們只能用方案2的方法, 這種方法在我們看來, 是成本比較低的, 也比較容易控制的, 因為這就和防火墻一樣, 我們只放行我們允許的事物.

具體實現

實現方案2最大的問題就是, 如何控制內置命名空間全局命名空間
我們第一個想法肯定就是覆蓋它們, 因為我們都知道不管是內置命名空間還是全局命名空間, 都是通過字典的形式在維護:

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

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

#內置命名空間
{"bytearray": , "IndexError": 

注: globals函數 是用來打印當前全局命名空間的函數, 同樣, 也能通過修改這個函數返回的字典對應的key, 實現全局命名空間的修改.例如:

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, 就變相實現了我們定義的操作, 這其實也能用于很多希望能夠動態賦值的需求場景! 比如說, 我不確定有多少個變量, 希望通過一個變量名列表, 動態生成這些變量, 在這種情況下, 就能參考這種方法, 不過還是希望謹慎使用, 因為修改了這個, 就是就修改了全局命名空間.

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

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__的動作, 如果這個結果是NULL, 那么就會用之前初始化好的__builtin__去存進去, 這些代碼具體可以看Pythonrun.c, 在這不詳細展開了.

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

# 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()可以看到全局命名空間確實已經被修改了, 因為test_var已經無法打印了, 觸發了NameError, 這樣的話, 就有辦法能夠限制全局命令空間了:

# 偽代碼

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

a = 123

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

wrap(func)

為什么我只寫偽代碼, 因為我發現這個功能實現起來是非常蛋疼! 原因就是, 在實現之前, 我們必須要解決幾個問題:

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

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

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

# 輸出

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

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

print globals()["__builtins__"].__dict__

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

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

s = globals()
s["__builtins__"].__dict__ = {}   # 覆蓋真正的內置命名空間
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

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

完美方案

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

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

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

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

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

代碼:

# coding: utf8
import inspect

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


a = 123

# 用戶自定義函數
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

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

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

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

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

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/38621.html

相關文章

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

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

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

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

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

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

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

      對python調用類特性方式詳細描述檢驗前提下類開啟也經常需要用到的,下面文中重要給大家介紹了相關Python類屬性和方法的開啟的相關資料,從文中根據實例編號介紹的非常詳細,務必的朋友可以參考一下  Python從技術的時候就已經已是一類面向對象語言表述,也正因為如此,在Python中打造一個類和對象是非常簡單的。  一、類、總體目標概述  在C語言程序設計中,把數據和信息以及對業務操作流程封...

    89542767 評論0 收藏0
  • Python標準庫sys庫常用功能相關解答

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

    89542767 評論0 收藏0

發表評論

0條評論

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