# Modo de desarrollo de Python

Nuevo en la versión 3.7.

El modo de desarrollo de Python introduce comprobaciones adicionales en tiempo de ejecución que son muy costosas para ser activadas por defecto. No debería ser más verboso que el predeterminado si el código es correcto; sólo se emiten nuevas advertencias cuando se detecta un problema.

Puede activarse mediante la opción de línea de comandos `-X dev` o estableciendo la variable de entorno `PYTHONDEVMODE` en `1`.

# Efectos del modo de desarrollo de Python

Activar el modo de desarrollo de Python es similar al siguiente comando, pero con efectos adicionales que se describen a continuación:

    PYTHONMALLOC=debug PYTHONASYNCIODEBUG=1 python3 -W default -X faulthandler

Efectos del modo de desarrollo de Python:

- Añadir `default` filtro de avisos.Se muestran las siguientes advertencias:

  - `DeprecationWarning`

  - `ImportWarning`

  - `PendingDeprecationWarning`

  - `ResourceWarning`

  Normalmente, los advertencias son filtradas por defecto warning filters.

  Se comporta como si se utilizara la opción de línea de comandos `-W default`.

  Utilice la opción de línea de comandos `-W error` o establezca la variable de entorno `PYTHONWARNINGS` en `error` para tratar las advertencias como errores.

- Instalar hooks(enganches) de depuración en los asignadores de memoria para comprobar:

  - Desbordamiento del búfer

  - Sobrecarga del búfer

  - Violación de la API del asignador de memoria

  - Uso inseguro del GIL

  Ver la función en C `PyMem_SetupDebugHooks()`.

  Se comporta como si la variable de entorno `PYTHONMALLOC` estuviera establecida en `debug`.

  Para activar el modo de desarrollo de Python sin instalar ganchos de depuración en los asignadores de memoria, establezca la variable de entorno `PYTHONMALLOC` a `default`.

- Llama a `faulthandler.enable()` al inicio de Python para instalar los handlers(manejadores) de las señales `SIGSEGV`, `SIGFPE`, `SIGABRT`, `SIGBUS` y `SIGILL` para volcar la traza de Python en caso de fallo.

  Se comporta como si se utilizara la opción de línea de comandos `-X faulthandler` o si la variable de entorno `PYTHONFAULTHANDLER` se establece en `1`.

- Habilitar asyncio debug mode. Por ejemplo, `asyncio` comprueba las corutinas que no fueron esperadas y las registra.

  Se comporta como si la variable de entorno `PYTHONASYNCIODEBUG` estuviera establecida en `1`.

- Comprueba los argumentos encoding y errors para las operaciones de codificación y decodificación de cadenas. Ejemplos: `open()`, `str.encode()` y `bytes.decode()`.

  Por defecto, para un mejor rendimiento, el argumento errors sólo se comprueba en el primer error de codificación/decodificación y el argumento encoding a veces se ignora para las cadenas vacías.

- El destructor de `io.IOBase` registra las excepciones `close()`.

- Establece el atributo `dev_mode` de `sys.flags` a `True`.

El Modo de Desarrollo de Python no habilita el módulo `tracemalloc` por defecto, porque el costo de la sobrecarga (para el rendimiento y la memoria) sería demasiado grande. Activar el módulo `tracemalloc` proporciona información adicional sobre el origen de algunos errores. Por ejemplo, `ResourceWarning` registra la traza donde se asignó el recurso, y un error de desbordamiento de búfer registra la traza donde se asignó el bloque de memoria.

El modo de desarrollo de Python no impide que la opción de línea de comandos `-O` elimine las declaraciones `assert` ni que establezca `__debug__` a `False`.

Distinto en la versión 3.8: El destructor de `io.IOBase` ahora registra las excepciones `close()`.

Distinto en la versión 3.9: Los argumentos enconding y errors se comprueban ahora para las operaciones de codificación y descodificación de cadenas.

# Ejemplo de ResourceWarning

Ejemplo de un script que cuenta el número de líneas del archivo de texto especificado en la línea de comandos:

    import sys

    def main():
        fp = open(sys.argv[1])
        nlines = len(fp.readlines())
        print(nlines)
        # The file is closed implicitly

    if __name__ == "__main__":
        main()

El script no cierra el archivo explícitamente. Por defecto, Python no emite ninguna advertencia. Ejemplo usando README.txt, que tiene 269 líneas:

    $ python3 script.py README.txt
    269

Al activar el modo de desarrollo de Python aparece una advertencia `ResourceWarning`:

    $ python3 -X dev script.py README.txt
    269
    script.py:10: ResourceWarning: unclosed file <_io.TextIOWrapper name='README.rst' mode='r' encoding='UTF-8'>
      main()
    ResourceWarning: Enable tracemalloc to get the object allocation traceback

Además, al activar `tracemalloc` se muestra la línea en la que se abrió el archivo:

    $ python3 -X dev -X tracemalloc=5 script.py README.rst
    269
    script.py:10: ResourceWarning: unclosed file <_io.TextIOWrapper name='README.rst' mode='r' encoding='UTF-8'>
      main()
    Object allocated at (most recent call last):
      File "script.py", lineno 10
        main()
      File "script.py", lineno 4
        fp = open(sys.argv[1])

La solución es cerrar explícitamente el archivo. Ejemplo utilizando un gestor de contexto:

    def main():
        # Close the file explicitly when exiting the with block
        with open(sys.argv[1]) as fp:
            nlines = len(fp.readlines())
        print(nlines)

No cerrar un recurso explícitamente puede dejar un recurso abierto durante mucho más tiempo del estimado; puede causar graves problemas al salir de Python. Es malo en CPython, pero es aún peor en PyPy. Cerrar los recursos explícitamente hace que una aplicación sea más detallista y más fiable.

# Ejemplo de error de descriptor de archivo incorrecto

Script que muestra la primera línea de sí mismo:

    import os

    def main():
        fp = open(__file__)
        firstline = fp.readline()
        print(firstline.rstrip())
        os.close(fp.fileno())
        # The file is closed implicitly

    main()

Por defecto, Python no emite ninguna advertencia:

    $ python3 script.py
    import os

El modo de desarrollo de Python muestra un `ResourceWarning` y registra un error «Bad file descriptor» cuando termina el objeto archivo:

    $ python3 script.py
    import os
    script.py:10: ResourceWarning: unclosed file <_io.TextIOWrapper name='script.py' mode='r' encoding='UTF-8'>
      main()
    ResourceWarning: Enable tracemalloc to get the object allocation traceback
    Exception ignored in: <_io.TextIOWrapper name='script.py' mode='r' encoding='UTF-8'>
    Traceback (most recent call last):
      File "script.py", line 10, in <module>
        main()
    OSError: [Errno 9] Bad file descriptor

`os.close(fp.fileno())` cierra el descriptor de archivo. Cuando el finalizador de objetos de archivo intenta cerrar el descriptor de archivo de nuevo, falla con el error `Bad file descriptor`. Un descriptor de archivo debe cerrarse sólo una vez. En el peor de los casos, cerrarlo dos veces puede provocar un fallo (ver bpo-18748 para un ejemplo).

La solución es eliminar la línea `os.close(fp.fileno())`, o abrir el archivo con `closefd=False`.