Преобразуйте или неформатируйте строку в переменные (например, format(), но наоборот) в Python
У меня есть строки формы Version 1.4.0\n
и Version 1.15.6\n
, и мне нужен простой способ извлечения трех чисел из них. Я знаю, что могу поместить переменные в строку с помощью метода format; Я в основном хочу сделать это назад, например:
# So I know I can do this:
x, y, z = 1, 4, 0
print 'Version {0}.{1}.{2}\n'.format(x,y,z)
# Output is 'Version 1.4.0\n'
# But I'd like to be able to reverse it:
mystr='Version 1.15.6\n'
a, b, c = mystr.unformat('Version {0}.{1}.{2}\n')
# And have the result that a, b, c = 1, 15, 6
Кто-то, кого я нашел, задал тот же вопрос, но ответ был специфичен для их конкретного случая: Использовать строку формата Python в обратном порядке для синтаксического анализа
Общий ответ (как сделать format()
в обратном порядке) был бы замечательным! Ответ на мой конкретный случай тоже будет очень полезен.
Ответы
Ответ 1
На самом деле библиотека регулярных выражений Python уже предоставляет общую функциональность, о которой вы просите. Вам просто нужно немного изменить синтаксис шаблона
>>> import re
>>> from operator import itemgetter
>>> mystr='Version 1.15.6\n'
>>> m = re.match('Version (?P<_0>.+)\.(?P<_1>.+)\.(?P<_2>.+)', mystr)
>>> map(itemgetter(1), sorted(m.groupdict().items()))
['1', '15', '6']
Как вы можете видеть, вам нужно изменить строки формата (un) от {0} до (? P < _0 > . +). Вы даже можете потребовать десятичное число с (? P < _0 > \d +). Кроме того, вам нужно избежать некоторых символов, чтобы они не интерпретировались как специальные символы регулярных выражений. Но это в турме может быть снова автоматизировано, например. с
>>> re.sub(r'\\{(\d+)\\}', r'(?P<_\1>.+)', re.escape('Version {0}.{1}.{2}'))
'Version\\ (?P<_0>.+)\\.(?P<_1>.+)\\.(?P<_2>.+)'
Ответ 2
>>> import re
>>> re.findall('(\d+)\.(\d+)\.(\d+)', 'Version 1.15.6\n')
[('1', '15', '6')]
Ответ 3
Просто, чтобы опираться на Uche answer, я искал способ перевернуть строку с помощью шаблона с помощью kwargs. Поэтому я собрал следующую функцию:
def string_to_dict(string, pattern):
regex = re.sub(r'{(.+?)}', r'(?P<_\1>.+)', pattern)
values = list(re.search(regex, string).groups())
keys = re.findall(r'{(.+?)}', pattern)
_dict = dict(zip(keys, values))
return _dict
Что работает:
>>> p = 'hello, my name is {name} and I am a {age} year old {what}'
>>> s = p.format(name='dan', age=33, what='developer')
>>> s
'hello, my name is dan and I am a 33 year old developer'
>>> string_to_dict(s, p)
{'age': '33', 'name': 'dan', 'what': 'developer'}
>>> s = p.format(name='cody', age=18, what='quarterback')
>>> s
'hello, my name is cody and I am a 18 year old quarterback'
>>> string_to_dict(s, p)
{'age': '18', 'name': 'cody', 'what': 'quarterback'}
Ответ 4
Это
a, b, c = (int(i) for i in mystr.split()[1].split('.'))
даст вам int
значения для a
, b
и c
>>> a
1
>>> b
15
>>> c
6
В зависимости от того, насколько регулярными или нерегулярными, то есть последовательными, ваши форматы номера/версии будут, вы можете рассмотреть использование регулярных выражений, хотя, если они останутся в этом формате, я бы предпочел более простое решение, если оно работает для вас.
Ответ 5
Некоторое время назад я сделал код ниже, который делает обратный формат, но ограничивается случаями, которые мне нужны.
И, я никогда не пробовал, но я думаю, что это также цель parse library
Мой код:
import string
import re
_def_re = '.+'
_int_re = '[0-9]+'
_float_re = '[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?'
_spec_char = '[\^$.|?*+()'
def format_parse(text, pattern):
"""
Scan `text` using the string.format-type `pattern`
If `text` is not a string but iterable return a list of parsed elements
All format-like pattern cannot be process:
- variable name cannot repeat (even unspecified ones s.t. '{}_{0}')
- alignment is not taken into account
- only the following variable types are recognized:
'd' look for and returns an integer
'f' look for and returns a float
Examples::
res = format_parse('the depth is -42.13', 'the {name} is {value:f}')
print res
print type(res['value'])
# {'name': 'depth', 'value': -42.13}
# <type 'float'>
print 'the {name} is {value:f}'.format(**res)
# 'the depth is -42.130000'
# Ex2: without given variable name and and invalid item (2nd)
versions = ['Version 1.4.0', 'Version 3,1,6', 'Version 0.1.0']
v = format_parse(versions, 'Version {:d}.{:d}.{:d}')
# v=[{0: 1, 1: 4, 2: 0}, None, {0: 0, 1: 1, 2: 0}]
"""
# convert pattern to suitable regular expression & variable name
v_int = 0 # available integer variable name for unnamed variable
cur_g = 0 # indices of current regexp group name
n_map = {} # map variable name (keys) to regexp group name (values)
v_cvt = {} # (optional) type conversion function attached to variable name
rpattern = '^' # stores to regexp pattern related to format pattern
for txt,vname, spec, conv in string.Formatter().parse(pattern):
# process variable name
if len(vname)==0:
vname = v_int
v_int += 1
if vname not in n_map:
gname = '_'+str(cur_g)
n_map[vname] = gname
cur_g += 1
else:
gname = n_map[vname]
# process type of required variables
if 'd' in spec: vtype = _int_re; v_cvt[vname] = int
elif 'f' in spec: vtype = _float_re; v_cvt[vname] = float
else: vtype = _def_re;
# check for regexp special characters in txt (add '\' before)
txt = ''.join(map(lambda c: '\\'+c if c in _spec_char else c, txt))
rpattern += txt + '(?P<'+gname+'>' + vtype +')'
rpattern += '$'
# replace dictionary key from regexp group-name to the variable-name
def map_result(match):
if match is None: return None
match = match.groupdict()
match = dict((vname, match[gname]) for vname,gname in n_map.iteritems())
for vname, value in match.iteritems():
if vname in v_cvt:
match[vname] = v_cvt[vname](value)
return match
# parse pattern
if isinstance(text,basestring):
match = re.search(rpattern, text)
match = map_result(match)
else:
comp = re.compile(rpattern)
match = map(comp.search, text)
match = map(map_result, match)
return match
для вашего случая, вот пример использования:
versions = ['Version 1.4.0', 'Version 3.1.6', 'Version 0.1.0']
v = format_parse(versions, 'Version {:d}.{:d}.{:d}')
# v=[{0: 1, 1: 4, 2: 0}, {0: 3, 1: 1, 2: 6}, {0: 0, 1: 1, 2: 0}]
# to get the versions as a list of integer list, you can use:
v = [[vi[i] for i in range(3)] for vi in filter(None,v)]
Обратите внимание на filter(None,v)
, чтобы удалить unparsable версии (которые возвращают None). Здесь это не обязательно.
Ответ 6
EDIT: см. этот ответ для получения более подробной информации о parse
и parmatter
.
Пакет pypi parse
хорошо подходит для этой цели:
pip install parse
Может использоваться следующим образом:
>>> import parse
>>> result=parse.parse('Version {0}.{1}.{2}\n', 'Version 1.15.6\n')
<Result ('1', '15', '6') {}>
>>> values=list(result)
>>> print(values)
['1', '15', '6']
Обратите внимание, что docs говорят пакет parse
не ТОЧНО эмулирует формат мини-языка по умолчанию; он также использует некоторые индикаторы типов, указанные re
. Особо следует отметить, что s
означает "пробел" по умолчанию, а не str
. Это можно легко изменить, чтобы соответствовать спецификации формата, изменив тип по умолчанию для s
на str
(используя extra_types
):
result = parse.parse(format_str, string, extra_types=dict(s=str))
Вот концептуальная идея для модификации встроенного класса string.Formatter
с использованием пакета parse
для добавления возможности unformat
, которую я использовал сам:
import parse
from string import Formatter
class Unformatter(Formatter):
'''A parsable formatter.'''
def unformat(self, format, string, extra_types=dict(s=str), evaluate_result=True):
return parse.parse(format, string, extra_types, evaluate_result)
unformat.__doc__ = parse.Parser.parse.__doc__
ВАЖНО: имя метода parse
уже используется классом Formatter
, поэтому я выбрал unformat
, чтобы избежать конфликтов.
UPDATE: вы можете использовать его так же, как это похоже на класс string.Formatter
.
Форматирование (идентичное '{:d} {:d}'.format(1, 2)
):
>>> formatter = Unformatter()
>>> s = formatter.format('{:d} {:d}', 1, 2)
>>> s
'1 2'
Unformatting:
>>> result = formatter.unformat('{:d} {:d}', s)
>>> result
<Result (1, 2) {}>
>>> tuple(result)
(1, 2)
Это, конечно, очень ограниченное использование, как показано выше. Тем не менее, я поставил пакет pypi (parmatter - проект изначально для моего собственного использования, но, возможно, другие найдут его полезным) который исследует некоторые идеи о том, как сделать эту идею более полезной. Пакет в значительной степени зависит от вышеупомянутого пакета parse
.