Чтение и закрытие большого количества файлов в новом классе приводит к ошибке: слишком много открытых файлов

#python #python-3.4

#python #python-3.4

Вопрос:

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

Вот пример кода:

 import os
import stringsfilter


def apply_filter(path, filter_dict):
    dirlist = os.listdir(path)
    for directory in dirlist:
        pwd = path   '/'   directory
        filelist = os.listdir(pwd)
        for filename in filelist:
            if filename.split('.')[-1] == "stats":
                sfilter = stringsfilter.StringsFilter(pwd, filename, filter_dict["strings"])
                sfilter.find_strings_and_move()
  

и вот stringsfilter.py:

 import main
import codecs
import os
import shutil


class StringsFilter:

    strings = None

    def __init__(self, filepath, filename, strings):
        self.filepath = filepath
        self.filename = filename
        self.strings = strings
        self.logger = main.get_module_logger("StringsFilter")
        self.file_desc = codecs.open(self.filepath   '/'   self.filename, 'r', encoding="utf-8-sig")
        self.logger.debug("[-] Strings: "   str(self.strings))
        self.logger.debug("[-] Instantiating class Strings Filter, filename: %s " % self.filename)

    def find_strings_and_move(self):
        for line in self.file_desc.readlines():
            for string in self.strings:
                if string in line:
                    self.move_to_folder()
                    return
        self.close()

    def move_to_folder(self):
        name = self.filename.split('.')[0]
        os.mkdir(self.filepath   '/'   name)
        shutil.copyfile(self.filepath   '/'   self.filename,
                        self.filepath   '/'   name   '/'   self.filename)
        self.close()

    def close(self):
        if self.file_desc:
            self.logger.debug("[-] Closing file %s" % self.filename)
            self.file_desc.close()
  

main.py:

 import logging

def get_module_logger(name):
    # create logger
    logger = logging.getLogger(name)

    # set logging level to log everything
    logger.setLevel(logging.DEBUG)

    # create file handler which logs everything
    fh = logging.FileHandler('files.log')
    fh.setLevel(logging.DEBUG)

    # create console handler
    ch = logging.StreamHandler()
    ch.setLevel(logging.INFO)

    # create formatter and add it to the handlersi
    formatter = logging.Formatter('[%(asctime)s] [%(name)-17s] [%(levelname)-5s] - %(message)s')
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)

    # add the handlers to the logger
    logger.addHandler(fh)
    logger.addHandler(ch)
    return logger
  

в журнале я вижу следующее:

 [2016-10-13 10:07:07,002] [StringsFilter    ] [DEBUG] - [-] Strings: ['DEVICE_PROBLEM']
[2016-10-13 10:07:07,002] [StringsFilter    ] [DEBUG] - [-] Instantiating class Strings Filter, filename: file1.stats 
[2016-10-13 10:07:07,003] [StringsFilter    ] [DEBUG] - [-] Closing file file1.stats
[2016-10-13 10:07:07,003] [StringsFilter    ] [DEBUG] - [-] Strings: ['DEVICE_PROBLEM']
[2016-10-13 10:07:07,003] [StringsFilter    ] [DEBUG] - [-] Strings: ['DEVICE_PROBLEM']
[2016-10-13 10:07:07,004] [StringsFilter    ] [DEBUG] - [-] Instantiating class Strings Filter, filename: file2.stats 
[2016-10-13 10:07:07,004] [StringsFilter    ] [DEBUG] - [-] Instantiating class Strings Filter, filename: file2.stats 
[2016-10-13 10:07:07,004] [StringsFilter    ] [DEBUG] - [-] Closing file file2.stats
[2016-10-13 10:07:07,004] [StringsFilter    ] [DEBUG] - [-] Closing file file2.stats
[2016-10-13 10:07:07,005] [StringsFilter    ] [DEBUG] - [-] Strings: ['DEVICE_PROBLEM']
[2016-10-13 10:07:07,005] [StringsFilter    ] [DEBUG] - [-] Strings: ['DEVICE_PROBLEM']
[2016-10-13 10:07:07,005] [StringsFilter    ] [DEBUG] - [-] Strings: ['DEVICE_PROBLEM']
[2016-10-13 10:07:07,005] [StringsFilter    ] [DEBUG] - [-] Instantiating class Strings Filter, filename: file3.stats 
[2016-10-13 10:07:07,005] [StringsFilter    ] [DEBUG] - [-] Instantiating class Strings Filter, filename: file3.stats 
[2016-10-13 10:07:07,005] [StringsFilter    ] [DEBUG] - [-] Instantiating class Strings Filter, filename: file3.stats 
[2016-10-13 10:07:07,006] [StringsFilter    ] [DEBUG] - [-] Closing file file3.stats
[2016-10-13 10:07:07,006] [StringsFilter    ] [DEBUG] - [-] Closing file file3.stats
[2016-10-13 10:07:07,006] [StringsFilter    ] [DEBUG] - [-] Closing file file3.stats
  

И это продолжается, кажется, что с каждой итерацией каждый оператор из init выполняется еще раз, пока не будет открыто слишком много файлов и программа не завершится

 OSError: [Errno 24] Too many files open
  

Я не могу понять, почему инструкции из init вызываются несколько раз при каждом создании экземпляра.

Комментарии:

1. Получение ошибки 'main' is not defined в строке 9 stringsfilter.py .

2. Извините, забыл о main.py . Это добавлено сейчас.

Ответ №1:

Причина, по которой одно и то же регистрируется несколько раз: при каждом main.get_module_logger("StringsFilter") вызове вы вызываете logger.addHandler(...) один и тот же регистратор, возвращаемый из logging.getLogger(name) , поэтому вы получаете несколько обработчиков в одном регистраторе. Лучше создать регистратор на уровне модуля

 import ...
LOG = main.get_module_logger("StringsFilter")
class StringsFilter:...
  

Что касается открытых файлов, я не вижу причины, но рассмотрите возможность использования with open(filename) as f: синтаксиса в find_strings_and_move() .

 LOG = main.get_module_logger("StringsFilter")
class StringsFilter:

    strings = None

    def __init__(self, filepath, filename, strings):
        self.filepath = filepath
        self.filename = filename
        self.strings = strings
        LOG.debug("[-] Strings: "   str(self.strings))
        LOG.debug("[-] Instantiating class Strings Filter, filename: %s " % self.filename)

    def find_strings_and_move(self):
        with open(self.filepath   '/'   self.filename, 'r') as file_desc:
            lines = file_desc.readlines()
        for line in lines:
            for string in self.strings:
                if string in line:
                    self.move_to_folder()
                    return

    def move_to_folder(self):
        name = self.filename.split('.')[0]
        os.mkdir(self.filepath   '/'   name)
        shutil.copyfile(self.filepath   '/'   self.filename,
                        self.filepath   '/'   name   '/'   self.filename)
  

Таким образом, вы убедитесь, что файл закрыт 1) перед перемещением 2) всегда

Комментарии:

1. Спасибо. Ваше решение работает нормально. Я не понимал, что дублирую ведение журнала в классе StringsFilter. Кроме того, я, вероятно, неправильно закрыл файлы, так как теперь использование инструкции with не приводит к ошибке: открыто слишком много файлов.

2. @Libor Я рад, что это сработало 🙂 Пожалуйста, подумайте о том, чтобы принять / поддержать ответ.