str.format (lista) con índice negativo no funciona en Python

Utilizo un índice negativo en los campos de reemplazo para generar una lista con formato, pero genera un TypeError. Los códigos son los siguientes:

 >>> a = [1,2,3]
 >>> a [2]
 3
 >>> a [-1]
 3
 >>> 'El último: {0 [2]}'. Formato (a)
 'El último: 3'
 >>> 'El último: {0 [-1]}'. Formato (a)
 Rastreo (llamadas recientes más última):
   Archivo "", línea 1, en 
 TypeError: los índices de lista deben ser enteros, no str

Es lo que yo llamaría un problema de diseño en las especificaciones de cadena de formato. Por los documentos ,

element_index ::= integer | index_string 

pero, por desgracia, -1 no es “un entero”, es una expresión. El operador sin unario ni siquiera tiene una prioridad particularmente alta, por lo que, por ejemplo, la print(-2**2) emite -4 , otro problema común y posiblemente un problema técnico de diseño (el operador ** tiene una prioridad más alta, por lo que el El aumento de potencia se produce primero, luego el signo de cambio solicitado por la prioridad más baja unario - ).

Cualquier cosa en esa posición en la cadena de formato que no sea un entero (pero, por ejemplo, una expresión) se trata como una cadena, para indexar un argumento dict, por ejemplo:

 $ python3 -c "print('The last:{0[2+2]}'.format({'2+2': 23}))" The last:23 

No estoy seguro de si vale la pena plantear un problema en el trac de Python, pero ciertamente es un comportamiento sorprendente :-(.

Correcto, no funciona. solución:

 >>> 'The last:{0}'.format(a[-1]) 'The last:3' 

Hay algunos problemas aquí, una vez que comiences a cavar:

El elemento en cuestión se llama “element_index”, que se define como un entero.

Problema 1: a menos que los usuarios sigan el enlace de “entero” al manual de referencia del idioma, no sabrán que -1 se considera una expresión, no un entero. Por cierto, cualquier persona tentada a decir “funciona como se documenta” debe ver primero el proplem 7 🙂

Solución preferida: cambie la definición para que “element_index” pueda tener un ‘-‘ opcional antes del entero.

Es un entero, ¿verdad? No tan rápido … más tarde, los documentos dicen que “una expresión de la forma ‘[índice]’ hace una búsqueda de índice usando __getitem__()

Problema 3: Debería decir ‘[element_index]’ (el índice no está definido).

Problema 4: No todo el mundo sabe lo que hace __getitem__() . Necesita documentos más claros.

Así que podemos usar un dict aquí así como un entero, ¿podemos? Sí, con un problema o dos:

El element_index es un entero? Sí, eso funciona con un dict:

 >>> "{0[2]}".format({2: 'int2'}) 'int2' 

Parece que también podemos usar cadenas no enteras, pero esto necesita documentación más explícita (Problema 5):

 >>> "{0[foo]}".format({'foo': 'bar'}) 'bar' 

Pero no podemos usar un dictado con una tecla como ‘2’ (Problema 6):

 >>> "{0[2]}".format({'2': 'str2'}) Traceback (most recent call last): File "", line 1, in  KeyError: 2 >>> "{0['2']}".format({'2': 'str2'}) Traceback (most recent call last): File "", line 1, in  KeyError: "'2'" 

Problema 7: Ese “entero” debería documentarse como “decimalinteger” … 0x22 y 0b11 se tratan como str, y 010 (un “octalinteger”) se tratan como 10, no 8:

 >>> "{0[010]}".format('0123456789abcdef') 'a' 

Actualización: PEP 3101 cuenta la verdadera historia:
“”
Las reglas para analizar una clave de elemento son muy simples. Si comienza con un dígito, entonces se trata como un número, de lo contrario se usa como una cadena.

Debido a que las claves no están delimitadas por comillas, no es posible especificar claves de diccionario arbitrarias (por ejemplo, las cadenas “10” o “: -]”) dentro de una cadena de formato.
“”

A menudo tomo cadenas de formato Python como opciones de configuración, con la cadena de formato provista con una lista específica y conocida de argumentos de palabras clave. Por lo tanto, direccionar los índices de una lista de longitud variable hacia adelante o hacia atrás dentro de la cadena de formato es exactamente el tipo de cosa que termino necesitando.

Acabo de escribir este truco para que funcione la indexación negativa:

 string_to_tokenise = "Hello_world" tokens = re.split(r"[^AZ\d]+", string_to_tokenise, flags=re.I) token_dict = {str(i) if i < 0 else i: tokens[i] for i in range(-len(tokens) + 1, len(tokens))} print "{thing[0]} {thing[-1]}".format(thing=token_dict) 

Resultado:

 Hello world 

Entonces, para explicar, en lugar de pasar la lista de tokens, creo un diccionario con todas las claves de enteros requeridas para indexar la lista de 0 a len (..) - 1, y también agrego las claves de enteros negativas para indexar desde termina de -1 a - (len (..) - 1), sin embargo, estas claves se convierten de enteros a cadenas, ya que así es como el formato los interpretará.