总结:

有多种函数,能够传入两种对象非常有用:一种是表示路径的对象(通常是字符串),另一种是表示某种流的对象(通常是从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...

但现在我有了一个 idea ,那就是使用第三个函数将其他两个版本结合起来,这取决于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

有没有更好的建议?我是否偏离了底线,需要以完全不同的方式处理这件事?

推荐答案

就我个人而言,这是一个固执己见的答案,判断类文件对象的属性以进行所需的操作是a pythonic方法来确定对象的类型,因为这是pythonic duck tests/duck-typing的本质:

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

Or from the python docs’ definition of duck-typing

一种编程风格,它不查看对象的类型来确定它是否有正确的接口;相反,方法或属性只是通过强调接口而不是特定类型来调用或使用("If it looks like a duck and quacks like a duck, it must be a duck."),精心设计的代码通过允许多态替换来提高其灵活性.Duck类型避免使用type()isinstance()进行测试.(但是请注意,duck类型可以用抽象基类来补充.)相反,它通常使用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__()

Python-3.x相关问答推荐

IPython似乎已安装但无法运行

TypeError:&Quot;Value&Quot;参数必须是标量、Dict或Series,但您传递了&Quot;Index&Quot;

无法使用Python slack 螺栓SDK读取在 slack 通道中收到的消息

当索引大于一个整数而小于前一个索引时,我如何返回列值?

将列表项的极列水平分解为新列

从Metacritic上通过网络擦除游戏数据的问题

在REPLACE INTO中引用变量会抛出sqlite3.OperationalError

拆分列表的元素并将拆分后的元素包含到列表中

Django - ValueError:无法将字符串转换为浮点数:''

spinbutton调整up/down箭头

python 3:如何判断一个对象是否是一个函数?

在 Python 3.5 中使用 aiohttp 获取多个 url

Python:如何判断一个项目是否被添加到一个集合中,没有 2x(hash,lookup)

Python图例属性错误

Pylint 给我最后的新行丢失

如何通过命令行将数组传递给python

如何在 Python 中计算两个包含字符串的列表的 Jaccard 相似度?

如何调试垂死的 Jupyter Python3 内核?

迭代器也是可迭代的吗?

Django Rest 框架 ListField 和 DictField