Функция Python, которая принимает файл-объект или путь

Я хочу написать функцию, которая принимает путь как строку или файл. До сих пор я:

def awesome_parse(path_or_file):
    if isinstance(path_or_file, basestring):
        f = open(path_or_file, 'rb')
    else:
        f = path_or_file
    with f as f:
        return do_stuff(f)

где do_stuff принимает открытый файл.

Есть ли лучший способ сделать это? Имеет ли with f as f: какие-либо последствия?

Спасибо!

Ответ 1

Нечетная вещь о вашем коде состоит в том, что если он передается открытым файлом, он закроет его. Это плохо. Независимо от того, какой код открыт, файл должен нести ответственность за его закрытие. Это делает функцию немного более сложной, хотя:

def awesome_parse(path_or_file):
    if isinstance(path_or_file, basestring):
        f = file_to_close = open(path_or_file, 'rb')
    else:
        f = path_or_file
        file_to_close = None
    try:
        return do_stuff(f)
    finally:
        if file_to_close:
            file_to_close.close()

Вы можете абстрагировать это, написав свой собственный менеджер контекстов:

@contextlib.contextmanager
def awesome_open(path_or_file):
    if isinstance(path_or_file, basestring):
        f = file_to_close = open(path_or_file, 'rb')
    else:
        f = path_or_file
        file_to_close = None

    try:
        yield f
    finally:
        if file_to_close:
            file_to_close.close()

def awesome_parse(path_or_file):
    with awesome_open(path_or_file) as f:
        return do_stuff(f)

Ответ 2

Вы можете сделать:

def awesome_parse(do_stuff):
    """Decorator to open a filename passed to a function
       that requires an open file object"""
    def parse_path_or_file(path_or_file):
        """Use a ternary expression to either open the file from the filename
           or just pass the extant file object on through"""
        with (open(path_or_file, 'rb') 
               if isinstance(path_or_file, basestring) 
                else path_or_file) as f:
            return do_stuff(f)
    return parse_path_or_file

И затем, когда вы объявляете какую-либо функцию, которая делает вещи в открытом объекте файла:

@awesome_parse
def do_things(open_file_object):
    """This will always get an open file object even if passed a string"""
    pass

@awesome_parse
def do_stuff(open_file_object):
    """So will this"""
    pass

Изменить 2: Более подробная информация о декораторе.