Mezclando los argumentos de datetime.strptime ()

Es un error bastante común mezclar los argumentos de cadena de fecha y formato datetime.strptime() usando:

 datetime.strptime("%B %d, %Y", "January 8, 2014") 

en lugar de al revés:

 datetime.strptime("January 8, 2014", "%B %d, %Y") 

Por supuesto, fallaría durante el tiempo de ejecución:

 >>> datetime.strptime("%B %d, %Y", "January 8, 2014") Traceback (most recent call last): File "", line 1, in  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/_strptime.py", line 325, in _strptime (data_string, format)) ValueError: time data '%B %d, %Y' does not match format 'January 8, 2014' 

Pero, ¿es posible detectar este problema de manera estática incluso antes de ejecutar el código? ¿Es algo con lo que pylint o flake8 puede ayudar?


He intentado la inspección del código de PyCharm, pero ambos fragmentos de código no emiten ninguna advertencia. Probablemente, debido a que ambos argumentos son del mismo tipo, ambos son cadenas que dificultan el problema. Tendríamos que analizar realmente si una cadena es una cadena de formato de fecha y hora o no. Además, la función Language Injections PyCharm / IDEA parece relevante.

Afirmo que esto no puede comprobarse estáticamente en el caso general .

Considere el siguiente fragmento de código:

 d = datetime.strptime(read_date_from_network(), read_format_from_file()) 

Este código puede ser completamente válido, donde tanto read_date_from_network y read_format_from_file realmente devuelven cadenas con el formato adecuado, o pueden ser basura total, ambas devolviendo None o alguna basura. En cualquier caso, esa información solo se puede determinar en tiempo de ejecución; por lo tanto, un comprobador estático no tiene poder.


Además, dada la definición actual de datetime.strptime, incluso si estuviéramos usando un lenguaje de tipo estático, no podríamos detectar este error (excepto en casos muy específicos), la razón es que la firma de esta función nos condenó desde el principio :

 classmethod datetime.strptime(date_string, format) 

en esta definición, date_string y format son ambas cadenas , aunque en realidad tienen un significado especial. Incluso si tuviéramos algo análogo en un lenguaje estáticamente tipado como este:

 public DateTime strpTime(String dateString, String format) 

El comstackdor (y el relevo y todos los demás) todavía solo ve:

 public DateTime strpTime(String, String) 

Lo que significa que ninguno de los siguientes son distinguibles entre sí:

 strpTime("%B %d, %Y", "January 8, 2014") // strpTime(String, String) CHECK strpTime("January 8, 2014", "%B %d, %Y") // strpTime(String, String) CHECK strpTime("cat", "bat") // strpTime(String, String) CHECK 

Esto no quiere decir que no se pueda hacer en absoluto; existen algunos dinteles para lenguajes de tipo estático como Java / C ++ / etc. inspeccionará las cadenas literales cuando las pase a algunas funciones específicas (como printf, etc.), pero esto solo se puede hacer cuando se llama a esa función directamente con una cadena de formato literal. Los mismos linters se vuelven igual de indefensos en el primer caso que presenté, porque simplemente aún no se sabe si las cuerdas tendrán el formato correcto.

Es decir, una guía puede advertir sobre esto:

 // Linter regex-es the first argument, sees %B et. al., warns you strpTime("%B %d, %Y", "January 8, 2014") 

pero no podría advertir sobre esto:

 strpTime(scanner.readLine(), scanner.readLine()) 

Ahora, lo mismo se podría diseñar en una lengüeta de python, pero no creo que sea muy útil porque las funciones son de primera clase, por lo que podría derrotar fácilmente la linter (hipotética de python) escribiendo:

 f = datetime.strptime d = f("January 8, 2014", "%B %d, %Y") 

Y luego volvemos a ser bastante flexibles.


Bonus: Lo que salió mal

El problema aquí es que datetime.strptime da un significado implícito a cada una de estas cadenas, pero no revela esa información al sistema de tipos. Lo que se podría haber hecho fue dar a las dos cadenas diferentes tipos de caracteres, entonces podría haber habido más seguridad, aunque a expensas de cierta facilidad de uso.

Por ejemplo (usando anotaciones de tipo PEP 484, una cosa real ):

 class DateString(str): pass class FormatString(str): pass class datetime(date): ... def strptime(date_string: DateString, format: FormatString) -> datetime: # etc. etc. 

Entonces empezaría a ser factible proporcionar una buena alineación en el caso general, aunque las clases DateString y FormatString tendrían que hacerse cargo de validar su entrada, porque, de nuevo, el sistema de tipos no puede hacer nada a ese nivel.


Epílogo:

Creo que la mejor manera de lidiar con esto es evitar el problema utilizando el método strftime , que está vinculado a un objeto de fecha y hora específico y toma solo un argumento de cadena de formato. Eso evita todo el problema al darnos una firma de función que no nos corta cuando lo abrazamos. Hurra.