2012-08-07 13 views
25

Tengo cadenas del formulario Version 1.4.0\n y Version 1.15.6\n, y me gustaría una forma simple de extraer los tres números de ellas. Sé que puedo poner variables en una cadena con el método de formato; Básicamente quiero hacerlo hacia atrás, de esta manera:Convierte, o no, un string a variables (como format(), pero al revés) en Python

# 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 

Otra persona me encontré con la misma pregunta, pero la respuesta fue específica a su caso particular: Use Python format string in reverse for parsing

una respuesta general (cómo hacer format() en reversa) sería genial! Sin embargo, una respuesta para mi caso específico sería muy útil.

+3

veo algunas de las respuestas a continuación que son directas para su problema. pero una mejor solución sería usar expresiones regulares en mi humilde opinión. –

+0

Esto parece ser un buen uso para [scanf] (http://code.activestate.com/recipes/502213-simple-scanf-implementation/) C-style – Gaius

Respuesta

0

En realidad, la biblioteca de expresiones regulares de Python ya proporciona la funcionalidad general que está solicitando. Sólo hay que cambiar la sintaxis del patrón ligeramente

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

Como se puede ver, hay que cambiar las cadenas de formato (ONU) desde {0} a (? P < _0>. +). Incluso podría requerir un decimal con (? P < _0> \ d +). Además, debe escapar de algunos de los caracteres para evitar que se interpreten como caracteres especiales de expresiones regulares. Pero esto en turm se puede automatizar de nuevo, p. con

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

Este

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

le dará int valores para a, b y c

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

Dependiendo de cómo regular o irregular, es decir, consistente, los formatos de número/versión será, usted Puede que desee considerar el uso de las expresiones regulares , aunque si van a permanecer en este formato, preferiría la solución más simple si Funciona para ti.

+1

+1 solo usa la expresión del generador, no es necesario '[]' . –

+1

@AshwiniChaudhary Sí, tienes razón. Primero aprendí acerca de la comprensión de listas, así que ahí es donde voy inicialmente, pero tienes razón, no hay necesidad de mantener una lista. Gracias, actualicé la respuesta. – Levon

+1

Usar un generador en este caso no tiene sentido, la comprensión de la lista hará bien el trabajo. – Willian

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

Vaya, lo que quise decir es 'x, y, z = [int (num) para el resultado en re.findall ('(\ d +) \. (\ D +) \. (\ D +)', 'Versión 1.15.6 \ n ') para el número en el resultado] ' –

+0

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

+0

Es un buen refinamiento pero aún no convierte los resultados en enteros. Modifico mi ejemplo: 'x, y, z = [int (num) para num en re.findall ('(\ d +) \. (\ D +) \. (\ D +)', 'Versión 1.15.6 \ n ') [0]] ' –

2

Hace algún tiempo hice el siguiente código que hace lo contrario de formato, pero limitada a los casos que necesitaba.

Y, nunca lo han probado, pero creo que este es también el propósito de la parse library

Mi código:

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 

para su caso, aquí es un ejemplo de uso:

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

Tenga en cuenta el filter(None,v) para eliminar las versiones no compatibles (que devuelven None). Aquí no es necesario.

4

Solo para construir en Uche's answer, estaba buscando una manera de invertir una cadena a través de un patrón con kwargs.Así que puse juntos la siguiente función:

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 

que funciona según:

>>> 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: También vea this answer para un poco más de información acerca parse y parmatter.

El paquete PyPI parse sirve bien para este propósito:

pip install parse 

Puede ser utilizado como esto:

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

Tenga en cuenta que el paquete the docs sayparse no hace exactamente emular el format specification mini-language por defecto; también usa algunos indicadores de tipo especificados por re. De especial importancia es que s significa "espacio en blanco" por defecto, en lugar de str. Esto puede ser fácilmente modificado para ser compatible con la especificación de formato cambiando el tipo predeterminado para s a str (usando extra_types):

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

Aquí es una idea conceptual para una modificación de la string.Formatter clase incorporada mediante el parse paquete para agregar capacidad de unformat que yo mismo he utilizado:

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__ 

IMPORTANTE: el nombre del método parse ya está en uso por la clase Formatter, así que he elegido unformat en su lugar para evitar conflictos.

ACTUALIZACIÓN: Puede usarlo así: muy similar a la clase string.Formatter.

Formateo (idéntico al '{: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) 

Esto es por supuesto de uso muy limitado como se muestra arriba. Sin embargo, he puesto un paquete pypi (parmatter - un proyecto originalmente para mi propio uso, pero tal vez otros lo encuentren útil) que explora algunas ideas de cómo poner esta idea en un trabajo más útil. El paquete se basa en gran medida en el paquete parse antes mencionado.

+0

¿Cómo uso esta clase que describes al final ?. Parece una adición perfecta a los métodos de cuerda regulares. – Harsh

+0

@Harsh, consulta mi respuesta actualizada para obtener orientación. –

+0

Gracias. Está más claro ahora. – Harsh

Cuestiones relacionadas