values read print functions create array python string list multiline

print - read a list python



Problema de advertencia por falta de coma entre errores de elementos de lista (3)

Esta expresión regular encontrará ocurrencias del problema. Simplemente busca todos los archivos en tu proyecto.

/[("[^"]*",[/s]*)*"[^"]*"[/s]*"

Probado en https://regex101.com/ y NotePad ++

La historia:

Cuando se define una lista de cadenas en múltiples líneas, a menudo es fácil olvidar una coma entre los elementos de la lista, como en este caso de ejemplo:

test = [ "item1" "item2" ]

La test lista ahora tendría un solo elemento "item1item2" .

Muy a menudo el problema aparece después de reorganizar los elementos en una lista.

Preguntas de muestra de desbordamiento de pila que tienen este problema:

La pregunta:

¿Hay una manera de, preferiblemente utilizando el análisis de código estático , emitir una advertencia en casos como este para detectar el problema lo antes posible?


Implementé un código basado en la publicación de @Jim. Que funcione en todas las situaciones:

import tokenize from io import BytesIO def my_checker(pycode): """ tokenizes python code and yields start, end, strline of any position where a scenario like this happens (missing string seperator): [..., "a string" "derp", ...] """ IDLE = 0 WAITING_STRING = 1 CHECKING_SEPARATOR = 2 tokenizer = tokenize.tokenize(BytesIO(pycode.encode(''utf-8'')).readline) state = IDLE for toknum, tokval, start, end, strcode in tokenizer: if state == IDLE: if toknum == tokenize.OP and tokval == ''['': state = WAITING_STRING elif state == WAITING_STRING: if toknum == tokenize.STRING: state = CHECKING_SEPARATOR elif toknum == tokenize.OP and tokval == ['']'']: state = IDLE elif state == CHECKING_SEPARATOR: if toknum == tokenize.STRING: yield (start, end, strcode) elif toknum == tokenize.OP and tokval in [''+'', '','']: state = WAITING_STRING elif toknum == tokenize.OP and tokval == '']'': state = IDLE my_code = """ foo = "derp" def derp(a,x): return str(''dingdong''+str(a*x)) [ "derp"+"FOO22" , "FOO", "donk" "slurp",0, 0 ] class extreme_logical_class(): STATIC_BAD_LIST = [0, "BLA,", "FOO" "derp" ] def __init__(self): self._in_method_check = ["A" "B"] nested_list = [ [''DERP'',''FOO''], [0,''hello'', ''peter'' ''pan''], [''this'', ''is'', [''ultra'', ''mega'' ''nested'']] ] """ for error in my_checker(my_code): print(''missing , in list at: line {}@{} to line {}@{}: "{}"''.format( error[0][0],error[0][1],error[1][0],error[1][1], error[2].strip() ))

El resultado es:

keksnicoh@localhost ~ % python3 find_bad_lists.py missing , in list at: line 6@36 to line 6@43: ""derp"+"FOO22" , "FOO", "donk" "blurp",0 0" missing , in list at: line 13@8 to line 13@14: ""derp"" missing , in list at: line 16@37 to line 16@40: "self._in_method_check = ["A" "B"]" missing , in list at: line 20@24 to line 20@29: "[0,''hello'', ''peter'' ''pan'']," missing , in list at: line 22@8 to line 22@16: "''nested'']]"

En la vida real, preferiría evitar cometer esos errores; Hay buenos IDE como Sublime Text que te permiten editar y formatear listas con varios cursores. Si te acostumbras a esos conceptos, este tipo de errores de "separación" no ocurrirán en tu código.

Por supuesto, si uno tiene un Equipo de Desarrolladores, podría integrar dicha herramienta en el entorno de prueba.


Estas son soluciones meramente probables ya que no soy realmente apto para el análisis estático .

Con tokenize :

Hace poco jugué con tokenizing código python y creo que tiene toda la información necesaria para realizar este tipo de controles cuando se agrega suficiente lógica. Para su lista dada, los tokens generados con python -m tokenize list1.py son los siguientes:

python -m tokenize list1.py 1,0-1,4: NAME ''test'' 1,5-1,6: OP ''='' 1,7-1,8: OP ''['' 1,8-1,9: NL ''/n'' 2,1-2,8: STRING ''"item1"'' 2,8-2,9: NL ''/n'' 3,1-3,8: STRING ''"item2"'' 3,8-3,9: NL ''/n'' 4,0-4,1: OP '']'' 4,1-4,2: NEWLINE ''/n'' 5,0-5,0: ENDMARKER ''''

Por supuesto, este es el caso " problemático " en el que los contenidos se concatenarán. En el caso donde está presente un, la salida cambia ligeramente para reflejar esto (agregué los tokens solo para el cuerpo de la lista):

1,7-1,8: OP ''['' 1,8-1,9: NL ''/n'' 2,1-2,8: STRING ''"item1"'' 2,8-2,9: OP '','' 2,9-2,10: NL ''/n'' 3,1-3,8: STRING ''"item2"'' 3,8-3,9: NL ''/n'' 4,0-4,1: OP '']''

Ahora tenemos el token OP '','' adicional que significa la presencia de un segundo elemento separado por coma.

Dada esta información, podríamos usar el método realmente útil generate_tokens en el módulo tokenize . El método tokenize.generate_tokens() , tokenize.tokenize() en Py3 , tiene una sola línea de readline , un método en objetos similares a archivos que esencialmente devuelve la siguiente línea para ese archivo como objeto ( respuesta relevante ). Devuelve una tupla nombrada con 5 elementos en total con información sobre el tipo de token, la cadena del token junto con el número de línea y la posición en la línea.

Usando esta información, teóricamente podría recorrer un archivo y cuando un OP '','' está ausente dentro de una inicialización de lista (cuyo comienzo se detecta al verificar que los tokens NAME , OP ''='' y OP ''['' existan en la misma línea) número) se puede emitir una advertencia en las líneas en las que se detectó.

Lo bueno de este enfoque es que es bastante sencillo generalizar. Para adaptarse a todos los casos en los que tiene lugar la concatenación literal de cadena (es decir, dentro de los operadores de ''agrupación'' (), {}, [] ) se comprueba que el token es de type = 51 (o 53 para Python 3 ) o que un valor en cualquiera de (, [, { existe en la misma línea (estas son gruesas, la parte superior de la cabeza sugiere sugerencias)).

Ahora, no estoy realmente seguro de cómo otras personas se enfrentan con este tipo de problemas, pero parece que podría ser algo que se pueda analizar . Toda la información necesaria es ofrecida por tokenize , la lógica para detectarlo es lo único que falta.

Nota de implementación: estos valores (por ejemplo, para el type ) difieren entre las versiones y están sujetos a cambios, por lo que es algo que se debe tener en cuenta. Sin embargo, uno podría aprovechar esto trabajando solo con constantes para los tokens.

Con parser y ast :

Otra solución probable que probablemente sea más tediosa podría involucrar el parser y los módulos ast . La concatenación de cadenas se realiza en realidad durante la creación del árbol de sintaxis abstracta, por lo que, alternativamente, podría detectarla allí.

Realmente no quiero descargar todo el resultado de los métodos para el parser y ast que voy a mencionar, pero, solo para asegurarnos de que estamos en la misma página, voy a utilizar la siguiente lista declaración de inicialización:

l_init = """ test = [ "item1" "item2", "item3" ] """

Para obtener el árbol de análisis generado, use p = parser.suite(l_init) . Una vez hecho esto, puede obtener una vista de él con p.tolist() (el resultado es demasiado grande para agregarlo). Lo que notará es que habrá tres entradas para los tres objetos str diferentes item1 , item1 , item1 .

Por otro lado, cuando el AST se crea con node = ast.parse(l_init) y se visualiza con ast.dump(node) , solo hay dos entradas : una para el elemento concatenado item1item2 y otra para el otro item1item2 .

Entonces, esta es otra forma probable de hacerlo, pero, como mencioné anteriormente, es mucho más tedioso. No estoy seguro de si la información de línea está disponible y usted trata con dos módulos diferentes. Téngalo como una idea retrospectiva si quiere jugar con objetos internos más altos en la cadena del compilador.

Comentarios finales: Como nota final, el enfoque de tokenize parece ser el más lógico en este caso. Por el contrario, parece que pylint realidad funciona con astroid y python lib que facilita el análisis de los árboles de sintaxis abstracta para el código de python. Por lo tanto, uno debería idealmente mirarlo y cómo se usa dentro del pylint .

Nota : Por supuesto , podría estar sobre-analizándolo por completo y bastaría con una solución más simple de ''buscar espacios en blanco o de nueva línea'', como ustedes sugirieron. :-)