2016-09-22 61 views
3

我不是python的專家,所以當我嘗試瞭解變量作用域的細微差別時,請耐心等待。模塊和變量作用域

作爲一個簡單的例子,描述了我面臨的問題,說我有以下三個文件。

第一個文件是outside_code.py。由於某些限制,我無法修改此文件。它必須照原樣。它包含了一些代碼,在某些時候運行eval(是的,我知道eval是撒旦的產卵,但是這是一個討論的一天)。例如,假設它包含以下幾行代碼:

def eval_string(x): 
    return eval(x) 

第二個文件是一組用戶定義的函數。我們稱之爲functions.py。它包含一些未知數量的函數定義,例如,讓我們說,functions.py包含一個函數,定義如下:

def foo(x): 
    print("Your number is {}!".format(x)) 

現在我寫的第三個文件,姑且稱之爲main.py.其中包含以下代碼:

import outside_code 
from functions import * 
outside_code.eval_string("foo(4)") 

我導入所有的函數定義從functions.py帶*,所以他們應該是main.py訪問,而不需要像做functions.foo()。我還導入outside_code.py,以便可以訪問其核心功能,即包含eval的代碼。最後,我在outside_code.py中調用該函數,傳遞一個與functions.py中定義的函數相關的字符串。

在簡化示例中,我希望代碼打印出「您的號碼是4!」。但是,我收到一個錯誤,指出'foo'未定義。這顯然意味着outside_code.py中的代碼無法訪問main.py中存在的相同foo函數。所以不知何故,我需要讓foo可以訪問它。任何人都可以告訴我到目前爲止foo的範圍,以及如何擴展它以覆蓋我真正想要使用它的空間?解決我的問題的最佳方法是什麼?

回答

1

您必須將這些名稱添加到outside_code的範圍內。如果outside_code是一個普通的Python模塊,你可以直接定義:

import outside_code 
import functions 

for name in getattr(functions, '__all__', (n for n in vars(functions) if not n[0] == '_')): 
    setattr(outside_code, name, getattr(functions, name)) 

這需要所有名稱functions出口(這你與from functions import *導入),並增加了相應的對象的引用,outside_code使eval()outside_code.eval_string()可以找到它們。

您可以使用ast.parse() function從表達它傳遞給eval_function()之前產生從表達式解析樹,然後提取所有的全局名稱和只添加那些outside_code將損失降到最低,可以這麼說,但你仍然會嘲笑其他模塊命名空間來完成這個工作。

請注意,這幾乎和使用eval()一樣邪惡,但如果您不能告訴eval()在其他模塊中使用命名空間參數,則這是您唯一的選擇。這是因爲默認爲,eval()使用它所運行的模塊的全局命名空間作爲命名空間。

但是,如果您的eval_string()函數實際接受更多參數,請查找命名空間或globals選項。如果存在,則可能功能看上去更像是這樣的:

def eval_string(x, namespace=None): 
    return eval(x, globals=namespace) 

之後,你可能只是這樣做:

outside_code.eval_string('foo(4)', vars(functions)) 

其中vars(functions)給你functions模塊的命名空間。

+0

謝謝。我試圖理解變量(函數)。當我查看它內部的內容時,我得到一個包含functions.py中定義的變量的字典,但它還包含許多其他隨機內容,如\ _ \ _ doc \ _ \ _,\ _ \ _builtins \ _ \ _,EOFError和SyntaxError等錯誤,以及eval,hex等內置函數。 如何獲取我在代碼中實際定義的內容,而沒有額外的東西? –

+0

@ K.Mao:那是該模塊的* full namespace *。所有這些名稱都可用於在該模塊中執行的表達式。你可以建立一個新的字典限制,只限於不以'_'開始的名字或者在__all__中列出的字典。 –

+0

將另一個文件的完整名稱空間作爲參數傳遞給eval會有什麼不好嗎?我會不小心覆蓋一些內置的重要內容,還是很好? –

1

foo已導入到main.py;它的範圍僅限於該文件(當然還包括最初定義的文件)。它在outside_code.py中不存在。

真正的eval函數接受局部和全局的字典,以允許您將元素添加到評估的代碼的名稱空間。但是如果你的eval_string還沒有通過,你什麼都不能做。

1

相關文件:https://docs.python.org/3.5/library/functions.html#eval

eval有一個可選的字典映射的全局名稱到值

eval('foo(4)', {'foo': foo}) 

會做你所需要的。它將字符串'foo'映射到函數對象foo。

編輯

重讀你的問題,它看起來像這不會爲你工作。我唯一想到其他的嘗試

eval_str('eval("foo(4)", {"foo": lambda x: print("Your number is {}!".format(x))})') 

但是,這是一個非常hackish的解決方案並不能很好地擴展到不適合在lambda表達式功能。