Bon timing avec l'article de Sam et Max (la débâcle de async en 3.7), j'ai récemment activé les warnings par défaut via cette variable d'environnement dans mon .zshrc :

PYTHONWARNINGS="once"



Et quelle horreur ! Je me suis fait spammer comme jamais ☠
Donc j'ai entrepris un gros nettoyage de printemps de l'écosystème Python.



Fixture Pytest


Pour faciliter les recherches et les tests, je me suis servi de cette fixture pytest dès que possible (à placer dans conftest.py):
import pytest


@pytest.fixture(autouse=True)
def no_warnings(recwarn):
    """Fail on warning."""

    yield

    warnings = []
    for warning in recwarn:  # pragma: no cover
        message = str(warning.message)
        if (
            # ImportWarning: Not importing directory "..." missing __init__(.py)
            "Not importing directory" in message
            # or "" in message
        ):
            continue
        warnings.append("{w.filename}:{w.lineno} {w.message}".format(w=warning))
    assert not warnings



Cette fonction fera planter le test, même s'il était censé passer, en cas de warning.



ResourceWarning: unclosed file


Ça arrive lorsqu'un descripteur de fichier est toujours ouvert quand le garbage collector tente de libérer les ressources.
Voici plusieurs fonctions qui renvoient le contenu d'un fichier :
def func1(file: str) -> bytes:
    """Le descripteur de fichier n'est jamais fermé."""
    return open(file, "rb").read()

def func2(file: str) -> bytes:
    """En soi, ce code est correct, mais si une erreur survient entre .open() et .close() alors il y aura un leak."""
    f = open(file, "rb")
    content = f.read()
    f.close()
    return content

def func3(file: str) -> bytes:
    """Code correct utilisant le bloc try...finally. Il n'y aura jamais de leak."""
    f = open(file, "rb")
    try:
        return f.read()
    finally:
        f.close()

def func4(file: str) -> bytes:
    """La bonne façon de faire, c'est pythonique. Quel que soit ce qui arrive, il n'y aura jamais de leak."""
    with open(file, "rb") as f:
        return f.read()


Seule func4 devrait être utilisée. De manière générale, lorsqu'un object peut être utilisé va le context manager with, préférez cette forme.
func3 fait tout aussi bien le boulot, mais je trouve plus jolie et moins verbeuse la version avec context manager.

Modules impactés : aten, dotenv, fire, fonttools, hypothesis, jenkinsapi, jira, joblib, keras, lazydata, monkeytype, mss (1, 2), napalm, pathtools3, ply, pypager, pytest-rerunfailures, python-chess, python-prompt-toolkit, pytorch, pyxdg, questionay, sorl-thumbnail, startlette, stegano, strictyaml, tensorflow, tinytag, toml, tornado, tuna, watchdog.
Projets impactés : algorithms, apt-listchanges, bandit, bashplotlib, cpython (1, 2), friture, httpbin, home-assistant, httpie, ipython, mitmproxy, nikola, nuitka (1, 2), pip, pipenv, pre-commit, ptpython, pygments, pyinstaller (1, 2, 3), ptterm, pymux, pyvim, tox, virtualenv, xonsh.



ResourceWarning: subprocess XXX is still running


Dans la même lignée que ResourceWarning: unclosed file, mais pour les processus encore en cours.
# Chacune de ces fonctions est une bonne façon de faire

def func1():
    with subprocess.Popen(...) as p:
        return p.stdout.read()

def func2():
    return subprocess.check_output(...)


Il est aussi possible d'appeler p.wait() mais il se peut qu'il y ait des ResourceWarning: unclosed file. Mieux vaut privilégier le context manager quand possible.

Projets impactés : nuxeo-drive, pyqt5.



ResourceWarning: unclosed socket.socket


Dans la même lignée que ResourceWarning: unclosed file, fixer les socket encore ouverts est un peu plus difficile. Si vous souhaitez jeter un œil, il y a de quoi faire avec HTTPie et requests.

Modules impactés : jira.



DeprecationWarning: invalid escape sequence


(SyntaxWarning à partir de Python 3.8)
(SyntaxError à partir de Python 3.9 [ou 4.0])

La source du problème est comment Python interprète le contenu des chaînes de caractère. Quand il y a un antislash, il va tenter de comprendre s'il s'agit d'un caractère unicode, hexadécimal ou un caractère spécial. Donc s'il y a un antislash et qu'il n'y a pas de séquence particulière à déchiffrer, ou même pour les regexp, il faut préfixer le code avec r ou br :
regexp = re.compile(r"(\d+)")
regexp_b = re.compile(br"(\d+)")
path = r"C:\Windows\System32"

def func() -> None:
    r"""Code qui ne fait rien ¯\_(ツ)_/¯"""
    return None


À noter que rb n'est pas valide en Python 2, donc br pour tout le monde.

Afin de dénicher ce genre d'erreur, vous pouvez utiliser cette commande :
python -Wall -m compileall -q -f *



Modules impactés : appkernel, cntk, crayons, docker-py (1, 2), jira, js2py, keras, networkx, parse, pandas, pathtools3, poetry, prettytable, protobuf, psutil, pycryptodome (1, 2, 3), pyobjc, pypac, pyrax, pyte, python-chess, python-prompt-toolkit, python-toolbox, pytorch, scikit-learn, selenium, sunpy, tinytag.
Projets impactés : algorithms, apt-listchanges, bandit, briefcase, certbot, cpython (1, 2, 3), emscripten, enki, ipython, mitmproxy, mypy, naomi, nikola, pep8speaks, pyinstaller, pympler, pytest-xdist, thefuck, waf, zeronet.



BytesWarning: comparison between bytes and string


Erreur typique que tout le monde a du gérer lors du passage de Python 2 à Python 3 :
if "abc" == b"abc":
    print("Ce code ne sera jamais exécuté car la condition sera toujours fausse :boom:")


Vous pouvez utiliser l'argument -bb pour aider à détecter ce genre d'erreurs :
python -bb ARGS



Modules impactés : nuxeo, tzlocal, watchodog, watchdog3.



Autres Résultats


  • ResourceWarning - unclosed scandir iterator : xonsh.


  • DeprecationWarning - using or importing the ABCs from 'collections' instead of from 'collections.abc' : nuitka, nuxeo, watchdog, watchdog3.


  • DeprecationWarning - type argument to addoption() is a string 'choice' : pytest-timeout.






  • DeprecationWarning - open() 'U' mode is deprecated : fire, pyinstaller (1, 2).


  • DeprecationWarning - BaseException.message has been deprecated : nuxeo.


  • DeprecationWarning - the 'logging.warn' method is deprecated, use 'warning' instead : appkernel, jenkinsapi, pip.






Statistiques


Combien de patches ont été mergé ?
  • 2018: 67 (+863 -656).

  • 2019: 81 (+1765 -1661) (67 projets +1647 -1550 au 2019-03-09)



Historique


  • 2019-02-20 : Meilleure version de la commande pour trouver les erreurs du type comparison between bytes and ....

  • 2019-02-13 : Ajout de la commande pour trouver les erreurs du type comparison between bytes and ....

  • 2019-01-03 : Ajout de la commande pour trouver les erreurs du type invalid escape sequence.

  • 2019-01-01 : Ajout de la section statistiques.

  • 2018-10-04 : Ajout de la function func3 utilisant le bloc try...finally ; nouvelle version de la fixture no_warnings().