如何区分对象等文件与对象等文件路径

Ric*_*ica 16 python python-3.x

摘要:

有多种函数可以传递两种对象非常有用:表示路径的对象(通常是字符串),以及表示某种流的对象(通常是派生的东西)来自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...
Run Code Online (Sandbox Code Playgroud)

这有效.好极了.但我宁愿不必担心首先打开文件或传递流,至少有时...所以我重构能够采取像对象的文件路径而不是像对象这样的文件,并且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...
Run Code Online (Sandbox Code Playgroud)

但是现在我认为有一个第三个函数组合其他两个版本会有用,这取决于file文件是否像文件路径一样,但是将f目标文件像对象一样返回给上下文管理器.这样我就可以编写这样的代码:

with  spiffy_file_makerAB(spiffy_obj, file_path_like, mode = 'x') as f:
    ...do other stuff with f...
Run Code Online (Sandbox Code Playgroud)

......但也像这样:

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` 
Run Code Online (Sandbox Code Playgroud)

请注意,这将需要与上面提供的简化版本略有不同的内容.

尽我所能,我还没有找到一个明显的方法来做到这一点,我发现的方式看起来很有人工作,以后可能会遇到问题.例如:

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
Run Code Online (Sandbox Code Playgroud)

对于更好的方法有什么建议吗?我是否会偏离基地,需要完全不同地处理这个问题?

2ps*_*2ps 14

对于我的钱,这是一个固执己见的答案,检查你需要的操作的类文件对象的属性是一种确定对象类型 pythonic方法,因为这是pythonic duck tests/duck-typing的本质:

在Python中大量使用Duck类型,规范示例是类文件类(例如,cStringIO允许将Python字符串视为文件).

或者从python docs的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
Run Code Online (Sandbox Code Playgroud)

在任何情况下,对于您的上下文管理器,我会继续创建一个如下所示的完整对象,以包装您正在寻找的功能.

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__()
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

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')
Run Code Online (Sandbox Code Playgroud)

如果您想使用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__()
Run Code Online (Sandbox Code Playgroud)

  • 这个答案是对鸭子类型优点的快速教育,它揭示了一种模式,不仅可用于解决 Python 中的类似问题,而且还可用于其他语言,并且也是一个很好的引用。 (2认同)