簡體   English   中英

如何區分對象等文件與對象等文件路徑

[英]How to differentiate a file like object from a file path like object

摘要:

有多種函數可以傳遞兩種對象非常有用:表示路徑的對象(通常是字符串)和表示某種流的對象(通常是派生的東西)來自IOBase ,但並非總是如此)。 這種功能如何區分這兩種對象,以便適當處理?


假設我有一個函數用於從某種對象文件生成器方法寫入文件:

spiff = MySpiffy()

def spiffy_file_makerA(spiffy_obj, file):
    file_str = '\n'.join(spiffy_obj.gen_file()) 
    file.write(file_str)

with open('spiff.out', 'x') as f:
    spiffy_file_makerA(spiff, f)
    ...do other stuff with f...

這有效。 好極了。 但我寧願不必擔心首先打開文件或傳遞流,至少有時...所以我重構能夠采取像對象的文件路徑而不是像對象這樣的文件,以及return語句:

def spiffy_file_makerB(spiffy_obj, file, mode):
    file_str = '\n'.join(spiffy_obj.gen_file()) 
    file = open(file, mode)
    file.write(file_str)
    return file

with spiffy_file_makerB(spiff, 'file.out', 'x') as f:
    ...do other stuff with f...

但是現在我認為,根據file是文件,還是文件路徑之類的第三個函數組合其他兩個版本會很有用,但是像對象一樣將f目標文件返回給上下文管理器。 這樣我就可以編寫這樣的代碼:

with  spiffy_file_makerAB(spiffy_obj, file_path_like, mode = 'x') as f:
    ...do other stuff with f...

......但也像這樣:

file_like_obj = get_some_socket_or_stream()

with spiffy_file_makerAB(spiffy_obj, file_like_obj, mode = 'x'):
    ...do other stuff with file_like_obj...
    # file_like_obj stream closes when context manager exits 
    # unless `closefd=False` 

請注意,這將需要與上面提供的簡化版本略有不同的內容。

盡我所能,我還沒有找到一個明顯的方法來做到這一點,我發現的方式看起來很有人工作,以后可能會遇到問題。 例如:

def spiffy_file_makerAB(spiffy_obj, file, mode, *, closefd=True):
    try: 
        # file-like (use the file descriptor to open)
        result_f = open(file.fileno(), mode, closefd=closefd)
    except TypeError: 
        # file-path-like
        result_f = open(file, mode)
    finally: 
        file_str = '\n'.join(spiffy_obj.gen_file()) 
        result_f.write(file_str)
        return result_f

對於更好的方法有什么建議嗎? 我是否會偏離基地,需要完全不同地處理這個問題?

對於我的錢,這是一個固執己見的答案,檢查你需要的操作的類文件對象的屬性是一種確定對象類型 pythonic方法,因為這是pythonic duck tests / duck-typing的本質:

在Python中大量使用Duck類型,規范示例是類文件類(例如, cStringIO允許將Python字符串視為文件)。

或者從python docs的duck-typing定義

一種編程風格,它不會查看對象的類型以確定它是否具有正確的接口; 相反,簡單地調用或使用方法或屬性(“ 如果它看起來像鴨子,像鴨子一樣嘎嘎,它必須是鴨子。 ”)通過強調接口而不是特定類型,精心設計的代碼通過允許改進其靈活性多態替換。 Duck-typing避免使用type()isinstance() (但請注意,鴨子類型可以用抽象基類來補充。)相反,它通常使用hasattr()測試或EAFP編程。

如果您非常強烈地認為僅僅檢查接口的適用性是不夠的,那么您可以反轉測試並測試basestringstr來測試提供的對象是否類似於路徑。 測試將根據您的python版本而有所不同。

is_file_like = not isinstance(fp, basestring) # python 2
is_file_like = not isinstance(fp, str) # python 3

在任何情況下,對於您的上下文管理器,我會繼續創建一個如下所示的完整對象,以包裝您正在尋找的功能。

class SpiffyContextGuard(object):
    def __init__(self, spiffy_obj, file, mode, closefd=True):
        self.spiffy_obj = spiffy_obj
        is_file_like = all(hasattr(attr) for attr in ('seek', 'close', 'read', 'write'))
        self.fp = file if is_file_like else open(file, mode)
        self.closefd = closefd

    def __enter__(self):
        return self.fp

    def __exit__(self, type_, value, traceback):
        generated = '\n'.join(self.spiffy_obj.gen_file())
        self.fp.write(generated)
        if self.closefd:
            self.fp.__exit__()

然后像這樣使用它:

with SpiffyContextGuard(obj, 'hamlet.txt', 'w', True) as f:
    f.write('Oh that this too too sullied flesh\n')

fp = open('hamlet.txt', 'a')
with SpiffyContextGuard(obj, fp, 'a', False) as f:
    f.write('Would melt, thaw, resolve itself into a dew\n')

