简体   繁体   English

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

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

Summary: 摘要:

There is a variety of function for which it would be very useful to be able to pass in two kinds of objects: an object that represents a path (usually a string), and an object that represents a stream of some sort (often something derived from IOBase , but not always). 有多种函数可以传递两种对象非常有用:表示路径的对象(通常是字符串)和表示某种流的对象(通常是派生的东西)来自IOBase ,但并非总是如此)。 How can this variety of function differentiate between these two kinds of objects so they can be handled appropriately? 这种功能如何区分这两种对象,以便适当处理?


Say I have a function intended to write a file from some kind of object file generator method: 假设我有一个函数用于从某种对象文件生成器方法写入文件:

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...

This works. 这有效。 Yay. 好极了。 But I'd prefer to not have to worry about opening the file first or passing streams around, at least sometimes... so I refactor with the ability to take a file path like object instead of a file like object, and a return statement: 但我宁愿不必担心首先打开文件或传递流,至少有时...所以我重构能够采取像对象的文件路径而不是像对象这样的文件,以及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...

But now I get the idea that it would be useful to have a third function that combines the other two versions depending on whether file is file like, or file path like, but returns the f destination file like object to a context manager. 但是现在我认为,根据file是文件,还是文件路径之类的第三个函数组合其他两个版本会很有用,但是像对象一样将f目标文件返回给上下文管理器。 So that I can write code like this: 这样我就可以编写这样的代码:

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

...but also like this: ......但也像这样:

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` 

Note that this will require something a bit different than the simplified versions provided above. 请注意,这将需要与上面提供的简化版本略有不同的内容。

Try as a I might, I haven't been able to find an obvious way to do this, and the ways I have found seem pretty contrived and just a potential for problems later. 尽我所能,我还没有找到一个明显的方法来做到这一点,我发现的方式看起来很有人工作,以后可能会遇到问题。 For example: 例如:

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

Are there any suggestions for a better way? 对于更好的方法有什么建议吗? Am I way off base and need to be handling this completely differently? 我是否会偏离基地,需要完全不同地处理这个问题?

For my money, and this is an opinionated answer, checking for the attributes of the file-like object for the operations you will need is a pythonic way to determine an object's type because that is the nature of pythonic duck tests/duck-typing : 对于我的钱,这是一个固执己见的答案,检查你需要的操作的类文件对象的属性是一种确定对象类型 pythonic方法,因为这是pythonic duck tests / duck-typing的本质:

Duck typing is heavily used in Python, with the canonical example being file-like classes (for example, cStringIO allows a Python string to be treated as a file). 在Python中大量使用Duck类型,规范示例是类文件类(例如, cStringIO允许将Python字符串视为文件)。

Or from the python docs' definition of duck-typing 或者从python docs的duck-typing定义

A programming style which does not look at an object's type to determine if it has the right interface; 一种编程风格,它不会查看对象的类型以确定它是否具有正确的接口; instead, the method or attribute is simply called or used (“ If it looks like a duck and quacks like a duck, it must be a duck. ”) By emphasizing interfaces rather than specific types, well-designed code improves its flexibility by allowing polymorphic substitution. 相反,简单地调用或使用方法或属性(“ 如果它看起来像鸭子,像鸭子一样嘎嘎,它必须是鸭子。 ”)通过强调接口而不是特定类型,精心设计的代码通过允许改进其灵活性多态替换。 Duck-typing avoids tests using type() or isinstance() . Duck-typing避免使用type()isinstance() (Note, however, that duck-typing can be complemented with abstract base classes.) Instead, it typically employs hasattr() tests or EAFP programming. (但请注意,鸭子类型可以用抽象基类来补充。)相反,它通常使用hasattr()测试或EAFP编程。

If you feel very strongly that there is some very good reason that just checking the interface for suitability isn't enough, you can just reverse the test and test for basestring or str to test whether the provided object is path-like. 如果您非常强烈地认为仅仅检查接口的适用性是不够的,那么您可以反转测试并测试basestringstr来测试提供的对象是否类似于路径。 The test will be different depending on your version of python. 测试将根据您的python版本而有所不同。

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

In any case, for your context manager, I would go ahead and make a full-blown object like the below in order to wrap the functionality that you were looking for. 在任何情况下,对于您的上下文管理器,我会继续创建一个如下所示的完整对象,以包装您正在寻找的功能。

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__()

And then use it like this: 然后像这样使用它:

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')

If you wanted to use try/catch semantics to check for type suitability, you could also wrap the file operations you wanted to expose on your context guard: 如果您想使用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__()

Probably not the answer you're looking for, but from a taste point of view I think it's better to have functions that only do one thing. 可能不是你正在寻找的答案,但从品味的角度来看,我认为最好只拥有一件事。 Reasoning about them is easier this way. 这种方式更容易推理它们。

I'd just have two functions: spiffy_file_makerA(spiffy_obj, file) , which handles your first case, and a convenience function that wraps spiffy_file_makerA and creates a file for you. 我只有两个函数: spiffy_file_makerA(spiffy_obj, file) ,它处理你的第一个案例,以及一个包含spiffy_file_makerA并为你创建文件的便利函数。

my suggestion is to pass pathlib.Path objects around. 我的建议是传递pathlib.Path对象 you can simply .write_bytes(...) or .write_text(...) to these objects. 你可以简单地将.write_bytes(...).write_text(...)到这些对象中。

other that that you'd have to check the type of your file variable (this is how polymorphism can be done in python): 另外,你必须检查你的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(...)

(i hope io.IOBase is the most basic class to check against...). (我希望io.IOBase是最基本的类来检查...)。 and you would have to catch possible exceptions around all that. 你必须抓住所有这些可能的例外。

Another approach to this problem, inspired by this talk from Raymond Hettinger at PyCon 2013 , would be to keep the two functions separate as suggested by a couple of the other answers, but to bring the functions together into a class with a number of alternative options for outputting the object. 另一个解决这个问题的方法,受到Raymond Hettinger在2013年PyCon上的讲话的启发,将是将两个函数分开,如其他几个答案所建议的那样,但是将这些函数组合成一个具有多个备选选项的类。用于输出对象。

Continuing with the example I started with, it might look something like this: 继续我开始的例子,它可能看起来像这样:

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)

Now we have lots of different options for exporting a MySpiffy object by using a SpiffyFile object. 现在,我们有很多的对于出口而言,不同的选项MySpiffy使用对象SpiffyFile对象。 We can just write it to a file directly: 我们可以直接将它写入文件:

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

We can override the path, too: 我们也可以覆盖路径:

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

But we can also use an existing open stream: 但我们也可以使用现有的开放流:

SpiffyFile(spiff).to_stream(my_stream)

Or, if we want to edit the string first we could open a new file stream ourselves and write the edited string to it: 或者,如果我们想首先编辑字符串,我们可以自己打开一个新的文件流并将编辑后的字符串写入其中:

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)

And finally, we can just use a context manager with the SpiffyFile object directly to as many different locations- or streams- as we like (note that we can pass the pathlib.Path object directly without worrying about string conversion, which is nifty): 最后,我们可以将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)

This approach is more consistent with the mantra: explicit is better than implicit. 这种方法与咒语更为一致:显性优于隐式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM