Expresión regular para coincidir con la lista separada por comas de clave = valor donde el valor puede contener comas

Tengo un “analizador” ingenuo que simplemente hace algo como:
[x.split('=') for x in mystring.split(',')]

Sin embargo, mystring puede ser algo como
'foo=bar,breakfast=spam,eggs'

Obviamente,
El divisor ingenuo simplemente no lo hará. Estoy limitado a la biblioteca estándar de Python 2.6 para esto,
Así, por ejemplo, no se puede utilizar el parámetro de reproducción .

La salida esperada es
[('foo', 'bar'), ('breakfast', 'spam,eggs')]

Estoy tratando de hacer esto con expresiones regulares, pero estoy enfrentando los siguientes problemas:

Mi primer bash
r'([a-z_]+)=(.+),?'
Me dio
[('foo', 'bar,breakfast=spam,eggs')]

Obviamente,
Hacer .+ No codicioso no resuelve el problema.

Asi que,
Supongo que de alguna manera tengo que hacer que la última coma (o $ ) sea obligatoria.
Hacer eso realmente no funciona,
r'([a-z_]+)=(.+?)(?:,|$)'
Al igual que con eso, se omite el contenido detrás de la coma en un valor que contiene uno,
por ejemplo, [('foo', 'bar'), ('breakfast', 'spam')]

Creo que debo usar algún tipo de operación de mirar atrás (?).
Las preguntas)
1. ¿Cuál uso? o
2. ¿Cómo hago eso / esto?

Editar :

Basado en la respuesta de datwigrak a continuación,
Terminé haciendo casi lo mismo que abarnert sugirió más tarde en una forma un poco más verbosa;

 vals = [x.rsplit(',', 1) for x in (data.split('='))] ret = list() while vals: value = vals.pop()[0] key = vals[-1].pop() ret.append((key, value)) if len(vals[-1]) == 0: break 

EDIT 2:

Solo para satisfacer mi curiosidad, ¿es esto realmente posible con expresiones regulares puras ? Es decir, para que re.findall() devuelva una lista de 2-tuplas?

Solo para fines de comparación, aquí hay una expresión regular que parece resolver el problema también:

 ([^=]+) # key = # equals is how we tokenise the original string ([^=]+) # value (?:,|$) # value terminator, either comma or end of string 

El truco aquí es restringir lo que estás capturando en tu segundo grupo. .+ traga el signo = , que es el carácter que podemos usar para distinguir las claves de los valores. La expresión regular completa no se basa en ningún seguimiento (por lo que debería ser compatible con algo como re2 , si es deseable) y puede funcionar en los ejemplos de abarnert.

Uso de la siguiente manera:

 re.findall(r'([^=]+)=([^=]+)(?:,|$)', 'foo=bar,breakfast=spam,eggs,blt=bacon,lettuce,tomato,spam=spam') 

Que devuelve:

 [('foo', 'bar'), ('breakfast', 'spam,eggs'), ('blt', 'bacon,lettuce,tomato'), ('spam', 'spam')] 

La respuesta de datwigrak o casi funciona, o funciona como está; es difícil decirlo por la forma en que se formatea la salida de muestra y las vagas descripciones de los pasos. Pero si es la versión que casi funciona, es fácil de arreglar.

Poniéndolo en código:

 >>> bits=[x.rsplit(',', 1) for x in s.split('=')] >>> kv = [(bits[i][-1], bits[i+1][0]) for i in range(len(bits)-1)] 

La primera línea es (creo) la respuesta de datwigrak. Por sí misma, la primera línea le da pares de (value_i, key_i+1) lugar de (key_i, value_i) . La segunda línea es la solución más obvia para eso. Con más pasos intermedios y un poco de salida, para ver cómo funciona:

 >>> s = 'foo=bar,breakfast=spam,eggs,blt=bacon,lettuce,tomato,spam=spam' >>> bits0 = s.split('=') >>> bits0 ['foo', 'bar,breakfast', 'spam,eggs,blt', 'bacon,lettuce,tomato,spam', 'spam'] >>> bits = [x.rsplit(',', 1) for x in bits0] >>> bits [('foo'), ('bar', 'breakfast'), ('spam,eggs', 'blt'), ('bacon,lettuce,tomato', 'spam'), ('spam')] >>> kv = [(bits[i][-1], bits[i+1][0]) for i in range(len(bits)-1)] >>> kv [('foo', 'bar'), ('breakfast', 'spam,eggs'), ('blt', 'bacon,lettuce,tomato'), ('spam', 'spam')] 

Podría sugerirte que utilices las operaciones de división como antes. Pero dividir primero en el igual, luego dividir en la coma más a la derecha, para hacer una lista única de cadenas de izquierda y derecha.

 input = "bob=whatever,king=kong,banana=herb,good,yellow,thorn=hurts" 

al principio se dividirá

 first_split = input.split("=") #first_split = ['bob' 'whatever,king' 'kong,banana' 'herb,good,yellow,thorn' 'hurts'] 

luego dividir en la coma más a la derecha le da:

 second_split = [single_word for sublist in first_split for item in sublist.rsplit(",",1)] #second_split = ['bob' 'whatever' 'king' 'kong' 'banana' 'herb,good,yellow' 'thorn' 'hurts'] 

entonces solo reúnes las parejas como esta:

 pairs = dict(zip(second_split[::2],second_split[1::2])) 

Puedes probar esto, funcionó para mí:

 mystring = "foo=bar,breakfast=spam,eggs,e=a" n = [] i = 0 for x in mystring.split(','): if '=' not in x: n[i-1] = "{0},{1}".format(n[i-1], x) else: n.append(x) i += 1 print n 

Obtienes un resultado como:

  ['foo=bar', 'breakfast=spam,eggs', 'e=a'] 

Entonces simplemente puedes revisar la lista y hacer lo que quieras.

Suponiendo que el nombre de la clave nunca contenga , puede dividirse en la siguiente secuencia sin , y = es seguido por = .

 re.split(r',(?=[^,=]+=)', inputString) 

(Esto es lo mismo que mi solución original. Espero que se re.split , en lugar de re.findall o str.split ).

La solución completa se puede hacer en una sola línea:

 [re.findall('(.*?)=(.*)', token)[0] for token in re.split(r',(?=[^,=]+=)', inputString)]