with SpiffyContextGuard(obj, fp, 'a', True) as f:
    f.write('Or that the everlasting had not fixed his canon\n')

如果您想使用try / catch語義來檢查類型是否適合,您還可以在上下文保護中包含您想要公開的文件操作:

class SpiffyContextGuard(object):
    def __init__(self, spiffy_obj, file, mode, closefd=True):
        self.spiffy_obj = spiffy_obj
        self.fp = self.file_or_path = file 
        self.mode = mode
        self.closefd = closefd

    def seek(self, offset, *args):
        try:
            self.fp.seek(offset, *args)
        except AttributeError:
            self.fp = open(self.file_or_path, mode)
            self.fp.seek(offset, *args)

    # define wrappers for write, read, etc., as well

    def __enter__(self):
        return self

    def __exit__(self, type_, value, traceback):
        generated = '\n'.join(self.spiffy_obj.gen_file())
        self.write(generated)
        if self.closefd:
            self.fp.__exit__()

可能不是你正在尋找的答案,但從品味的角度來看,我認為最好只擁有一件事。 這種方式更容易推理它們。

我只有兩個函數: spiffy_file_makerA(spiffy_obj, file) ,它處理你的第一個案例,以及一個包含spiffy_file_makerA並為你創建文件的便利函數。

我的建議是傳遞pathlib.Path對象 你可以簡單地將.write_bytes(...).write_text(...)到這些對象中。

另外,你必須檢查你的file變量的類型(這是如何在python中完成多態):

from io import IOBase

def some_function(file)
    if isinstance(file, IOBase):
        file.write(...)
    else:
        with open(file, 'w') as file_handler:
            file_handler.write(...)

(我希望io.IOBase是最基本的類來檢查...)。 你必須抓住所有這些可能的例外。

另一個解決這個問題的方法,受到Raymond Hettinger在2013年PyCon上的講話的啟發,將是將兩個函數分開,如其他幾個答案所建議的那樣,但是將這些函數組合成一個具有多個備選選項的類。用於輸出對象。

繼續我開始的例子,它可能看起來像這樣:

class SpiffyFile(object):
    def __init__(self, spiffy_obj, file_path = None, *, mode = 'w'):
        self.spiffy = spiffy_obj
        self.file_path = file_path
        self.mode = mode
    def to_str(self):
        return '\n'.join(self.spiffy.gen_file())
    def to_stream(self, fstream):
        fstream.write(self.to_str())
    def __enter__(self):
        try:
            # do not override an existing stream
            self.fstream
        except AttributeError:
            # convert self.file_path to str to allow for pathlib.Path objects
            self.fstream = open(str(self.file_path), mode = self.mode)
        return self
    def __exit__(self, exc_t, exc_v, tb):
        self.fstream.close()
        del self.fstream
    def to_file(self, file_path = None, mode = None):
        if mode is None:
            mode = self.mode
        try:
            fstream = self.fstream
        except AttributeError:
            if file_path is None:
                file_path = self.file_path
            # convert file_path to str to allow for pathlib.Path objects
            with open(str(file_path), mode = mode) as fstream:
                self.to_stream(fstream)
        else:
            if mode != fstream.mode:
                raise IOError('Ambiguous stream output mode: \
                           provided mode and fstream.mode conflict')
            if file_path is not None:
                raise IOError('Ambiguous output destination: \
                           a file_path was provided with an already active file stream.')
            self.to_stream(fstream)

現在,我們有很多的對於出口而言,不同的選項MySpiffy使用對象SpiffyFile對象。 我們可以直接將它寫入文件:

from pathlib import Path
spiff = MySpiffy()
p = Path('spiffies')/'new_spiff.txt'
SpiffyFile(spiff, p).to_file()

我們也可以覆蓋路徑:

SpiffyFile(spiff).to_file(p.parent/'other_spiff.text')

但我們也可以使用現有的開放流:

SpiffyFile(spiff).to_stream(my_stream)

或者,如果我們想首先編輯字符串,我們可以自己打開一個新的文件流並將編輯后的字符串寫入其中:

my_heading = 'This is a spiffy object\n\n'
with open(str(p), mode = 'w') as fout:
    spiff_out = SpiffyFile(spiff).to_str()
    fout.write(my_heading + spiff_out)

最后,我們可以將SpiffyFile對象的上下文管理器直接用於我們喜歡的多個不同位置或流(請注意,我們可以直接傳遞pathlib.Path對象而不必擔心字符串轉換,這很漂亮):

with SpiffyFile(spiff, p) as spiff_file:
    spiff_file.to_file()
    spiff_file.to_file(p.parent/'new_spiff.txt')
    print(spiff_file.to_str())
    spiff_file.to_stream(my_open_stream)

這種方法與咒語更為一致:顯性優於隱式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM