2012-08-07 11 views
25

Mam ciągi znaków w postaci Version 1.4.0\n i Version 1.15.6\n i chciałbym prosty sposób wyodrębnienia z nich trzech liczb. Wiem, że mogę umieścić zmienne w łańcuchu za pomocą metody formatowania; Zasadniczo chcę, aby to zrobić w tył, tak:Konwertuj lub formatuj ciąg znaków do zmiennych (takich jak format(), ale w odwrotnej kolejności) w Pythonie

# 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 

Ktoś znalazłem to samo pytanie, ale odpowiedź była specyficzna dla ich konkretnym przypadku: Use Python format string in reverse for parsing

ogólnej odpowiedzi (jak to zrobić format() na odwrót) byłoby świetnie! Odpowiedź na moją konkretną sprawę również byłaby bardzo pomocna.

+3

Widzę poniżej kilka odpowiedzi, które bezpośrednio dotyczą Twojego problemu. ale lepszym rozwiązaniem byłoby użycie wyrażeń regularnych imho. –

+0

Wydaje się to przydatne dla [scanf] (http://code.activestate.com/recipes/502213-simple-scanf-implementation/) Styl C w stylu – Gaius

Odpowiedz

0

Właściwie biblioteka wyrażeń regularnych Python zapewnia już ogólną funkcjonalność, o którą prosisz. Po prostu trzeba zmienić składnię wzorca nieznacznie

>>> 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'] 

Jak widać, trzeba zmienić format (ONZ) sznurki od {0} do (? P < _0>. +). Możesz nawet wymagać dziesiętnego z (? P < _0> \ d +). Ponadto musisz uciec niektórym postaciom, aby zapobiec ich interpretacji jako specjalnych znaków regularnych. Ale to w turm można ponownie zautomatyzować, np. z

>>> re.sub(r'\\{(\d+)\\}', r'(?P<_\1>.+)', re.escape('Version {0}.{1}.{2}')) 
'Version\\ (?P<_0>.+)\\.(?P<_1>.+)\\.(?P<_2>.+)' 
3

Ten

a, b, c = (int(i) for i in mystr.split()[1].split('.')) 

daje int wartości a, b i c

>>> a 
1 
>>> b 
15 
>>> c 
6 

zależności od tego jak regularne lub nieregularne, czyli konsekwentny, twoje formaty Numer/wersja będzie, ty może rozważyć użycie wyrażeń regularnych , ale jeśli pozostaną w tym formacie, wolałbym prostsze rozwiązanie, jeśli działa dla ciebie.

+1

+1 wystarczy użyć wyrażenia generatora, bez potrzeby '[]' . –

+1

@AshwiniChaudhary Tak, masz rację. Najpierw dowiedziałem się o zrozumieniu listy, więc to jest to, do czego dążę początkowo, ale masz rację, nie ma potrzeby utrzymywania listy - dzięki, zaktualizowałem odpowiedź. – Levon

+1

Używanie generatora w tym przypadku nie ma sensu, zrozumienie listy sprawi, że zadanie będzie w porządku. – Willian

8
>>> import re 
>>> re.findall('(\d+)\.(\d+)\.(\d+)', 'Version 1.15.6\n') 
[('1', '15', '6')] 
+0

Ups, chodzi mi o 'x, y, z = [int (num) dla wyniku w re.findall ('(\ d +) \. (\ D +) \. (\ D +)', 'Wersja 1.15.6 \ n ') dla num w wyniku] ' –

+0

a, b, c = re.findall (' (\ d +) \. (\ d +) \. (\ d +) ',' Wersja 1.15.6 \ n ') [ 0] – Willian

+0

To miłe udoskonalenie, ale nadal nie konwertuje wyników na liczby całkowite. Zmieniam mój przykład: 'x, y, z = [int (num) dla num w re.findall ('(\ d +) \. (\ D +) \. (\ D +)', 'Wersja 1.15.6 \ n ') [0]] ' –

2

Jakiś czas temu zrobiłem poniższy kod, który robi odwrotność formatu, ale ogranicza się do przypadków, których potrzebowałem.

I, nigdy nie próbowałem, ale myślę, że jest to także cel parse library

mój kod:

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 

Twoim przypadku, tutaj jest przykład użycia:

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)] 

Zauważ, że filter(None,v) usuwa niepodobne wersje (które zwracają Brak). Tutaj nie jest konieczne.

4

Po to, aby zbudować na Uche's answer, szukałem sposobu na odwrócenie łańcucha za pomocą wzorca z kwargs.Tak ułożyła następującą funkcję:

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 

który działa zgodnie z:

>>> 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'} 
2

EDIT: Zobacz także this answer za trochę więcej informacji o parse i parmatter.

Pakiet PyPI parse Temu celowi służy również:

pip install parse 

mogą być używane tak:

>>> 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'] 

Zauważ, że the docs say pakiet parse nie dokładnie naśladować format specification mini-language domyślnie; używa również niektórych wskaźników typu określonych przez re. Na szczególną uwagę zasługuje fakt, że s domyślnie oznacza "spacje", a nie str. To może być łatwo modyfikowane zgodnie ze specyfikacją formatu zmieniając typ domyślny s do str (używając extra_types):

result = parse.parse(format_str, string, extra_types=dict(s=str)) 

Oto koncepcyjny pomysł na modyfikację string.Formatter wbudowanej klasy przy użyciu parse pakiet aby dodać unformat możliwość, że użyłem sobie:

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__ 

WAŻNE: nazwa metody parse jest już używany przez klasę Formatter, więc wybrałem unformat zamiast tego, aby uniknąć konfliktów.

AKTUALIZACJA: Możesz go używać w ten sposób - bardzo podobnie do klasy string.Formatter.

formatowanie (identyczny '{: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) 

Jest to oczywiście bardzo ograniczone zastosowanie, jak pokazano powyżej. Jednakże przygotowałem pakiet pypi (parmatter - projekt pierwotnie na własny użytek, ale być może inni uznają to za użyteczne), który bada kilka pomysłów na to, jak wprowadzić ten pomysł do bardziej użytecznej pracy. Opakowanie w dużej mierze opiera się na wspomnianym wcześniej pakiecie parse.

+0

Jak korzystać z tej klasy, którą opisujesz na końcu ?. Wygląda na idealne uzupełnienie zwykłych metod ciągów. – Harsh

+0

@Harsh zobacz moją zaktualizowaną odpowiedź dla niektórych wskazówek. –

+0

Dzięki. Teraz jest jaśniej. – Harsh

Powiązane problemy