Перевод аргументов в подходящий тип данных
Есть строка :
args = " abc 556 [d, [e, 1.14], True], 'two words' {dict:test}"
Как её можно разложить по типу данных, чтобы в итоге получилось это :
args = ["abc", 556, ["d", ["e", 1.14], True], "two words", {"dict":"test"}]
Сам пытался сделать через большое количество if/elif/else но к успеху не пришел.
Подскажите пожалуйста, как можно это адекватно реализовать.
Ответы (1 шт):
Можно сделать так:
Объяснение на примере строки
args = " abc 556 [d, [e, 1.14], True], 'two words' {dict:test}"
Исходя из примера, предполагается, что запятые могут быть пропущены только после строк и чисел, после скобок ])} они стоят, там где нужны.
Сначала строку
argsнужно "починить" - превратить содержимое в валидный код на Python. Для этого требуется обернуть строковые типы в кавычки и добавить недостающие запятые. Обычные поиск и замена здесь не подойдут, так как они затрагивают уже существующие строковые типы, напримерre.sub(',? ', ',', args)заменит пробел внутри
'two words'-'two,words'. Следовательно, нужно отслеживать, чтобы замена не происходила внутри кавычек.Я решил использовать модуль
tokenize, которым можно разбить строку на неделимые с точки зрения интерпретатора Python атомы (токены) с указанием их типа.# В первом столбце тип токена # Во втором значение токена 1 abc # 1 = name. Без кавычек срока распознаётся как имя переменной. 2 556 # 2 = number 53 [ # 53 = operator 1 d 53 , 53 [ 1 e 53 , 2 1.14 53 ] 53 , 1 True 53 ] 53 , 3 'two words' # 3 = string 53 { 1 dict 53 : 1 test 53 }Проставляем запятые. Для добавления запятой к токену, требуется выполнение следующих условий:
- текущий токен имеет тип: число, строка, "имя переменной".
- следующий токен не
:,}]).
Оборачиваем в кавычки все токены типа "имя переменной" (кроме True, False) и меняем их тип на
string, таким образом преобразуя их в строки. В итоге строка выглядит так:'abc', 556, ['d', ['e', 1.14], True], 'two words', {'dict':'test'}Заключаем отремонтированную строку в квадратные скобки, чтобы на выходе получился список, и передаём методу
ast.literal_eval.ast.literal_evalпарсит эту строку, распознаёт типы данных и создаёт соответствующие объекты. В данном случаеast.literal_evalпредпочтительнееeval(), так как имеет ограниченные возможности, что уменьшает риск исполнения вредоносного кода.
Решение
import ast
import tokenize
import io
def fixing_args(args):
args = args.strip()
args = io.StringIO(args).readline
list_of_tokens = list(tokenize.generate_tokens(args))
new_list_of_tokens = []
need_sep_toks_types = {1, 2, 3}
no_need_sep_toks = set(":,}])")
for token, next_token in zip(list_of_tokens, list_of_tokens[1:]):
separator = ',' if token.type in need_sep_toks_types and next_token.string not in no_need_sep_toks else ''
tok_type, tok_string, *tok_others = token
if token.type == 1 and token.string not in {"True", "False"}:
tok_type = 3
tok_string = f"'{token.string}'{separator}"
else:
tok_string = f"{token.string}{separator}"
new_token = tokenize.TokenInfo(tok_type, tok_string, *tok_others)
new_list_of_tokens.append(new_token)
return tokenize.untokenize(new_list_of_tokens)
args = " abc 556 [d, [e, 1.14], True], 'two words' {dict:test}"
fixed_arg_string = fixing_args(args)
fixed_arg_string = f"[{fixed_arg_string}]"
lst_of_args = ast.literal_eval(fixed_arg_string)
for arg in lst_of_args:
print(arg)
Output
abc
556
['d', ['e', 1.14], True]
two words
{'dict': 'test'}