2012-08-10 113 views
3

注意:如果您知道我想要的任何(非精細)庫代碼,請啓發C/C++程序員,我將接受該答案作爲答案。Python對象生命週期特徵

我有一個全局變量設置爲以下類的一個實例。它的目的是允許我設置一些手動中斷點,以便在scrapy蜘蛛中放置一些快速且髒的樣式調試點(當特定標準符合調諧解析器時,我特別需要中斷,還有一些非常罕見的輸入數據異常) - 改編自this

Os是OS X 10.8。

import termios, fcntl, sys, os 

class DebugWaitKeypress(object): 
    def __init__(self): 
     self.fd = sys.stdin.fileno() 
     self.oldterm = termios.tcgetattr(self.fd) 
     self.newattr = termios.tcgetattr(self.fd) 
     self.newattr[3] = self.newattr[3] & ~termios.ICANON & ~termios.ECHO 
     termios.tcsetattr(self.fd, termios.TCSANOW, self.newattr) 

     self.oldflags = fcntl.fcntl(self.fd, fcntl.F_GETFL) 
     fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags | os.O_NONBLOCK) 

    def wait(self): 
     sys.stdin.read(1) 

    def __del__(self): 
     print "called del" 
     termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.oldterm) 
     fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags) 

當我按下Ctrl-C和過程展開我得到以下異常:

Exception AttributeError: "'NoneType' object has no attribute 'tcsetattr'" in <bound method DebugWaitKeypress.__del__ of <hon.spiders.custom_debug.DebugWaitKeypress object at 0x108985e50>> ignored 

我失去了一些東西有關對象的壽命我猜的機制?如何補救這種情況。 AFAIK任何類實例應該在導入的代碼之前銷燬,否?按照聲明/定義的相反順序。

我會忽略這一點,如果在進程退出後的終端並沒有搞砸了:d

編輯:

提洛對Seth的回答評論使我明白,我需要使用類似於函數,或者作爲根函數支配並在那裏初始化上下文的任何其他函數/生成器。這種方式當進程正在關閉時,上下文管理器的__exit__方法將被調用。我不需要在每個wait()調用上重新編程終端流。

儘管重新編程的代價可能並不重要,但知道Python中這些基本的C/C++語義如何是很好的。

編輯2:用標準輸入搞亂當

扭曲(其scrapy用途)變爲apeshit。所以我必須用文件IO來解決問題。

回答

5

長話短說:__del__是無用的這個目的(和幾乎任何其他目的;你應該忘記它的存在)。如果您需要確定性清理,請使用上下文管理器。

AFAIK任何類實例應該在導入的代碼之前銷燬,否?按照聲明/定義的相反順序。

這就是C++。算了吧。 Python並不關心這一點,實際上它甚至不關心大多數需要這樣做的事情。在整個Python語言中沒有這樣的聲明,模塊級變量存儲在本質上是無序的關聯數組中。變量不存儲對象,它們存儲引用(它們是而不是 C++引用,它們基本上是指針,沒有指針算術) - 對象位於堆上,不知道變量,綁定,語句或順序的陳述。

此外,當一個對象被垃圾收集,並且不管它是否全部爲,都是未定義的。由於引用計數,你會在CPython中獲得大多數爲確定性圖片,但即使在那裏,它也會在你有周期的秒鐘內下降。其結果是__del__可能會在任何時間點(包括模塊的一半已被拆除)或根本不會被調用。定義__del__引用對象的多個對象也很麻煩,儘管有些GC試圖做正確的事情。底線是,您可以在__del__運行時承擔很少的工作,所以您不能做太多。你可以通過另一種方法獲得應該清理的資源,但不是這樣的資源。經驗法則:從不依靠它爲任何東西強制性。

取而代之,創建一個context manager and use it via with。你得到確定性的清理,而不用擔心對象的生命週期。因爲,被告知的事實,對象生命週期和資源生命週期是兩個完全不同的事情,並且只是糾纏在C++中,因爲它是在該環境中執行資源管理的最佳方式。在Python,RAII不適,相反,我們有這樣的:

with <context manager> as var: 
    # do something 
# "context closed", whatever that means - for resources, usually cleanup 

順便說一句,你可以遠遠通過contextlib更方便地定義它(從您的版本很快音譯,可能包含錯誤或醜陋):

from contextlib import contextmanager 


@contextmanager 
def debug_wait_keypress(): 
    fd = sys.stdin.fileno() 
    oldterm = termios.tcgetattr(fd) 
    newattr = termios.tcgetattr(fd) 
    newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO 
    termios.tcsetattr(fd, termios.TCSANOW, newattr) 
    oldflags = fcntl.fcntl(fd, fcntl.F_GETFL) 
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) 
    try: 
     yield 
    finally: 
     termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) 
     fcntl.fcntl(fd, fcntl.F_SETFL, oldflags) 

您的wait方法成爲一項免費功能。

3

如果__del__被調用,它發生一段時間後該對象的引用計數爲零,可能直到節目結束,而不是在任何特定的順序。你也不能依靠任何外部(特別是全局變量)在__del__中可用。

在你的情況下,python在調用DebugWaitKeyPress.__del__之前清除了對termios模塊的引用。這就是爲什麼你得到'NoneType' object has no attribute 'tcsetattr'消息。 termiosNone到您嘗試使用它。

我想你會更好地執行context manager,並把你的__del__代碼__exit__

然後你就可以說是這樣的:

with DebugWaitKeypress(...) as thing: 
    do_something_with_it(thing) 
# here, __exit__() is called to do cleanup 

object.__del__ docs

由於被 調用其下__del __()方法岌岌可危的情況下,期間發生的異常他們的執行被忽略,並且 改爲向sys.stderr發送警告。另外,當__del __()爲 以響應模塊被刪除而調用時(例如,當程序完成時執行 )時,由__del __()方法 引用的其他全局變量可能已經被刪除或正在被刪除拆除 (例如進口機械關閉)。出於這個原因,__del __() 方法應該盡最大可能保持外部不變量。從版本1.5開始,Python保證在刪除其他全局變量之前,從其 模塊中刪除名稱以單個下劃線開頭的全局變量 ;如果不存在其他對 這樣的全局變量的引用,這可能有助於確保導入的模塊 在__del __()方法被調用時仍然可用。

+0

嗯有沒有其他的上下文管理器可能的結構?該文檔似乎表明該構造可用於語句塊。這意味着每次我想要打斷點時,我必須重新配置終端兩次。 – 2012-08-10 01:30:30

+0

@HassanSyed你似乎低估了它。作爲*任何*(甚至friggin的導入和類定義,儘管你很難找到一個好的用例)可以進入上下文管理器,例如,你可以將調用包裝到你的'main'函數中一個,它會在發生任何事情之前配置終端,並且僅當'main'離開時重置配置(通常由於異常導致*或*)。就像你可以在C++的'main'中的堆棧上創建'DebugWaitKeypress'一樣。 – delnan 2012-08-10 01:33:10

+0

嗯,這是我認爲我需要的洞察力。我可以在作爲生成器實現的scrapy的'parse'方法中初始化一個全局類,我在生成器的頂部啓動上下文,然後當我用完url時返回生成器超出範圍,它在scrapy之前開始關閉。 – 2012-08-10 01:42:48