Подтвердить что ты не робот

Преобразуйте или неформатируйте строку в переменные (например, 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() в обратном порядке) был бы замечательным! Ответ на мой конкретный случай тоже будет очень полезен.

4b9b3361

Ответ 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.