#python #yaml #eval #pyyaml
Вопрос:
У меня есть приведенный ниже файл YAML, содержащий логику синтаксического анализа, соответствующую имени файла:
rules_container = """
file_name:
rule_1: {module: str, method: startswith, args: [s_]}
rule_2: {module: str, method: endswith, args: [.log]}
logic: rule_1 and rule_2
"""
Я загружаю его и получаю приведенное ниже отображение:
>>> import yaml
>>> import pprint
>>> d_yml = yaml.safe_load(rules_container)
>>> pprint.pprint(d_yml)
{'file_name': {'logic': 'rule_1 and rule_2',
'rule_1': {'args': ['s_'],
'method': 'startswith',
'module': 'str'},
'rule_2': {'args': ['.log'],
'method': 'endswith',
'module': 'str'}}}
Приведенные ниже функции помогают мне проанализировать вышеуказанные правила, и в конечном итоге я получаю:
- словарь составленных правил
- логика применения правил к строке (т. е. имени файла)
def rule_compiler(module, method, args=None, kwargs=None):
def str_parser(string):
return getattr(string, method)(*args)
return str_parser
def rule_parser(container):
compiled_rules = {
k:rule_compiler(**v) for k,v in container.items() if k.startswith('rule')
}
logic = container.get('logic', None)
return compiled_rules, logic
ПРИМЕЧАНИЕ: Я не использую module
и kwargs
здесь, но они используются в другом месте файла конфигурации для других правил.
Итак, если я обращусь rule_parser()
к контейнеру правил, я получу это:
>>> rp = rule_parser(d_yml['file_name'])
>>> print(rp)
({'rule_1': <function __main__.rule_compiler.<locals>.str_parser(string)>,
'rule_2': <function __main__.rule_compiler.<locals>.str_parser(string)>},
'rule_1 and rule_2')
Если я попытаюсь проанализировать строку с каждым отдельным правилом, они будут работать так, как ожидалось:
>>> fname = 's_test.out'
>>> rp[0]['rule_1'](fname)
True
>>> rp[0]['rule_2'](fname)
False
Я хочу применить логику, определенную «логикой», и получить это:
>>> rp[0]['rule_1'](fname) and rp[0]['rule_2'](fname)
False
Я уже придумал, как это сделать, проанализировав строку, содержащую логическую логику, преобразовав ее во что-то похожее на описанное выше, а затем вызовите eval()
ее.
Можете ли вы придумать какой-нибудь другой способ, не связанный eval()
с этим ?
ПРИМЕЧАНИЕ: может быть более двух правил, поэтому замена логики простым «и/или», а затем использование какого if
-либо оператора для определения того, какое из них применять, также не сработает.
ПРИМЕЧАНИЕ: использование регулярных выражений и полное устранение необходимости в нескольких правилах в данном конкретном случае не является вариантом.
Спасибо!
Комментарии:
1. Возможно, вам повезет с
ast.literal_eval(...)
(не проверено).2. Правильный способ в долгосрочной перспективе-создать синтаксический анализатор, который читает интересующий вас язык, генерирует AST, а затем создает реализации узлов AST. Возьмите курс разработки компилятора университетского уровня, и к его концу вы будете готовы. Однако я не уверен, что это подходящая область для вопроса о переполнении стека. Любой приличный генератор синтаксического анализатора позволит вам настроить соответствующие правила приоритета и т. Д.
3. @Jan, это, конечно, не буквально. Я бы не ожидал
literal_eval
, что это сработает.4. @Jan я получаю
ValueError: malformed node or string
«сast.literal_eval()
«. Тем не менее, спасибо за ваш вклад.
Ответ №1:
Вот идея, используя pandas.eval
, не уверен, насколько она подходит вашему случаю.
import pandas as pd
fname = 's_test.out'
rule, logic = rp
def apply_rule(rule_dict: dict, fname: str) -> dict:
return {rule: func(fname)
for rule, func in rule_dict.items()}
print(pd.eval(logic, local_dict = apply_rule(rule, fname)))
Выход:
False
Комментарии:
1. Кажется, это хорошо работает. Мне не нужно анализировать/изменять строку, предоставленную с помощью
logic
ключа, и беспокоиться о создании допустимого выражения для выполнения с помощьюeval()
. Более того, если я попытаюсь передать «опасную» строку черезlogic
(например"__import__('os').listdir('.')"
) Я получаю исключениеValueError: "__import__" is not a supported function
. Так что, кроме того факта , что мне приходится импортироватьpandas
, я не вижу никаких недостатков.2. Я не смог заставить его работать с помощью
boolean.py
библиотеки, и я не собираюсь создавать синтаксический анализатор. Я понимаю, что при использовании все еще существуют некоторые рискиpd.eval()
. Хотя на данный момент это выглядит более безопасным — и более универсальным — чемeval()
. Поэтому я собираюсь выбрать это в качестве решения своей проблемы.
Ответ №2:
Правильный ответ может быть где угодно, начиная с использования библиотеки, такой как boolean.py для анализа ваших строк (менее гибкий, вероятно, самый простой выбор, кроме eval
) для чтения лексеров и синтаксических анализаторов и реализации одного из них с нуля или с помощью программного обеспечения для создания синтаксического анализатора (наиболее гибкого, но сложного и трудоемкого). В этом случае я бы, вероятно, рекомендовал начать с библиотеки, специально созданной для вычисления логических выражений, и, если это не сработает, попробуйте что-нибудь более сложное.
Правка: Я должен отметить, что вы могли бы использовать eval
, если у пользователей нет точек входа для внедрения вредоносного кода, вы правильно очистили строку оценки и т. Д. — Вероятно, это не стоит вашего времени, но стоит подумать, если другие варианты не соответствуют вашим потребностям.
Комментарии:
1. Мне нравится идея использовать boolean.py . Я пытался заставить это работать, но я всегда оказываюсь в ситуации, когда мне приходится динамически создавать переменные на основе имен моих правил. Итак, я вернулся к исходной точке, только с другой плохой практикой .
2. Не могли бы вы использовать словарь для динамического задания имен «переменных» или заранее создать несколько переменных и установить для них нужные значения?
3. Мое понимание использования этой библиотеки заключается в том , что мне нужно добраться до точки, где у меня есть что-то вроде
rule_1, rule_2 = algebra.symbols(True, False)
, гдеTrue
иFalse
являются результатамиstartswith()
иendswith()
методами соответственно. Чтобы попасть туда, мне нужно сделать имена правил, которые могут изменяться, частью пространства имен функции. Итак, я создаю переменные динамически. В противном случае строка, содержащаяся вlogic
, больше не будет работать как есть.4. И даже если бы я смог добраться до сути, если бы имена правил стали частью локального пространства имен, я все равно не смог бы получить ожидаемый результат от этой библиотеки. На самом деле, если я это делаю
algebra.parse(logic) == [True|False|TRUE|FALSE]
, я всегда получаюFALSE
.