1

在這個問題上「Generating an MD5 checksum of a file」,我有這樣的代碼:可以參考依靠關閉Python中的文件嗎?

import hashlib 
def hashfile(afile, hasher, blocksize=65536): 
    buf = afile.read(blocksize) 
    while len(buf) > 0: 
     hasher.update(buf) 
     buf = afile.read(blocksize) 
    return hasher.digest() 

[(fname, hashfile(open(fname, 'rb'), hashlib.sha256())) for fname in fnamelst] 

我被批評爲打開列表理解的內部文件,以及一個人認爲,如果我有足夠長的名單我將耗盡打開文件句柄。接口顯着降低了hashfile的靈活性,並建議使用散列文件獲取文件名參數並使用with

是否有必要?我真的做錯了什麼嗎?

測試出這個代碼:

#!/usr/bin/python3 

import sys 
from pprint import pprint # Pretty printing 

class HereAndGone(object): 
    def __init__(self, i): 
     print("%d %x -> coming into existence." % (i, id(self)), 
       file=sys.stderr) 
     self.i_ = i 
    def __del__(self): 
     print("%d %x <- going away now." % (self.i_, id(self)), 
       file=sys.stderr) 

def do_nothing(hag): 
    return id(hag) 

l = [(i, do_nothing(HereAndGone(i))) for i in range(0, 10)] 

pprint(l) 

結果輸出:

0 7f0346decef0 -> coming into existence. 
0 7f0346decef0 <- going away now. 
1 7f0346decef0 -> coming into existence. 
1 7f0346decef0 <- going away now. 
2 7f0346decef0 -> coming into existence. 
2 7f0346decef0 <- going away now. 
3 7f0346decef0 -> coming into existence. 
3 7f0346decef0 <- going away now. 
4 7f0346decef0 -> coming into existence. 
4 7f0346decef0 <- going away now. 
5 7f0346decef0 -> coming into existence. 
5 7f0346decef0 <- going away now. 
6 7f0346decef0 -> coming into existence. 
6 7f0346decef0 <- going away now. 
7 7f0346decef0 -> coming into existence. 
7 7f0346decef0 <- going away now. 
8 7f0346decef0 -> coming into existence. 
8 7f0346decef0 <- going away now. 
9 7f0346decef0 -> coming into existence. 
9 7f0346decef0 <- going away now. 
[(0, 139652050636528), 
(1, 139652050636528), 
(2, 139652050636528), 
(3, 139652050636528), 
(4, 139652050636528), 
(5, 139652050636528), 
(6, 139652050636528), 
(7, 139652050636528), 
(8, 139652050636528), 
(9, 139652050636528)] 

很明顯,正在創建的列表理解的每個元素構成破壞每個HereAndGone對象。只要沒有引用它,Python引用計數就會釋放該對象,這會在計算該列表元素的值後立即發生。

當然,也許一些其他的Python實現不這樣做。 Python實現需要做某種形式的引用計數嗎?從gc模塊的文檔看來,引用計數是該語言的核心功能。

而且,如果我確實做錯了,你會如何建議我重新編寫它以保持列表理解的簡潔明瞭,以及可以像文件一樣讀取的任何接口的靈活性?

+0

「Python實現需要做某種形式的引用計數嗎?」 - 沒有。 – user2357112

+1

「它似乎從gc模塊的文檔中看起來像引用計數是該語言的核心功能。」 - 大多數gc模塊,尤其是關於關閉的部分,應被視爲可選功能。 – user2357112

+0

修改'hashfile',以便獲取文件名並處理打開和關閉文件本身。一般來說,使用系統的內存管理來管理其他資源是一個糟糕的主意。 – tfb

回答

0

有人指出Data Model section of the Python Language Reference它非常明確地表示「當它們變得無法到達時不要依賴對象的即時終結(所以你應該總是明確地關閉文件)」。所以,這表明代碼依賴於不能保證的行爲。

即使它是,它仍然是脆弱的。它無形地依賴於該文件永遠不會被具有超過單個文件散列的循環引用或壽命的數據結構引用。誰知道將來代碼會發生什麼,以及是否有人會記住這個關鍵細節?

問題是該怎麼做。這個問題中的hashfile函數非常靈活,並且弄亂它的接口來獲取文件名並讓它在函數內部打開文件並因此而喪失其靈活性似乎是一種恥辱。我認爲最小的解決方案是這樣的:

我覺得解決方案是重新考慮一下接口,使其更通用。

def hash_bytestr_iter(hasher, bytesiter, ashexstr=False): 
    for block in bytesiter: 
     hasher.update(bytesiter) 
    return (hasher.hexdigest() if ashexstr else hasher.digest()) 

def iter_and_close_file(afile, blocksize=65536): 
    with afile: 
     block = afile.read(blocksize) 
     while len(block) > 0: 
      yield block 

一個可能只是使原來hashfile使用afile作爲上下文經理過去了,但我覺得這種以一種微妙的方式突破的預期。它使hashfile關閉該文件,並且它的名稱只是它會計算散列而不關閉文件。

我懷疑有很多情況下,當你有字節塊,並想散列它們,就好像它們是它們的連續塊或流的一部分。散列一個字節塊的迭代器更加通用,然後散列一個文件。

同樣,我認爲有很多情況下你想迭代一個文件類對象,然後關閉它。所以這使得這兩個功能都是可重用的和一般的。