python浓缩(14)执行环境
為什么80%的碼農(nóng)都做不了架構(gòu)師?>>> ??
本章主題
可調(diào)用對(duì)象
代碼對(duì)象
語(yǔ)句和內(nèi)置函數(shù)
執(zhí)行其他程序
終止執(zhí)行
各類(lèi)操作系統(tǒng)接口
相關(guān)模塊
python 中有多種運(yùn)行外部程序的方法,比如,運(yùn)行操作系統(tǒng)命令或另外的python 腳本,或執(zhí)行一個(gè)磁盤(pán)上的文件,或通過(guò)網(wǎng)絡(luò)來(lái)運(yùn)行文件。有些特定的執(zhí)行場(chǎng)景包括:
在當(dāng)前腳本繼續(xù)運(yùn)行
創(chuàng)建和管理子進(jìn)程
執(zhí)行外部命令或程序
執(zhí)行需要輸入的命令
通過(guò)網(wǎng)絡(luò)來(lái)調(diào)用命令
執(zhí)行命令來(lái)創(chuàng)建需要處理的輸出
執(zhí)行其他的 Python 腳本
執(zhí)行一系列動(dòng)態(tài)生成的 Python 語(yǔ)句
導(dǎo)入 Python 模塊 (和執(zhí)行它頂層的代碼)
python 中,內(nèi)建和外部模塊都可以提供上述各種功能。程序員得根據(jù)實(shí)現(xiàn)的需要,從這些模塊中選擇合適的處理方法。本章將對(duì)python 執(zhí)行環(huán)境進(jìn)行全面的描述,但不會(huì)涉及如何啟動(dòng)python解釋器和不同的命令行選項(xiàng)。讀者可以從第二章中查閱到相關(guān)信息。
python 執(zhí)行環(huán)境之旅從可調(diào)用對(duì)象開(kāi)始,接著是代碼對(duì)象,然后去看看什么樣的python語(yǔ)句和內(nèi)建函數(shù)適合支持我們需要的功能。執(zhí)行其他程序的能力不僅大大增強(qiáng)了python 腳本的威力,也節(jié)約了資源,因?yàn)橹貜?fù)實(shí)現(xiàn)這些代碼肯定是不合邏輯的,更是浪費(fèi)時(shí)間和人力。python 給當(dāng)前腳本環(huán)境提供了許多執(zhí)行程序或者外部命令的機(jī)制,我們將介紹下最普遍的幾個(gè)命令。接下來(lái),我們對(duì)python 的受限執(zhí)行環(huán)境作一個(gè)簡(jiǎn)短的概況,最后,介紹下各種終止執(zhí)行的方法(而不是讓程序正常完成)。就從可調(diào)用對(duì)象開(kāi)始我們的旅程吧。
14.1 可調(diào)用對(duì)象
許多python 對(duì)象都是可調(diào)用的,即能通過(guò)函數(shù)操作符“()”來(lái)調(diào)用的對(duì)象。要調(diào)用可調(diào)用對(duì)象,函數(shù)操作符得緊跟在可調(diào)用對(duì)象之后。比方說(shuō),用“foo()”來(lái)調(diào)用函數(shù)"foo"。可調(diào)用對(duì)象可以通過(guò)函數(shù)式編程接口來(lái)進(jìn)行調(diào)用,如apply(),filter(),map(),以及reduce(),這四個(gè)接口我們都在11 章討論過(guò)了。Python 有4 種可調(diào)用對(duì)象:函數(shù),方法,類(lèi),以及一些類(lèi)的實(shí)例。記住這些對(duì)象的任何引用或者別名都是可調(diào)用的。
14.1.1 函數(shù)
python 有3 種不同類(lèi)型函數(shù)對(duì)象:內(nèi)建函數(shù)、用戶(hù)自定義函數(shù)、方法
內(nèi)建函數(shù)(BIFs)
BIF 是用c/c++寫(xiě)的,編譯過(guò)后放入python 解釋器,然后把它們作為第一(內(nèi)建)名字空間的一部分加載進(jìn)系統(tǒng)。如前面章節(jié)所提到的,這些函數(shù)在_bulitin_模塊里,并作為_(kāi)_builtins__模塊導(dǎo)入到解釋器中。
表14.1 內(nèi)建函數(shù)屬性
BIF 有基礎(chǔ)類(lèi)型屬性,其中一些獨(dú)特的屬性已列在表14.1 中你可以用dir()列出函數(shù)的所有屬性:
>>>?dir(type) ['__call__',?'__class__',?'__cmp__',?'__delattr__',?'__doc__','__getattribute__',?'__hash__',?'__init__',?'__module__','__name__',?'__new__',?'__reduce__',?'__reduce_ex__', '__repr__',?'__self__',?'__setattr__',?'__str__']從內(nèi)部機(jī)制來(lái)看,因?yàn)锽IFs 和內(nèi)建方法(BIMs)屬于相同的類(lèi)型,所以對(duì)BIF 或者BIM 調(diào)用type()的結(jié)果是:
>>>?type(dir) <type?'builtin_function_or_method'>注意這不能應(yīng)用于工廠(chǎng)函數(shù),因?yàn)閠ype()正好會(huì)返回產(chǎn)生對(duì)象的類(lèi)型:
>>>?type(int) <type?'type'> >>>?type(type) <type?'type'>用戶(hù)定義的函數(shù)(UDF)
UDF(User-Defined Function)通常是用python 寫(xiě)的,定義在模塊的最高級(jí),因此會(huì)作為全局名字空間的一部分(一旦創(chuàng)建好內(nèi)建名字空間)裝載到系統(tǒng)中。函數(shù)也可在其他的函數(shù)體內(nèi)定義,并且由于在2.2 中嵌套作用域的改進(jìn),可以對(duì)多重嵌套作用域中的屬性進(jìn)行訪(fǎng)問(wèn)。可以用func_closure 屬性來(lái)鉤住在其他地方定義的屬性。
表14.2 用戶(hù)自定義函數(shù)屬性
從內(nèi)部機(jī)制來(lái)看,用戶(hù)自定義的函數(shù)是“函數(shù)“類(lèi)型的,如在下面的例子中用type()表明的一樣:
>>>?def?foo():? pass >>>?type(foo) <type?'function'>lambda 表達(dá)式和用戶(hù)自定義函數(shù)相比略有不同。雖然它們都是返回一個(gè)函數(shù)對(duì)象,但是lambda 表達(dá)式不是用def 語(yǔ)句創(chuàng)建的,而是用lambda 關(guān)鍵字:因?yàn)閘ambda 表達(dá)式?jīng)]有給命名綁定的代碼提供基礎(chǔ)結(jié)構(gòu),所以要通過(guò)函數(shù)式編程接口來(lái)調(diào)用,或把它的引用賦值給一個(gè)變量,然后就可以直接調(diào)用或者再通過(guò)函數(shù)來(lái)調(diào)用。變量?jī)H是個(gè)別名,并不是函數(shù)對(duì)象的名字。
通過(guò)lambda 來(lái)創(chuàng)建函數(shù)對(duì)象除了沒(méi)有命名之外,享有和用戶(hù)自定義函數(shù)相同的屬性;__name__或者func_name 屬性給定為字符串"<lambda>"。使用type()工廠(chǎng)函數(shù),lambda 表達(dá)式返回和用戶(hù)自定義函數(shù)相同的函數(shù)對(duì)象:
>>>?lambdaFunc?=?lambda?x:?x?*?2 >>>?lambdaFunc(100) 200 >>>?type(lambdaFunc) <type?'function'>在上面的例子中,我們將表達(dá)式賦值給一個(gè)別名。我們也可以直接在一個(gè)lambda 表達(dá)式上調(diào)用type():
>>>?type(lambda:1) <type?'function'>我們快速的來(lái)看看UDF 名字,使用上面的lambdaFunc 和先前小節(jié)中的foo():
>>>?foo.__name__ 'foo' >>>?lambdaFunc.__name__ '<lambda>'從11.9 小節(jié)中我們可以看到,一旦函數(shù)聲明以后(且函數(shù)對(duì)象可用),程序員也可以自定義函數(shù)屬性。所有的新屬性變成udf.__dict__對(duì)象的一部分。在本章的稍后內(nèi)容中,將討論獲取含有python 代碼的字符串并執(zhí)行該代碼。到了本章最后,會(huì)有一個(gè)組合例子,著重描寫(xiě)函數(shù)屬性和python 代碼(字符串)的動(dòng)態(tài)求值和執(zhí)行語(yǔ)句。
14.1.2 方法
13 章研究了方法。用戶(hù)自定義方法是被定義為類(lèi)的一部分的函數(shù)。許多python 數(shù)據(jù)類(lèi)型,比如列表和字典,也有方法,這些被稱(chēng)為內(nèi)建方法。方法通過(guò)對(duì)象的名字和句點(diǎn)屬性標(biāo)識(shí)進(jìn)行命名。
內(nèi)建方法(BIMs)
在前面的小節(jié)中,我們討論了內(nèi)建方法與內(nèi)建函數(shù)的類(lèi)似之處。只有內(nèi)建類(lèi)型(BIT)有BIM。對(duì)于內(nèi)建方法,type()工廠(chǎng)函數(shù)給出了和BIF 相同的輸出--注意,我們是如何提供一個(gè)內(nèi)建對(duì)象來(lái)訪(fǎng)問(wèn)BIM:
>>>?type([].append) <type?'builtin_function_or_method'>此外,BIM 和BIF 兩者也都享有相同屬性。不同之處在于BIM 的__self__屬性指向一個(gè)Python對(duì)象,而B(niǎo)IF 指向None。
對(duì)于類(lèi)和實(shí)例,都能以該對(duì)象為參數(shù),通過(guò)內(nèi)建函數(shù)dir()來(lái)獲得他們的數(shù)據(jù)和方法屬性。這也可以用在BIM 上:
>>>?dir([].append) ['__call__',?'__class__',?'__cmp__',?'__delattr__',?'__doc__', '__getattribute__',?'__hash__',?'__init__',?'__module__', '__name__',?'__new__',?'__reduce__',?'__reduce_ex__', '__repr__',?'__self__',?'__setattr__',?'__str__']不用多久就會(huì)發(fā)現(xiàn),從功能上看,用實(shí)際的對(duì)象去訪(fǎng)問(wèn)其方法并不是非常有用,如最后的例子。由于沒(méi)有引用來(lái)保存這個(gè)對(duì)象,所以它立即被垃圾回收了。你處理這種訪(fǎng)問(wèn)的類(lèi)型唯一的用處就是顯示BIT 有什么方法。
用戶(hù)定義的方法(UDM)
UDM(User-defined method)包含在類(lèi)定義之中,擁有標(biāo)準(zhǔn)函數(shù)的包裝,僅有定義它們的類(lèi)可以使用。如果沒(méi)有在子類(lèi)定義中被覆蓋掉,也可以通過(guò)子類(lèi)實(shí)例來(lái)調(diào)用它們。UDM 與類(lèi)對(duì)象是關(guān)聯(lián)的(非綁定方法),或者通過(guò)類(lèi)的實(shí)例來(lái)調(diào)用(綁定方法)。無(wú)論UDMs 是否綁定,所有的UMD 都是相同的類(lèi)型——“實(shí)例方法“:
>>>?class?C(object):?#?define?class?#?定義類(lèi) ...?def?foo(self):?pass?#?define?UDM?#?定義UDM ... >>>?c?=?C()?#?instantiation?#?實(shí)例化 >>>?type(C)?#?type?of?class?#?類(lèi)的類(lèi)別 <type?'type'> >>>?type(c)?#?type?of?instance?#?實(shí)例的類(lèi)別 <class?'__main__.C'> >>>?type(C.foo)?#?type?of?unbound?method?#?非綁定方法的類(lèi)別 <type?'instancemethod'> >>>?type(c.foo)?#?type?of?bound?method?#?綁定方法的類(lèi)別 <type?'instancemethod'>表11.4 中展示了UDM 的屬性。訪(fǎng)問(wèn)對(duì)象本身會(huì)揭示正在引用的是綁定方法還是非綁定方法。eg,綁定的方法揭示了方法綁定到哪一個(gè)實(shí)例:
>>>?C.foo?#?unbound?method?object?#?非綁定方法對(duì)象 <unbound?method?C.foo> >>>?c.foo?#?bound?method?object?#?綁定方法對(duì)象 <bound?method?C.foo?of?<__main__.C?object?at?0x00B42DD0> >>>?c?#?instance?foo()'s?bound?to?#?foo()實(shí)例被綁定到…… <__main__.C?object?at?0x00B42DD0>14.1.3 類(lèi)
可以利用類(lèi)的可調(diào)用性來(lái)創(chuàng)建實(shí)例。“調(diào)用”類(lèi)的結(jié)果便是創(chuàng)建了實(shí)例。
>>>?class?C(object): def?__init__(self,?*args): print?'Instantiated?with?these?arguments:\n',?args >>>?c1?=?C()?#?invoking?class?to?instantiate?c1 Instantiated?with?these?arguments: () >>>?c2?=?C('The?number?of?the?counting?shall?be',?3)? Instantiated?with?these?arguments: ('The?number?of?the?counting?shall?be',?3)一個(gè)新的問(wèn)題是如何讓實(shí)例能夠被調(diào)用。
14.1.4 類(lèi)的實(shí)例
python 給類(lèi)提供了名為_(kāi)_call__的特別方法,該方法允許程序員創(chuàng)建可調(diào)用的對(duì)象(實(shí)例)。默認(rèn)情況下,__call__()方法是沒(méi)有實(shí)現(xiàn)的,這意味著大多數(shù)實(shí)例都是不可調(diào)用的。然而,如果在類(lèi)定義中覆蓋了這個(gè)方法,那么這個(gè)類(lèi)的實(shí)例就成為可調(diào)用的了。調(diào)用這樣的實(shí)例對(duì)象等同于調(diào)用__call__()方法。自然地,任何在實(shí)例調(diào)用中給出的參數(shù)都會(huì)被傳入到__call()__中。……那么foo()就和foo.__call__(foo)的效果相同, 這里foo 也作為參數(shù)出現(xiàn),因?yàn)槭菍?duì)自己的引用,實(shí)例將自動(dòng)成為每次方法調(diào)用的第一個(gè)參數(shù)。如果 ___call___()有參數(shù),比如,(self, arg),那么foo(arg)就和調(diào)用foo.__call__(foo, arg)一樣。這里我們給出一個(gè)可調(diào)用實(shí)例的例子,和前面小節(jié)的例子相似:
>>>?class?C(object): ...?def?__call__(self,?*args): ...?print?"I'm?callable!?Called?with?args:\n",?args ... >>>?c?=?C()?#?instantiation?#?實(shí)例化 >>>?c?#?our?instance?#?我們的實(shí)例 <__main__.C?instance?at?0x00B42DD0> >>>?callable(c)?#?instance?is?callable?#實(shí)例是可調(diào)用的 True >>>?c()?#?instance?invoked?#?調(diào)用實(shí)例 I'm?callable!?Called?with?arguments: () >>>?c(3)?#?invoked?with?1?arg?#?呼叫的時(shí)候給出一個(gè)參數(shù) I'm?callable!?Called?with?arguments: (3,) >>>?c(3,?'no?more,?no?less')?#?invoked?with?2?args?#?呼叫的時(shí)候給出兩個(gè)參數(shù) I'm?callable!?Called?with?arguments: (3,?'no?more,?no?less')記住只有定義類(lèi)的時(shí)候?qū)崿F(xiàn)了__call__方法,類(lèi)的實(shí)例才能成為可調(diào)用的。
14.2 代碼對(duì)象
可調(diào)用的對(duì)象是python 執(zhí)行環(huán)境里最重要的部分,卻只是冰山一角。python 語(yǔ)句,賦值,表達(dá)式,甚至還有模塊構(gòu)成了更宏大的場(chǎng)面。這些可執(zhí)行對(duì)象無(wú)法像可調(diào)用物那樣被調(diào)用。更確切地說(shuō),這些對(duì)象只是構(gòu)成可執(zhí)行代碼塊的拼圖的很小一部分,而這些代碼塊被稱(chēng)為代碼對(duì)象。每個(gè)可調(diào)用物的核心都是代碼對(duì)象,由語(yǔ)句,賦值,表達(dá)式,以及其他可調(diào)用物組成。查看一個(gè)模塊意味著觀(guān)察一個(gè)較大的、包含了模塊中所有代碼的對(duì)象。然后代碼可以分成語(yǔ)句,賦值,表達(dá)式,以及可調(diào)用物。可調(diào)用物又可以遞歸分解到下一層,那兒有自己的代碼對(duì)象。一般說(shuō)來(lái),代碼對(duì)象可以作為函數(shù)或者方法調(diào)用的一部分來(lái)執(zhí)行,也可用exec 語(yǔ)句或內(nèi)建函數(shù)eval()來(lái)執(zhí)行。從整體上看,一個(gè)python 模塊的代碼對(duì)象是構(gòu)成該模塊的全部代碼。如果要執(zhí)行python 代碼,那么該代碼必須先要轉(zhuǎn)換成字節(jié)編譯的代碼(又稱(chēng)字節(jié)碼)。這才是真正的代碼對(duì)象。然而,代碼對(duì)象不包含任何關(guān)于代碼對(duì)象執(zhí)行環(huán)境的信息,這便是可調(diào)用對(duì)象存在的原因,可調(diào)用對(duì)象被用來(lái)包裝一個(gè)代碼對(duì)象并提供額外的信息。還記得前面的小節(jié)中UDF(用戶(hù)自定義函數(shù)) 的udf.func_code 屬性嗎?那就是代碼對(duì)象。UDM (用戶(hù)自定義方法)的udm.im_func 函數(shù)對(duì)象又是怎么一回事呢?因?yàn)槟且彩且粋€(gè)函數(shù)對(duì)象,所以他同樣有它自己的udm.im_func.func_code 代碼對(duì)象。這樣的話(huà),你會(huì)發(fā)現(xiàn),函數(shù)對(duì)象僅是代碼對(duì)象的包裝,方法則是給函數(shù)對(duì)象的包裝。你可以到處看看。當(dāng)研究到最底層,你會(huì)發(fā)現(xiàn)便是一個(gè)代碼對(duì)象。
14.3 可執(zhí)行的對(duì)象聲明和內(nèi)建函數(shù)
Python 提供了大量的BIF 來(lái)支持可調(diào)用/可執(zhí)行對(duì)象,其中包括exec 語(yǔ)句。這些函數(shù)幫助程序員執(zhí)行代碼對(duì)象,也可以用內(nèi)建函數(shù)complie()來(lái)生成代碼對(duì)象。
14.3.1 callable()
callable()是一個(gè)布爾函數(shù),確定一個(gè)對(duì)象是否可以通過(guò)函數(shù)操作符(())來(lái)調(diào)用。如果函數(shù)可調(diào)用便返回True,否則便是False,這里有些對(duì)象及其對(duì)應(yīng)的callable 返回值:
>>>?callable(dir)?#?built-in?function?#?內(nèi)建函數(shù) True >>>?callable(1)?#?integer?#整數(shù) False >>>?def?foo():?pass ... >>>?callable(foo)?#?user-defined?function?#?用戶(hù)自定義函數(shù) True >>>?callable('bar')?#?string?#字符串 False >>>?class?C(object):?pass ... >>>?callable(C)?#?class?#類(lèi) True14.3.2 compile()
compile()函數(shù)允許在運(yùn)行時(shí)刻迅速生成代碼對(duì)象,然后用exec 語(yǔ)句或內(nèi)建函數(shù)eval()來(lái)執(zhí)行這些對(duì)象或者對(duì)它們進(jìn)行求值。一個(gè)很重要的觀(guān)點(diǎn)是:exec 和eval()都可以執(zhí)行字符串格式的Python 代碼。當(dāng)執(zhí)行字符串形式的代碼時(shí),每次都必須對(duì)這些代碼進(jìn)行字節(jié)編譯處理。compile()函數(shù)提供了一次性字節(jié)代碼預(yù)編譯,以后每次調(diào)用的時(shí)候,都不用編譯了。compile 的三個(gè)參數(shù)都是必需的,第一參數(shù)代表了要編譯的python 代碼。第二個(gè)字符串,雖然是必需的,但通常被置為空串。該參數(shù)代表了存放代碼對(duì)象的文件的名字(字符串類(lèi)型)。compile 的通常用法是動(dòng)態(tài)生成字符串形式的Python 代碼, 然后生成一個(gè)代碼對(duì)象——代碼顯然沒(méi)有存放在任何文件。
最后的參數(shù)是個(gè)字符串,它用來(lái)表明代碼對(duì)象的類(lèi)型。有三個(gè)可能值:
'eval' 可求值的表達(dá)式[和eval()一起使用]
'single' 單一可執(zhí)行語(yǔ)句[和exec 一起使用]
'exec' 可執(zhí)行語(yǔ)句組[和exec 一起使用]
在最后的例子中,我們第一次看到input()。一直以來(lái),我們都是從raw_input()中讀取輸入的。內(nèi)建函數(shù)input()是我們將在本章稍后討論的一個(gè)快捷函數(shù)。
14.3.3 eval()
eval()對(duì)表達(dá)式求值或者為字符串、內(nèi)建函數(shù)complie()創(chuàng)建的預(yù)編譯代碼對(duì)象。eval()第一個(gè)參數(shù)是要執(zhí)行的對(duì)象。第二個(gè)和第三個(gè)參數(shù),都為可選的,分別代表了全局和局部名字空間中的對(duì)象。如果給出這兩個(gè)參數(shù),globals 必須是個(gè)字典,locals可以是任意的映射對(duì)象,比如,一個(gè)實(shí)現(xiàn)了__getitem__()方法的對(duì)象。如果都沒(méi)給出這兩個(gè)參數(shù),分別默認(rèn)為globals()和locals()返回的對(duì)象,如果只傳入了一個(gè)全局字典,那么該字典也作為locals 傳入。好了,我們一起來(lái)看看eval():
>>>?eval('932') 932 >>>?int('932') 932在這種情況下,eval()和int()都返回相同的結(jié)果:整數(shù)932。然而,它們采用的方式卻不盡相同。內(nèi)建函數(shù)eval()接收引號(hào)內(nèi)的字符串并把它作為python 表達(dá)式進(jìn)行求值。內(nèi)建函數(shù)int()接收代表整數(shù)的字符串并把它轉(zhuǎn)換為整數(shù)。這只有在該字符串只由字符串932 組成的時(shí)候才會(huì)成功,而該字符串作為表達(dá)式返回值932,932 也是字符串”932”所代表的整數(shù)。當(dāng)我們用純字符串表達(dá)式的時(shí)候,兩者便不再相同了:
>>>?eval('100?+?200') 300 >>>?int('100?+?200')?Traceback?(innermost?last): File?"<stdin>",?line?1,?in?? ValueError:?invalid?literal?for?int():?100?+?200在這種情況下,eval()接收一個(gè)字符串并把"100+200"作為表達(dá)式求值,當(dāng)進(jìn)行整數(shù)加法后,給出返回值300。而對(duì)int()的調(diào)用失敗了,因?yàn)樽址畢?shù)不是能代表整數(shù)的字符串, 因?yàn)樵谧址杏蟹欠ǖ奈淖?#xff0c;即,空格以及“+”字符。可以這樣理解eval()函數(shù)的工作方式:對(duì)表達(dá)式兩端的引號(hào)視而不見(jiàn)。
14.3.4 exec
和eval()相似,exec 語(yǔ)句執(zhí)行代碼對(duì)象或字符串形式的python 代碼。類(lèi)似地,用compile()預(yù)編譯重復(fù)代碼有助于改善性能,因?yàn)樵谡{(diào)用時(shí)不必經(jīng)過(guò)字節(jié)編譯處理。exec 語(yǔ)句只接受一個(gè)參數(shù),下面便是它的通用語(yǔ)法:
exec?obj被執(zhí)行的對(duì)象(obj)可以只是原始的字符串,比如單一語(yǔ)句或是語(yǔ)句組,它們也可以預(yù)編譯成一個(gè)代碼對(duì)象(分別用'single'和'exec"參數(shù))。下面的例子中,多個(gè)語(yǔ)句作為一個(gè)字符串發(fā)送給exec:
>>>?exec?""" ...?x?=?0 ...?print?'x?is?currently:',?x ...?while?x?<?5: ...?x?+=?1 ...?print?'incrementing?x?to:',?x ...?""" x?is?currently:?0 incrementing?x?to:?1 incrementing?x?to:?2 incrementing?x?to:?3 incrementing?x?to:?4 incrementing?x?to:?5最后, exec 還可以接受有效的python 文件對(duì)象。如果我們用上面的多行代碼創(chuàng)建一個(gè)叫xcount.py 的文件,那么也可以用下面的方法執(zhí)行相同的代碼:
>>>?f?=?open('xcount.py')?#?open?the?file >>>?exec?f?#?execute?the?file x?is?currently:?0 incrementing?x?to:?1 incrementing?x?to:?2 incrementing?x?to:?3 incrementing?x?to:?4 incrementing?x?to:?5 >>>?exec?f?#嘗試再一次執(zhí)行 >>>?#哦,失敗了....為什么?注意一旦執(zhí)行完畢,繼續(xù)對(duì)exec 的調(diào)用就會(huì)失敗。并不是真正的失敗,只是不再做任何事。事實(shí)上,exec 已從文件中讀取了全部的數(shù)據(jù)且停留在文件末尾(EOF)。當(dāng)用相同文件對(duì)象對(duì)exec 進(jìn)行調(diào)用的時(shí)候,便沒(méi)有可以執(zhí)行的代碼了,所以exec 什么都不做,如同上面看見(jiàn)的行為。我們?nèi)绾沃浪贓OF 呢?
用文件對(duì)象的tell()方法來(lái)告訴我們處于文件的何處,然后用os.path.getsize()來(lái)告訴我們xcount.py 腳本有多大。這樣你就會(huì)發(fā)現(xiàn),兩個(gè)數(shù)字完全一樣:
>>>?f.tell()?#?where?are?we?in?the?file??#?我們?cè)谖募氖裁吹胤?#xff1f; 116 >>>?f.close()?#?close?the?file?#?關(guān)閉文件 >>>?from?os.path?import?getsize >>>?getsize('xcount.py')?#?what?is?the?file?size??#?文件有多大? 116如果想在不關(guān)閉和重新打開(kāi)文件的情況下再次運(yùn)行它,可以用seek()到文件最開(kāi)頭并再次調(diào)用exec 了。比如,假定我們還沒(méi)有調(diào)用f.close(),那么我們可以這樣做:
>>>?f.seek(0)?#?rewind?to?beginning?倒會(huì)文件開(kāi)頭 >>>?exec?f x?is?currently:?0 incrementing?x?to:?1 incrementing?x?to:?2 incrementing?x?to:?3 incrementing?x?to:?4 incrementing?x?to:?5 >>>?f.close()14.3.5 input()
內(nèi)建函數(shù)input()是eval()和raw_input()的組合,等價(jià)于eval(raw_input())。類(lèi)似于raw_input(),input()有一個(gè)可選的參數(shù),該參數(shù)代表了給用戶(hù)的字符串提示。如果不給定參數(shù)的話(huà),該字符串默認(rèn)為空串。
從功能上看,input 不同于raw_input(),因?yàn)閞aw_input()總是以字符串的形式,逐字地返回用戶(hù)的輸入。input()履行相同的的任務(wù);而且,它還把輸入作為python 表達(dá)式進(jìn)行求值。這意味著input()返回的數(shù)據(jù)是對(duì)輸入表達(dá)式求值的結(jié)果:一個(gè)python 對(duì)象。
下面的例子會(huì)讓人更加清楚:當(dāng)用戶(hù)輸入一個(gè)列表時(shí),raw_input()返回一個(gè)列表的字符串描繪,而input()返回實(shí)際的列表:
>>>?aString?=?raw_input('Enter?a?list:?')? Enter?a?list:?[?123,?'xyz',?45.67?] >>>?aString "[?123,?'xyz',?45.67?]" >>>?type(aString) <type?'str'>上面用raw_input()運(yùn)行。正如你看見(jiàn)的,每樣?xùn)|西都是字符串。現(xiàn)在來(lái)看看當(dāng)用input()的時(shí)候會(huì)發(fā)生什么:
>>>?aList?=?input('Enter?a?list:?')?Enter?a?list:?[?123,?'xyz',?45.67?] >>>?aList [123,?'xyz',?45.67] >>>?type(aList) <type?'list'>雖然用戶(hù)輸入字符串,但是input()把輸入作為python 對(duì)象來(lái)求值并返回表達(dá)式的結(jié)果。
14.3.6 使用Python 在運(yùn)行時(shí)生成和執(zhí)行Python 代碼
在這個(gè)小節(jié)中,我們將看到兩個(gè)python 腳本的例子,這兩個(gè)例子在運(yùn)行時(shí)刻把python 代碼作為字符串并執(zhí)行。第一個(gè)例子更加動(dòng)態(tài),但第二個(gè)突出了函數(shù)屬性。
在運(yùn)行時(shí)生成和執(zhí)行Python 代碼
第一個(gè)例子是loopmake.py 腳本,一個(gè)簡(jiǎn)單的、迅速生成和執(zhí)行循環(huán)的計(jì)算機(jī)輔助軟件工程(CASE)。它提示用戶(hù)給出各種參數(shù)(比如,循環(huán)類(lèi)型(while 或for), 迭代的數(shù)據(jù)類(lèi)型[數(shù)字或序列]),生成代碼字串,并執(zhí)行它動(dòng)態(tài)生成和執(zhí)行Python 代碼
dashes?=?'\n'?+?'-'?*?50 print?(dashes)exec_dict?=?{'f':"""for?%s?in?%s:print?%s""",'s':"""%s=0%s=%swhile?%s<?len(%s):print?%s[%s]%s=%s+1""",'n':"""%s=%dwhile?%s?<?%d:print?%s%s?=?%s+%d"""}def?main():ltype?=?input("Loop?type??(For/While)?")dtype?=?input('Data?type??(Number/Seq)?')if?dtype?==?'n':start?=?int(input('Starting?value??'))stop?=?int(input('Ending?value?(non-inclusive)??'))step?=?int(input('Stepping?value??'))seq?=?str(range(start,?stop,?step))else:seq?=?input('Enter?sequence:?')var?=?input('Iterative?variable?name??')if?ltype?==?'f':exec_str?=?exec_dict['f']?%?(var,?seq,?var)elif?ltype?==?'w':if?dtype?==?'s':svar?=?input('Enter?sequence?name??')exec_str?=?exec_dict['s']?%?(var,?svar,?seq,?var,?svar,?svar,?var,?var,?var)elif?dtype?==?'n':exec_str?=?exec_dict['n']?%?(var,?start,?var,?stop,?var,?var,?var,?step)print?(exec_str)print?(dashes)print?('Your?custom-generated?code:'?+?dashes)print?(exec_str?+?dashes)print?('Test?execution?of?the?code:'?+?dashes)exec(exec_str)print?(dashes)if?__name__?==?'__main__':main()運(yùn)行
Loop?type??(For/While)?f Data?type??(Number/Sequence)?n Starting?value??0 Ending?value?(non-inclusive)??4 Stepping?value??1 Iterative?variable?name??counter -------------------------------------------------- The?custom-generated?code?for?you?is: -------------------------------------------------- for?counter?in?[0,?1,?2,?3]: print?counter -------------------------------------------------- Test?execution?of?the?code: -------------------------------------------------- 0 1 2 3 -------------------------------------------------- %?loopmake.py Loop?type??(For/While)?w Data?type??(Number/Sequence)?n Starting?value??0 Ending?value?(non-inclusive)??4 Stepping?value??1 Iterative?variable?name??counter -------------------------------------------------- Your?custom-generated?code: -------------------------------------------------- counter?=?0 while?counter?<?4: print?counter counter?=?counter?+?1 -------------------------------------------------- Test?execution?of?the?code: -------------------------------------------------- 0 1 2 3 -------------------------------------------------- %?loopmake.py Loop?type??(For/While)?f Data?type??(Number/Sequence)?s Enter?sequence:?[932,?'grail',?3.0,?'arrrghhh'] Iterative?variable?name??eachItem -------------------------------------------------- Your?custom-generated?code: -------------------------------------------------- for?eachItem?in?[932,?'grail',?3.0,?'arrrghhh']: print?eachItem -------------------------------------------------- Test?execution?of?the?code: -------------------------------------------------- 932 grail 3.0 arrrghhh -------------------------------------------------- %?loopmake.py Loop?type??(For/While)?w Data?type??(Number/Sequence)?s Enter?sequence:?[932,?'grail',?3.0,?'arrrghhh'] Iterative?variable?name??eachIndex Enter?sequence?name??myList -------------------------------------------------- Your?custom-generated?code: -------------------------------------------------- eachIndex?=?0 myList?=?[932,?'grail',?3.0,?'arrrghhh'] while?eachIndex?<?len(myList): print?myList[eachIndex] eachIndex?=?eachIndex?+?1 -------------------------------------------------- Test?execution?of?the?code: -------------------------------------------------- 932 grail 3.0 arrrghhh --------------------------------------------------不同于raw_input(),input()會(huì)把輸入當(dāng)成python 表達(dá)式來(lái)求值,即使用戶(hù)以字符串的形式輸入,也會(huì)返回一個(gè)python 對(duì)象
為了很好地控制腳本的大小,從原來(lái)的腳本中剔除了所有的注釋和錯(cuò)誤檢測(cè)。在本書(shū)的web站點(diǎn)上,都可以找到原來(lái)的和修改后的版本。
擴(kuò)展的版本包括了額外的特性,比如用于字符串輸入的不必要的引號(hào),輸入數(shù)據(jù)的默認(rèn)值,以及檢測(cè)無(wú)效的返回和標(biāo)識(shí)符;也不允許以關(guān)鍵字和內(nèi)建名字作為變量名字,有條件地執(zhí)行代碼。
第二個(gè)例子著重描寫(xiě)了在第11 章"函數(shù)"引入的函數(shù)屬性,它是從Python 增強(qiáng)提議232(PEP 232)中的例子得到的靈感。假設(shè)你是一位負(fù)責(zé)質(zhì)量控制的軟件開(kāi)發(fā)者,你鼓勵(lì)你的工程師將回歸測(cè)試或回歸指令代碼放到主代碼中,但又不想讓測(cè)試代碼混合到產(chǎn)品代碼中。你可以讓工程師創(chuàng)建字符串形式的測(cè)試代碼。當(dāng)你的測(cè)試框架執(zhí)行的時(shí)候,它會(huì)檢測(cè)函數(shù)是否定義了測(cè)試體,如果是的話(huà),(求值并)執(zhí)行它。如果不是,便跳過(guò),像通常一樣執(zhí)行。
Example 14.2 ?(funcAttrs.py):調(diào)用sys.exit()使python 解釋器退出。exit()的任何整數(shù)參數(shù)作為退出狀態(tài)會(huì)返回給調(diào)用者,該值默認(rèn)為0
def?foo():return?Truedef?bar():'bar()?does?not?do?much'return?Truefoo.__doc__?=?'foo()?does?not?do?much' foo.tester?=?''' if?foo():print?('PASSED') else:print?('FAILED') '''for?eachAttr?in?dir():obj?=?eval(eachAttr)if?isinstance(obj,?type(foo)):if?hasattr(obj,?'__doc__'):print?('\nFunction?"%s"?has?a?doc?string:\n\t%s'?%?(eachAttr,?obj.__doc__))if?hasattr(obj,?'tester'):print?('Function?"%s"?has?a?tester...?executing'%?eachAttr)exec(obj.tester)else:print?('"%s"?is?not?a?function'?%?eachAttr)好了,真正的工作在這里開(kāi)始。我們從用內(nèi)建函數(shù)dir()迭代現(xiàn)在(即全局)名字空間開(kāi)始。它返回的列表包含了所有對(duì)象的名字。因?yàn)檫@些都是字符串,我們需要在使用eval將它們轉(zhuǎn)化為真正的python 對(duì)象。
除了預(yù)期的系統(tǒng)變量,比如,__builtins__,我們還期望顯示函數(shù)。我們只對(duì)函數(shù)有興趣。執(zhí)行代碼后,我們得到如下的輸出:
"__builtins__"?is?not?a?function "__doc__"?is?not?a?function "__file__"?is?not?a?function "__loader__"?is?not?a?function "__name__"?is?not?a?function "__package__"?is?not?a?function "__spec__"?is?not?a?functionFunction?"bar"?has?a?doc?string:bar()?does?not?do?muchFunction?"foo"?has?a?doc?string:foo()?does?not?do?much Function?"foo"?has?a?tester...?executing PASSED14.4 執(zhí)行其他(Python)程序
當(dāng)討論執(zhí)行其他程序時(shí),把它們分類(lèi)為python 程序和非python 程序,后者包括了二進(jìn)制可執(zhí)行文件或其他腳本語(yǔ)言的源代碼。先討論如何運(yùn)行其他的python 程序,然后是如何用os 模塊調(diào)用外部程序。
14.4.1 導(dǎo)入
運(yùn)行時(shí)刻有很多執(zhí)行另外python 腳本的方法。正如我們先前討論的,第一次導(dǎo)入模塊會(huì)執(zhí)行模塊最高級(jí)的代碼。不管你是否需要,這就是python 導(dǎo)入的行為。提醒,只有屬于模塊最高級(jí)的代碼才是全局變量,全局類(lèi),和全局函數(shù)聲明。
核心筆記:當(dāng)模塊導(dǎo)入后,就執(zhí)行所有的模塊,它運(yùn)行所有最高級(jí)別的python 代碼,比如,'main()’。如果foo 含有bar 函數(shù)的聲明,那么便執(zhí)行def foo(...)。再問(wèn)一次為什么會(huì)這樣做呢?由于某些原因,bar 必須被識(shí)別為foo模塊中一個(gè)有效的名字,也就是說(shuō)bar 在foo 的名字空間中,其次,解釋器要知道它是一個(gè)已聲明的函數(shù),就像本地模塊中的任何一個(gè)函數(shù)。現(xiàn)在我們知道要做什么了,那么如何處理那些不想每次導(dǎo)入都執(zhí)行的代碼呢?縮進(jìn)它,并放入if __name__ == '__main__' 的內(nèi)部。跟著應(yīng)該是一個(gè)if 語(yǔ)句,它通過(guò)檢測(cè)__name__來(lái)確定是否要調(diào)用腳本,比如,“if__name__ =='__main__'”。如果相等的話(huà),你的腳本會(huì)執(zhí)行main 內(nèi)代碼;否則只是打算導(dǎo)入這個(gè)腳本,那么可以在這個(gè)模塊內(nèi)對(duì)代碼進(jìn)行測(cè)試。
#?import1.py print?'loaded?import1' import?import2====== #?import2.py print?'loaded?import2'這是當(dāng)我們導(dǎo)入import1 時(shí)的輸出
>>>?import?import1? loaded?import1?loaded?import2 >>>根據(jù)建議檢測(cè)__name__值的迂回工作法,我們改變了import1.py 和import2.py 里的代碼,這樣的情況就不會(huì)發(fā)生了,這里是修改后的import.py 版本:
#?import1.py import?import2 if?__name__?==?'__main__':print?'loaded?import1'接著是import2.py 的代碼,以相同的方式修改:
#?import2.py if?__name__?==?'__main__'print?'loaded?import2'當(dāng)從python 中導(dǎo)入import1 的時(shí)候,我們不再會(huì)得到任何輸出
>>>?import?import1在某些情況中,你可能想要顯示輸出來(lái)確定輸入模塊。這取決于你自身的情況。我們的目標(biāo)是提供實(shí)效的編程例子來(lái)屏蔽副作用。
14.4.2 execfile()
顯然,導(dǎo)入模塊不是從另外的python 腳本中執(zhí)行python 腳本最可取的方法。那也就不是導(dǎo)入過(guò)程。導(dǎo)入模塊的副作用是導(dǎo)致最高級(jí)代碼運(yùn)行。這章一開(kāi)始,我們描述了如何通過(guò)文件對(duì)象,使用exec 語(yǔ)句來(lái)讀取python 腳本的內(nèi)容并執(zhí)行。
下面的代碼給出了例子:
f?=?open(filename,?'r') exec?f? f.close()這3 行可以調(diào)用execfile()來(lái)?yè)Q掉:
execfile(filename)雖然上述代碼執(zhí)行了一個(gè)模塊,但是僅可以在現(xiàn)有的執(zhí)行環(huán)境下運(yùn)行(比如,它自己的全局和局部的名字空間)。在某些情況下,可能需要用不同全局和局部的名字空間集合,而不是默認(rèn)的集合來(lái)執(zhí)行模塊。execfile() 函數(shù)的語(yǔ)法非常類(lèi)似于eval()函數(shù)的。
execfile(filename,?globals=globals(),?locals=locals())類(lèi)似eval(),globals 和locals 都是可選的,若不提供參數(shù)值的話(huà),默認(rèn)為執(zhí)行環(huán)境的名字空間。如果只給定globals,那么locals 默認(rèn)和globals 相同。如果提供locals 值的話(huà),它可以是任何映射對(duì)象[一個(gè)定義/覆蓋了__getitem__()的對(duì)象]。在2.4之前,locals 必須是一個(gè)字典。注意:(在修改的時(shí)候)小心局部名字空間。比較安全的做法是傳入一個(gè)虛假的"locals"字典并檢查是否有副作用。execfile()不保證不會(huì)修改局部名字空間。見(jiàn)python 庫(kù)參kao手冊(cè)對(duì)execfile()的解釋
14.4.3 將模塊作為腳本執(zhí)行
python2.4 里加入了一個(gè)新的命令行選項(xiàng)(或開(kāi)關(guān)),允許從shell 或DOS 提示符,直接把模塊作為腳本來(lái)執(zhí)行。當(dāng)以腳本的方式來(lái)書(shū)寫(xiě)你的模塊的時(shí)候,執(zhí)行它們是很容易的。你可以使用命令行從你的工作目錄調(diào)用你的腳本。
$?myScript.py?#?or?$?python?myScript.py若模塊是標(biāo)準(zhǔn)庫(kù)的一部分,安裝在site-packages 里,或者僅僅是包里面的模塊,處理這樣的模塊就不是那么容易了,尤其是它們共享了已存在的同名python 模塊。eg,要運(yùn)行免費(fèi)的python web服務(wù)器,以便創(chuàng)建和測(cè)試你自己的web頁(yè)面和CGI 腳本。須在命令行敲入:
$?python?/usr/local/lib/python2x/CGIHTTPServer.py Serving?HTTP?on?0.0.0.0?port?8000?...這是段很長(zhǎng)的命令,如果它是第三方的,不得不深入到site-packages 去找到它真正定位的地方。如果沒(méi)給出完全的路徑名,可以從命令行運(yùn)行一個(gè)模塊,并讓python 的導(dǎo)入機(jī)制為我們做這種跑腿工作嗎?是肯定的。可以用python -c 命令行開(kāi)關(guān):
$?python?-c?"import?CGIHTTPServer;?CGIHTTPServer.test()"該選項(xiàng)允許指定你想要運(yùn)行的python 語(yǔ)句。雖然它可以這樣工作,但問(wèn)題是__name__模塊不是‘__main__‘而是你正在使用的模塊。解釋器通過(guò)import 裝載了你的模塊,并不是它當(dāng)作腳本。所以,所有在if __name__ == '__main__' 之下的代碼是不會(huì)執(zhí)行的,所以你不得不手動(dòng)地調(diào)用模塊的test()函數(shù),就如同前面我們所做的一樣。所以我們想同時(shí)要兩者的優(yōu)點(diǎn)——能夠在類(lèi)庫(kù)中執(zhí)行作為腳本的模塊而不是作為導(dǎo)入的模塊。這就是-m 參數(shù)的動(dòng)機(jī)。現(xiàn)在可以像這樣運(yùn)行腳本:
$?python?-m?CGIHTTPServer這是不小的改進(jìn)。盡管如此,還沒(méi)有完全如預(yù)想那樣實(shí)現(xiàn)特性。所以在python2.5 中,-m 開(kāi)關(guān)有了更多的兼容性。從2.5 開(kāi)始,你可以用相同的參數(shù)來(lái)運(yùn)行包內(nèi)或需要特別加載的模塊,比如zip文件里的模塊,這是在2.3 加入的特性(12.5.7 小節(jié),396 頁(yè))。python2.4 只讓你執(zhí)行標(biāo)準(zhǔn)的庫(kù)模塊。所以初始版本的-m 選項(xiàng)是不能運(yùn)行特殊的模塊如PyCHecker(python 的lint),或其他的profiler(注意這些是裝載和運(yùn)行其他模塊的模塊)。但是2.5 版本解決了這個(gè)問(wèn)題。
14.5 執(zhí)行其他(非Python)程序
在python 程序里也可以執(zhí)行非python 程序。這些程序包括了二進(jìn)制可執(zhí)行文件,其他的shell 腳本等等。所有的要求只是一個(gè)有效的執(zhí)行環(huán)境,比如,允許文件訪(fǎng)問(wèn)和執(zhí)行,腳本文件必須能訪(fǎng)問(wèn)它們的解釋器(perl, bash,等等),二進(jìn)制必須是可訪(fǎng)問(wèn)的(和本地機(jī)器的構(gòu)架兼容)最終,程序員必須kao慮python 腳本是否必須和其他將要執(zhí)行的程序通信。有些程序需要輸入,而有的程序返回輸出以及執(zhí)行完成時(shí)的錯(cuò)誤代碼,也許有的兩者都做。針對(duì)不同的環(huán)境,python 提供了各種執(zhí)行非python 程序的方法。本節(jié)討論的所有函數(shù)都可以在os 模塊中找到。在表14.6 中,我們做了總結(jié)(我們會(huì)對(duì)那些只適合特定平臺(tái)的函數(shù)進(jìn)行標(biāo)注),作為對(duì)本節(jié)剩余部分的介紹。
隨著越來(lái)越接近軟件的操作系統(tǒng)層面,你就會(huì)發(fā)現(xiàn)執(zhí)行跨平臺(tái)程序(甚至是python 腳本)的一致性開(kāi)始有些不確定了。在這個(gè)小節(jié)中描述的程序在os 模塊中。事實(shí)上,有多個(gè)os模塊。比如說(shuō),基于Unix 衍生系統(tǒng)(例如Linux,MacOS X, Solaris,BSD 等等)的模塊是posix 模塊,windows 的是nt(無(wú)論你現(xiàn)在用的是哪個(gè)版本的windows;dos 用戶(hù)有dos 模塊),舊的macOS 為mac 模塊。不用擔(dān)心,當(dāng)你調(diào)用import os 的時(shí)候,python 會(huì)裝載正確的模塊。你不需要直接導(dǎo)入特定的操作系統(tǒng)模塊。
在我們看看每個(gè)模塊函數(shù)之前,對(duì)于python2.4 或者更新版本的用戶(hù),這里有個(gè)subprocess 模塊,可以作為上面所有函數(shù)很好的替代品。本章稍后演示如何使用這些函數(shù),然后在最后給出subprocess.Popen 類(lèi)和subprocess.call()函數(shù)的等價(jià)使用方法。
14.5.1 os.system()
第一個(gè)函數(shù)是system(),接收字符串形式的系統(tǒng)命令并執(zhí)行它。當(dāng)執(zhí)行命令的時(shí)候,python 的運(yùn)行是掛起的。當(dāng)執(zhí)行完成之后,將會(huì)以system()的返回值形式給出退出狀態(tài),python 的執(zhí)行也會(huì)繼續(xù)。
system()保留了現(xiàn)有的標(biāo)準(zhǔn)文件,包括標(biāo)準(zhǔn)的輸出,意味著執(zhí)行任何的命令和程序顯示輸出都會(huì)傳到標(biāo)準(zhǔn)輸出上。這里要當(dāng)心,因?yàn)樘囟☉?yīng)用程序比如公共網(wǎng)關(guān)接口(CGI),如果將除了有效的超文本標(biāo)示語(yǔ)言(HTML)字符串之外的輸出,經(jīng)過(guò)標(biāo)準(zhǔn)輸出發(fā)送回客戶(hù)端,會(huì)引起web 瀏覽器錯(cuò)誤。system()通常和不會(huì)產(chǎn)生輸出的命令一起使用,其中的一些命令包括了壓縮或轉(zhuǎn)換文件的程序,掛載磁盤(pán)到系統(tǒng)的程序,或其他執(zhí)行特定任務(wù)的命令---通過(guò)退出狀態(tài)顯示成功或失敗而不是通過(guò)輸入和/或輸出通信。利用退出狀態(tài),0 表示成功,非零表示其他類(lèi)型的錯(cuò)誤。
為了給出一個(gè)例子,我們執(zhí)行了兩個(gè)從交互解釋器中獲取程序輸入的命令,這樣你便可以觀(guān)察system()是如何工作的:
>>>?import?os >>>?result?=?os.system('cat?/etc/motd')?Have?a?lot?of?fun... >>>?result 0 >>>?result?=?os.system('uname?-a') Linux?solo?2.2.13?#1?Mon?Nov?8?15:08:22?CET?1999?i586?unknown >>>?result 0可以看到兩個(gè)命令的輸出和它們執(zhí)行的退出狀態(tài)。下面是一個(gè)執(zhí)行dos 命令的例子:
14.5.2 os.popen()
popen()函數(shù)是文件對(duì)象和system()函數(shù)的結(jié)合,工作方式和system()相同,但它可以建立一個(gè)指向那個(gè)程序的單向連接,然后如訪(fǎng)問(wèn)文件一樣訪(fǎng)問(wèn)這個(gè)程序。如果程序要求輸入,要用'w'模式寫(xiě)入那個(gè)命令來(lái)調(diào)用popen()。發(fā)送給程序的數(shù)據(jù)會(huì)通過(guò)標(biāo)準(zhǔn)輸入接收到。
同樣地,'r'模式允許spawn 命令,當(dāng)它寫(xiě)入標(biāo)準(zhǔn)輸出的時(shí)候,你就可以通過(guò)類(lèi)文件句柄使用熟悉的file 對(duì)象的read*()方法來(lái)讀取輸入。就像對(duì)于文件,當(dāng)使用完畢以后,你應(yīng)當(dāng)close()連接。調(diào)用unix 程序uname 提供機(jī)器和使用的操作系統(tǒng)的相關(guān)信息。該命令產(chǎn)生了一行輸出,并直接寫(xiě)到屏幕上。如果想要把該字符串讀入變量中并執(zhí)行內(nèi)部操作或者把它存儲(chǔ)到日志文件中,我們可以使用popen()。實(shí)際上,代碼如下所示:
>>>?import?os >>>?f?=?os.popen('uname?-a') >>>?data?=?f.readline() >>>?f.close() >>>?print?data, Linux?solo?2.2.13?#1?Mon?Nov?8?15:08:22?CET?1999?i586?unknown如你所見(jiàn),popen()返回一個(gè)類(lèi)文件對(duì)象;注意readline(),往往,保留輸入文本行尾的newline字符。
14.5.3 os.fork(), os.exec*(),os.wait*()
我們不會(huì)對(duì)操作系統(tǒng)理論做詳盡的介紹,只是稍稍地介紹一下進(jìn)程(process)。fork()采用稱(chēng)為進(jìn)程的單一執(zhí)行流程控制,可稱(chēng)之為創(chuàng)建“岔路口”。用戶(hù)系統(tǒng)同時(shí)接管了兩個(gè)fork——也就是說(shuō)讓用戶(hù)擁有了兩個(gè)連續(xù)且并行的程序。(不用說(shuō),它們運(yùn)行的是同一個(gè)程序,因?yàn)閮蓚€(gè)進(jìn)程都是緊跟在fork()調(diào)用后的下一行代碼開(kāi)始執(zhí)行的)。調(diào)用fork()的原始進(jìn)程稱(chēng)為父進(jìn)程,而作為該調(diào)用結(jié)果新創(chuàng)建的進(jìn)程則稱(chēng)為子進(jìn)程。當(dāng)子進(jìn)程返回的時(shí)候,其返回值永遠(yuǎn)是0;當(dāng)父進(jìn)程返回時(shí),其返回值永遠(yuǎn)是子進(jìn)程的進(jìn)程標(biāo)識(shí)符(又稱(chēng)進(jìn)程ID,或PID)(這樣父進(jìn)程就可以監(jiān)控所有的子進(jìn)程了)PID 也是唯一可以區(qū)分他們的方式!我們提到了兩個(gè)進(jìn)程會(huì)在調(diào)用fork()后立刻運(yùn)行。因?yàn)榇a是相同的,如果沒(méi)有其他的動(dòng)作,我們將會(huì)看到同樣的執(zhí)行結(jié)果。而這通常不是我們想要的結(jié)果。創(chuàng)建另外一個(gè)進(jìn)程的主要目的是為了運(yùn)行其他程序,所以必須在父進(jìn)程和子進(jìn)程返回時(shí)采取分流措施。正如上面我們所說(shuō),它們的PID 是不同的,而這正是我們區(qū)分它們的方法。
對(duì)于那些有進(jìn)程管理經(jīng)驗(yàn)的人來(lái)說(shuō),接下來(lái)的這段代碼是再熟悉不過(guò)了:
ret?=?os.fork()?#?spawn?2?processes,?both?return?#產(chǎn)生兩個(gè)進(jìn)程,都返回 if?ret?==?0:?#?child?returns?with?PID?of?0?#子進(jìn)程返回的PID?是0child_suite?#?child?code?#子進(jìn)程的代碼 else:?#?parent?returns?with?child's?PID?#父進(jìn)程返回是子進(jìn)程的PIDparent_suite?#?parent?code?#父進(jìn)程的代碼在代碼第一行便調(diào)用了fork()。現(xiàn)在子進(jìn)程和父進(jìn)程同時(shí)在運(yùn)行。子進(jìn)程本身有虛擬內(nèi)存地址空間的拷貝,以及一份父進(jìn)程地址空間的原樣拷貝。-----是的,兩者幾乎都是相同的。fork()返回兩次,意味著父進(jìn)程和子進(jìn)程都返回了。如何區(qū)分兩者呢?當(dāng)父親返回的時(shí)候,會(huì)帶有進(jìn)程的PID。而當(dāng)子進(jìn)程返回的時(shí)候,其返回值為0。
利用if-else 語(yǔ)句,給子進(jìn)程和父進(jìn)程指定各自的執(zhí)行代碼。在子進(jìn)程的代碼中,可以調(diào)用任何exec*()函數(shù)來(lái)運(yùn)行完全不同的程序,或者同一個(gè)程序中的其他的函數(shù)(只要子進(jìn)程和父進(jìn)程用不同的路徑執(zhí)行)。普遍做法是讓子進(jìn)程做所有的臟活,而父進(jìn)程耐心等來(lái)子進(jìn)程完成任務(wù),或繼續(xù)執(zhí)行,稍后再來(lái)檢查子進(jìn)程是否正常結(jié)束。
所有的exec*()函數(shù)裝載文件或者命令,并用參數(shù)列表(分別給出或作為參數(shù)列表的一部分)來(lái)執(zhí)行它。如果適用的話(huà),也可以給命令提供環(huán)境變量字典。這些變量普遍用于給程序提供對(duì)當(dāng)前執(zhí)行環(huán)境的精確描述。其中一些著名的變量包括用戶(hù)的名字,搜索路徑,現(xiàn)在的shell,終端類(lèi)型,本地化語(yǔ)言,機(jī)器類(lèi)型,操作系統(tǒng)名字等等。
所有版本的exec*()都會(huì)用給定文件作為現(xiàn)在要執(zhí)行的程序取代當(dāng)前(子)進(jìn)程的Python 解釋器。和system()不一樣,對(duì)于Python 來(lái)說(shuō)沒(méi)有返回值(因?yàn)镻ython 已經(jīng)被替代了)。如果因?yàn)槟撤N原因,程序不能執(zhí)行,那么exec*()就會(huì)失敗,進(jìn)而導(dǎo)致引發(fā)異常。
接下來(lái)的代碼在子進(jìn)程中開(kāi)始了一個(gè)稱(chēng)為“xbill"的可愛(ài)小巧的游戲,而父進(jìn)程繼續(xù)運(yùn)行Python解釋器。因?yàn)樽舆M(jìn)程從不返回,所以無(wú)需去顧慮調(diào)用exec*()后的子進(jìn)程代碼。注意該命令也是參數(shù)列表中的必須的第一個(gè)參數(shù)。
ret?=?os.fork() if?ret?==?0:?#?child?code?#子進(jìn)程代碼execvp('xbill',?['xbill']) else:?#?parent?code?#父進(jìn)程代碼?os.wait()可以看到對(duì)wait()的調(diào)用。當(dāng)子進(jìn)程執(zhí)行完畢,需要它們的父進(jìn)程進(jìn)行掃尾工作。這個(gè)任務(wù),稱(chēng)為”收獲孩子”(reaping a child),可以用wati*()函數(shù)完成。緊跟在fork()之后,父進(jìn)程可以等待子進(jìn)程完成并在那進(jìn)行掃尾。父進(jìn)程也可以繼續(xù)運(yùn)行,稍后再掃尾,同樣也是用wait*()函數(shù)中的一個(gè)。不管父進(jìn)程選擇了那個(gè)方法,該工作都必須進(jìn)行。當(dāng)子進(jìn)程完成執(zhí)行,還沒(méi)有被收獲的時(shí)候,它進(jìn)入了閑置狀態(tài),變成了著名的僵尸進(jìn)程。在系統(tǒng)中,應(yīng)該盡量把僵尸進(jìn)程的數(shù)目降到最少,因?yàn)樵谶@種狀態(tài)下的子進(jìn)程仍保留著在存活時(shí)期分配給它們的系統(tǒng)資源,而這些資源只能在父進(jìn)程收獲它們之后才能釋放掉。
調(diào)用wait()會(huì)掛起執(zhí)行(比如,waits),直到子進(jìn)程(其他的子進(jìn)程)正常執(zhí)行完畢或通過(guò)信號(hào)終止。wait()將會(huì)收獲子進(jìn)程,釋放所有的資源。如果子進(jìn)程已經(jīng)完成,那么wait()只是進(jìn)行些收獲的過(guò)程。waitpid()具有和wait()相同的的功能,但是多了一個(gè)參數(shù)PID(指定要等待子進(jìn)程的進(jìn)程標(biāo)識(shí)符),以及選項(xiàng)(通常是零或用‘OR’組成的可選標(biāo)志集合)
14.5.4 os.spawn*()
函數(shù)spawn*()家族和fork,exec*()相似,因?yàn)樗鼈冊(cè)谛逻M(jìn)程中執(zhí)行命令;然而,你不需要分別調(diào)用兩個(gè)函數(shù)來(lái)創(chuàng)建進(jìn)程,并讓這個(gè)進(jìn)程執(zhí)行命令。你只需調(diào)用一次spawn*()家族。由于其簡(jiǎn)單性,你放棄了“跟蹤”父進(jìn)程和子進(jìn)程執(zhí)行的能力;該模型類(lèi)似于在線(xiàn)程中啟動(dòng)函數(shù)。還有點(diǎn)不同的是你必須知道傳入spawn*()的魔法模式參數(shù)。在其他的操作系統(tǒng)中(尤其是嵌入式實(shí)時(shí)操作系統(tǒng)[RTOS]),spawn*()比f(wàn)ork()快很多。不是這種情況的操作系統(tǒng)通常使用寫(xiě)實(shí)拷貝(copy-on-write)技術(shù)。參閱python 庫(kù)參kao手冊(cè)來(lái)獲得更多spanw*()的資料。各種spanw*()家族成員是在1.5 和1.6(含1.6)之間加入的。
14.5.5 subprocess 模塊
在python2.3 出來(lái)之后,一些關(guān)于popen5 模塊的工作開(kāi)始展開(kāi)。一開(kāi)始該命名繼承了先前popen*()函數(shù)的傳統(tǒng),但是并沒(méi)有延續(xù)下來(lái),該模塊最終被命名為subproess,其中一個(gè)類(lèi)叫Popen,集中了我們?cè)谶@章討論的大部分面向進(jìn)程的函數(shù)。同樣也有名為call()的便捷函數(shù),可以輕易地取代了os.system()。在python2.4 中,subprocess 初次登場(chǎng)。下面就是演示該模塊的例子:替換 os.system()Linux 上的例子:
>>>?from?subprocess?import?call >>>?import?os >>>?res?=?call(('cat',?'/etc/motd')) Linux?starship?2.4.18-1-686?#4?Sat?Nov?29?10:18:26?EST?2003?i686 GNU/Linux >>>?res 0Win32 例子取代os.popen(),創(chuàng)建Popen()實(shí)例的語(yǔ)法只比調(diào)用os.popen()函數(shù)復(fù)雜了一點(diǎn)
>>>?from?subprocess?import?Popen,?PIPE >>>?f?=?Popen(('uname',?'-a'),?stdout=PIPE).stdout >>>?data?=?f.readline() >>>?f.close() >>>?print?data, Linux?starship?2.4.18-1-686?#4?Sat?Nov?29?10:18:26?EST?2003?i686 GNU/Linux >>>?f?=?Popen('who',?stdout=PIPE).stdout >>>?data?=?[?eachLine.strip()?for?eachLine?in?f?] >>>?f.close() >>>?for?eachLine?in?data: ...?print?eachLine ...14.5.6 相關(guān)函數(shù)
表14.7 列出了可以執(zhí)行上述任務(wù)的函數(shù)(及其模塊)
14.6 受限執(zhí)行
在python 歷史某個(gè)時(shí)期內(nèi),存在著使用了rexec 和bastion 模塊的限制執(zhí)行的概念。第一個(gè)模塊允許沙盒(sandbox)中的執(zhí)行代碼修改內(nèi)建對(duì)象。第二個(gè)模塊用來(lái)過(guò)濾屬性和包裝你的類(lèi)。然而,由于一個(gè)顯著的缺點(diǎn)和彌補(bǔ)安全漏洞的困難,這些模塊便被廢棄了。那些維護(hù)使用了這些模塊的老代碼的人員可能會(huì)用到這兩個(gè)模塊的文檔。
14.6 結(jié)束執(zhí)行
當(dāng)程序運(yùn)行完成,所有模塊最高級(jí)的語(yǔ)句執(zhí)行完畢后退出,我們便稱(chēng)這是干凈的執(zhí)行。可能有很多情況,需要從python 提前退出,比如某種致命錯(cuò)誤,或是不滿(mǎn)足繼續(xù)執(zhí)行的條件的時(shí)候。在python 中,有各種應(yīng)對(duì)錯(cuò)誤的方法。其中之一便是通過(guò)異常和異常處理。
另外一個(gè)方法便是建造一個(gè)“清掃器”方法,這樣便可以把代碼的主要部分放在if 語(yǔ)句里,在沒(méi)有錯(cuò)誤的情況下執(zhí)行,因而可以讓錯(cuò)誤的情況“正常地“終結(jié)。然而,有時(shí)也需要在退出調(diào)用程序的時(shí)候,返回錯(cuò)誤代碼以表明發(fā)生何種事件。
14.7.1 sys.exit() and SystemExit
立即退出程序并返回調(diào)用程序的主要方式是sys 模塊中的exit()函數(shù)。sys.exit()的語(yǔ)法為:
sys.exit(status=0)當(dāng)調(diào)用sys.exit()時(shí),就會(huì)引發(fā)systemExit()異常。除非對(duì)異常進(jìn)行監(jiān)控(在一個(gè)try 語(yǔ)句和合適的except 子句中),異常通常是不會(huì)被捕捉到或處理的,解釋器會(huì)用給定的狀態(tài)參數(shù)退出,如果沒(méi)有給出的話(huà),該參數(shù)默認(rèn)為0。System Exit 是唯一不看作錯(cuò)誤的異常。它僅僅表示要退出python的愿望。
sys.exit()經(jīng)常用在命令調(diào)用的中途發(fā)現(xiàn)錯(cuò)誤之后,比如,如果參數(shù)不正確,無(wú)效,或者參數(shù)數(shù)目不正確。下面的例子14.4(args.py)僅僅是一個(gè)測(cè)試腳本,在正確執(zhí)行之前需要給出確定數(shù)目的參數(shù)。
執(zhí)行這個(gè)腳本我們得到如下輸出:
$?args.py At?least?2?arguments?required?(incl.?cmd?name).?usage:?args.py?arg1?arg2 [arg3...?] $?args.py?XXX At?least?2?arguments?required?(incl.?cmd?name).?usage:?args.py?arg1?arg2?[arg3...?] $?args.py?123?abc number?of?args?entered:?3 args?(incl.?cmd?name)?were:?['args.py',?'123',?'abc'] $?args.py?-x?-2?foo number?of?args?entered:?4 args?(incl.?cmd?name)?were:?['args.py',?'-x',?'-2', 'foo'] Example?14.4?Exiting?Immediately?(args.py)?立即退出?(args.py)調(diào)用sys.exit()使python 解釋器退出。exit()的任何整數(shù)參數(shù)都會(huì)以退出狀態(tài)返回給調(diào)用者,該值默認(rèn)為0;
1 #!/usr/bin/env python
2
3 import sys
4
5 def usage():
6 print 'At least 2 arguments (incl. cmd name).'
7 print 'usage: args.py arg1 arg2 [arg3... ]'
8 sys.exit(1)
9
10 argc = len(sys.argv)
11 if argc < 3:
12 usage()
13 print "number of args entered:", argc
14 print "args (incl. cmd name) were:", sys.argv
許多命令行驅(qū)動(dòng)的程序在進(jìn)行之前,用腳本的核心功能測(cè)試了輸入的有效性。如果驗(yàn)證失敗,那么便調(diào)用usage()函數(shù)去告知用戶(hù)什么樣的問(wèn)題會(huì)導(dǎo)致這個(gè)錯(cuò)誤,并"提示"用戶(hù)如何才能正確地調(diào)用腳本。
14.7.2 sys.exitfunc()
sys.exitfunc()默認(rèn)是不可用的,但你可以改寫(xiě)它以提供額外的功能。當(dāng)調(diào)用了sys.exit()并在解釋器退出之前,就會(huì)用到這個(gè)函數(shù)了。這個(gè)函數(shù)不帶任何參數(shù)的,所以你創(chuàng)建的函數(shù)也應(yīng)該是無(wú)參的。
如果sys.exitfunc 已經(jīng)被先前定義的exit 函數(shù)覆蓋了,最好的方法是把這段代碼作為你exit()函數(shù)的一部分來(lái)執(zhí)行。一般說(shuō)來(lái),exit 函數(shù)用于執(zhí)行某些類(lèi)型的關(guān)閉活動(dòng),比如關(guān)閉文件和網(wǎng)絡(luò)連接,最好用于完成維護(hù)任務(wù),比如釋放先前保留的系統(tǒng)資源。
下面的例子介紹了如何設(shè)置exit()函數(shù),如果已經(jīng)被設(shè)置了,則確保執(zhí)行該函數(shù):
import sys
prev_exit_func = getattr(sys, 'exitfunc', None)
def my_exit_func(old_exit = prev_exit_func):
# :
# perform cleanup 進(jìn)行清理
# :
if old_exit is not None and callable(old_exit):
old_exit()
sys.exitfunc = my_exit_func
在清理執(zhí)行以后,我們執(zhí)行了老的exit()函數(shù)。getattr()調(diào)用只是檢查了先前的exitfunc()是否已經(jīng)定義。如果沒(méi)有,那么prev_exit_func 賦值為None,否則, prev_exit_func 變成exit 函數(shù)新的別名,然后作為參數(shù)傳入我們的新exit 函數(shù),my_exit_func。
對(duì)getattr()的調(diào)用可以這樣寫(xiě):
if hasattr(sys, 'exitfunc'):
prev_exit_func = sys.exitfunc # getattr(sys, 'exitfunc')
else:
prev_exit_func = None
14.7.3 os._exit() Function os._exit() 函數(shù)
os 模塊的_exit()函數(shù)不應(yīng)該在一般應(yīng)用中使用。(平臺(tái)相關(guān),只適用特定的平臺(tái),比如基于Unix的平臺(tái),以及Win32 平臺(tái))。其語(yǔ)法為:
os._exit(status)
這個(gè)函數(shù)提供的功能與sys.exit()和sys.exitfunc()相反,根本不執(zhí)行任何清理便立即退出python。與sys.exit()不同,狀態(tài)參數(shù)是必需的。通過(guò)sys.exit()退出是退出解釋器的首選方法。
14.7.4 os.kill() Function
os 模塊的kill()函數(shù)模擬傳統(tǒng)的unix 函數(shù)來(lái)發(fā)送信號(hào)給進(jìn)程。kill()參數(shù)是進(jìn)程標(biāo)識(shí)數(shù)(PID)和你想要發(fā)送到進(jìn)程的信號(hào)。發(fā)送的典型信號(hào)為SIGINT, SIGQUIT,或更徹底地,SIGKILL,來(lái)使進(jìn)程終結(jié)。
14.8 各種操作系統(tǒng)接口
在一章中,我們已看到各種通過(guò)os 模塊和操作系統(tǒng)進(jìn)行交互的方法。我們看到的大多數(shù)函數(shù)都是處理文件或外部進(jìn)程執(zhí)行。這里有些 方法允許對(duì)現(xiàn)在的用戶(hù)和進(jìn)程有較特殊的動(dòng)作,我們將簡(jiǎn)要地看看。表14.8 中描述的大部分函數(shù)只在posix 系統(tǒng)上工作,除非標(biāo)明了適用于Windows 環(huán)境。
14.9 相關(guān)模塊
在表14.9 中, 除了os 和sys 模塊,你還可以找到與這章執(zhí)行環(huán)境主題相關(guān)的模塊列表。
轉(zhuǎn)載于:https://my.oschina.net/cqlcql/blog/662512
總結(jié)
以上是生活随笔為你收集整理的python浓缩(14)执行环境的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [Unity] 3D数学基础 - 坐标系
- 下一篇: Python脚本备份数据库