Есть ли лучший способ создавать динамические функции на лету, без использования форматирования строк и exec? - PullRequest
2 голосов
/ 12 марта 2012

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

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

Я упростил приведенный ниже пример, но идея та же.

Моя оригинальная функция выглядела такон вызывается от 100 до 10000 раз за прогон, поэтому вы можете понять, почему я хочу его оптимизировать:

def parse_txt(f):
    d = {}
    for line in f:
        if not line:
            pass

        elif 'apples' in line:
            d['apples'] = True

        elif 'bananas' in line:
            d['bananas'] = True

        elif line.startswith('End of section'):
            return d


f = open('fruit.txt','r')
d = parse_txt(f)
print d 

Проблема, с которой я сталкиваюсь, состоит в том, что в моей программе много условий,потому что он проверяет много разных вещей и хранит значения для него.И при проверке каждой строки на наличие от 0 до 30 ключевых слов, это быстро замедляется.Я не хочу этого делать, потому что не каждый раз, когда я запускаю программу, меня интересует все.Меня интересуют только 5-6 ключевых слов, но я разбираю каждую строку для примерно 30 ключевых слов.

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

def make_func(args):
    func_str = """
def parse_txt(f):
    d = {}
    for line in f:
        if not line:
            pass
    """

    if 'apples' in args:
        func_str += """ 
        elif 'apples' in line:
            d['apples'] = True
"""

    if 'bananas' in args:
        func_str += """ 
        elif 'bananas' in line:
            d['bananas'] = True
"""

    func_str += """ 
        elif line.startswith('End of section'):
            return d"""

    print func_str
    exec(func_str)
    return parse_txt

args = ['apples','bananas']
fun = make_func(args)
f = open('fruit.txt','r')
d = fun(f)
print d

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

Например, если я дам ему args=['bananas'], он не проверит на 'apples', и это именно то, что я хочу сделать.

Это сделает его намного более эффективным.

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

Я ищу альтернативные или лучшие способы сделать это.Я попытался использовать набор функций для вызова каждой строки, и хотя это сработало, оно не дало мне увеличения скорости, которое дает мне мое текущее решение, потому что оно добавляет несколько вызовов функций для каждой строки.Мое текущее решение не имеет этой проблемы, потому что его нужно вызывать только один раз в начале программы.Я читал о проблемах безопасности с exec и eval, но меня это не особо волнует, потому что я единственный, кто его использует.

РЕДАКТИРОВАТЬ: я должен добавить это, для ясности,Я значительно упростил свою функцию.Из ответов я понимаю, что не достаточно ясно это понял.Я не проверяю ключевые слова в согласованном порядке.Иногда мне нужно проверить 2 или 3 ключевых слова в одной строке, иногда только 1. Я также не отношусь к результату одинаково.Например, иногда я извлекаю одно значение из строки, в которой я нахожусь, иногда мне нужно проанализировать следующие 5 строк.

Ответы [ 4 ]

3 голосов
/ 12 марта 2012

Я бы попытался определить список ключевых слов, которые вы хотите найти («ключевые слова») и сделать это:

for word in keywords:
    if word in line:
        d[word] = True

Или, используя понимание списка:

dict([(word,True) for word in keywords if word in line])

Если я не ошибаюсь, это не должно быть намного медленнее, чем ваша версия.

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

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

def keyword_handler_word1(line):
    (...)

(...)

def keyword_handler_wordN(line):
    (...)

keyword_handlers = { 'word1': keyword_handler_word1, (...), 'wordN': keyword_handler_wordN }

Затем в фактическом коде обработки:

for word in keywords:
    # keyword_handlers[word] is a function
    keyword_handlers[word](line)
2 голосов
/ 12 марта 2012

Используйте регулярные выражения.Примерно так:

>>> lookup = {'a': 'apple', 'b': 'banane'} # keyword: characters to look for
>>> pattern = '|'.join('(?P<%s>%s)' % (key, val) for key, val in lookup.items())
>>> re.search(pattern, 'apple aaa').groupdict()
{'a': 'apple', 'b': None}
1 голос
/ 12 марта 2012
def create_parser(fruits):
    def parse_txt(f):
        d = {}
        for line in f:
            if not line:
                pass
            elif line.startswith('End of section'):
                return d
            else:
                for testfruit in fruits:
                    if testfruit in line:
                        d[testfruit] = True

Это то, что вы хотите - динамически создавать тестовую функцию.

В зависимости от того, что вы действительно хотите сделать, можно, конечно, удалить один уровень сложности и определить

def parse_txt(f, fruits):
    [...]

или

def parse_txt(fruits, f):
    [...]

и работа с functools.partial.

0 голосов
/ 12 марта 2012

Вы можете использовать заданную структуру, например:

fruit = set(['cocos', 'apple', 'lime'])
need = set (['cocos', 'pineapple'])
need. intersection(fruit)

верните вам «кокосы».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...