[英]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字符串視為文件)。
一種編程風格,它不會查看對象的類型以確定它是否具有正確的接口; 相反,簡單地調用或使用方法或屬性(“ 如果它看起來像鴨子,像鴨子一樣嘎嘎叫,它必須是鴨子。 ”)通過強調接口而不是特定類型,精心設計的代碼通過允許改進其靈活性多態替換。 Duck-typing避免使用
type()
或isinstance()
。 (但請注意,鴨子類型可以用抽象基類來補充。)相反,它通常使用hasattr()
測試或EAFP編程。
如果您非常強烈地認為僅僅檢查接口的適用性是不夠的,那么您可以反轉測試並測試basestring
或str
來測試提供的對象是否類似於路徑。 測試將根據您的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.