Я должен открыть какой-нибудь текстовый файл и прочитать его построчно и вернуть только строку, содержащую числа.

Является ли хорошей идеей использовать выражение with в _iter__? Подобно:

def __iter__(self):
    with open(file_name) as fp:
        for i in fp:
            if is_number(i):
                yield i

Или лучше:

def __enter__(self):
    self._fp = open(self._file, 'r')
    return self

def __exit__(self, exc_type, exc_val, exc_tb):
    self._fp.close()

def __iter__(self) -> int:
    for tracker_id in self._fp:
        if re.search('\d', tracker_id):
            yield int(tracker_id)
0
Mike 7 Июл 2019 в 01:49

3 ответа

Лучший ответ

Я думаю, что вторая форма кода лучше.

Первая версия зависит от итератора, возвращаемого __iter__, существующего только в течение итерации. Если что-то случится, чтобы выйти из итерации без освобождения итератора, тогда файл можно оставить открытым на неопределенное время.

Такое использование в большинстве случаев безопасно, поскольку объект и его итератор будут собираться мусором, если в теле цикла происходит исключение, поскольку нет никаких ссылок на итератор, кроме того, который содержится в самом цикле for ( хотя, если сборка мусора отключена, это может быть небезопасно для интерпретаторов, отличных от CPython):

for x in Whatever():  # assuming your methods are in a class named Whatever
    # do stuff

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

it = iter(Whatever())

for x in it:
    # do stuff

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

with Whatever() as w:
    for x in w:
        # do stuff

Основным недостатком второй версии кода является то, что вы не можете выполнять итерации одного и того же объекта более одного раза в одно и то же время, поскольку они совместно используют один и тот же файловый объект. Если кто-то хочет выполнить итерацию дважды по одному и тому же файлу, ему нужно создать несколько экземпляров класса.

Одноразовый характер объекта может быть более естественным, если он сам является итератором, а не просто повторяемым (например, так работают файловые объекты):

class Whatever:
    def __enter__(self):
        self._fp = open(self._file, 'r')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._fp.close()

    def __iter__(self):
        return self

    def __next__(self)
        tracker_id = next(self._fp)
        while re.search('\d', tracker_id) is None:
            tracker_id = next(self._fp)
        return int(tracker_id)

Обратите внимание, что мы намеренно не пытаемся перехватить какое-либо исключение StopIteration, которое может быть вызвано вызовом next в нашем файле, поскольку это будет нашим сигналом того, что мы также сделали.

1
Blckknght 7 Июл 2019 в 07:17

Вам нужен генератор, а не менеджер контекста. Чтобы создать его, вы можете попробовать что-то вроде этого:

import re

def filter_lines(filename: str, pattern: str):
    p = re.compile(pattern)
    with open(filename) as f:
        for line in f:
            if re.search(p, line):
                yield line

if __name__ == "__main__":
    for line in filter_lines('myfile.txt', '\d'):
        print(line)

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

2
abdusco 6 Июл 2019 в 23:07

В первом случае файл открывается при запросе итерации. Это может привести к дополнительному вводу / выводу, если выполняется несколько итераций. Во втором случае файл всегда открывается, когда объект используется в операторе with, даже если итерация не выполняется.

Есть компромиссы - один подход может быть более эффективным в зависимости от того, как используется объект. Если вам нужно поддерживать различные шаблоны использования, вы можете комбинировать подходы. Лениво откройте файл при первом запросе итерации, а затем закройте его в __exit__. Если вам не нужна такая гибкость, выберите вариант, который лучше всего подходит для вероятного использования объекта.

0
IronMan 6 Июл 2019 в 22:59