Je modifie un code pour qu'il soit compatible entre Python 2
et Python 3
, mais j'ai observé un avertissement dans la sortie du test unitaire.
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/case.py:601:
ResourceWarning: unclosed socket.socket fd=4,
family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6,
laddr=('1.1.2.3', 65087), raddr=('5.8.13.21', 8080)
Un peu de recherche a déterminé que cela se produisait également à partir de bibliothèques populaires telles que request et boto3 .
Je pourrais ignorer l'avertissement ou le filtrer complètement. Si c'était mon service, je pourrais définir l'en-tête connection: close
dans ma réponse ( link ).
Voici un exemple présentant l'avertissement dans Python 3.6.1
:
app.py
import requests
class Service(object):
def __init__(self):
self.session = requests.Session()
def get_info(self):
uri = 'http://api.stackexchange.com/2.2/info?site=stackoverflow'
response = self.session.get(uri)
if response.status_code == 200:
return response.json()
else:
response.raise_for_status()
def __del__(self):
self.session.close()
if __== '__main__':
service = Service()
print(service.get_info())
test.py
import unittest
class TestService(unittest.TestCase):
def test_growing(self):
import app
service = app.Service()
res = service.get_info()
self.assertTrue(res['items'][0]['new_active_users'] > 1)
if __== '__main__':
unittest.main()
Existe-t-il un moyen meilleur/correct de gérer la session de sorte qu'elle soit explicitement fermée et de ne pas compter sur __del__()
pour générer ce type d'avertissement.
Merci pour toute aide.
Avoir la logique de démontage dans __del__
peut rendre votre programme incorrect ou plus difficile à raisonner, car il n'y a aucune garantie quant au moment où cette méthode sera appelée, ce qui peut potentiellement conduire à l'avertissement que vous avez reçu. Il y a deux façons de résoudre ce problème:
tearDown
unittest
's tearDown
method vous permet de définir du code qui sera exécuté après chaque test. L'utilisation de ce hook pour fermer la session fonctionnera même si le test échoue ou comporte une exception, ce qui est Nice.
app.py
import requests
class Service(object):
def __init__(self):
self.session = requests.Session()
def get_info(self):
uri = 'http://api.stackexchange.com/2.2/info?site=stackoverflow'
response = self.session.get(uri)
if response.status_code == 200:
return response.json()
else:
response.raise_for_status()
def close(self):
self.session.close()
if __== '__main__':
service = Service()
print(service.get_info())
service.close()
test.py
import unittest
import app
class TestService(unittest.TestCase):
def setUp(self):
self.service = app.Service()
super().setUp()
def tearDown(self):
self.service.close()
def test_growing(self):
res = self.service.get_info()
self.assertTrue(res['items'][0]['new_active_users'] > 1)
if __== '__main__':
unittest.main()
Un gestionnaire de contexte est également un moyen très utile de définir explicitement la portée de quelque chose. Dans l'exemple précédent, vous devez vous assurer que .close()
est appelé correctement sur chaque site d'appel, sinon vos ressources risquent de fuir. Avec un gestionnaire de contexte, cela est géré automatiquement même s'il existe une exception dans l'étendue du gestionnaire de contexte.
En vous basant sur la solution 1), vous pouvez définir des méthodes magiques supplémentaires (__enter__
et __exit__
) afin que votre classe fonctionne avec l'instruction with
.
Remarque: ce qui est bien ici, c'est que ce code prend également en charge l'utilisation de la solution 1), avec la fonction explicite .close()
, ce qui peut être utile si un gestionnaire de contexte était gênant pour une raison quelconque.
app.py
import requests
class Service(object):
def __init__(self):
self.session = requests.Session()
def __enter__(self):
return self
def get_info(self):
uri = 'http://api.stackexchange.com/2.2/info?site=stackoverflow'
response = self.session.get(uri)
if response.status_code == 200:
return response.json()
else:
response.raise_for_status()
def close(self):
self.session.close()
def __exit__(self, exc_type, exc_value, traceback):
self.close()
if __== '__main__':
with Service() as service:
print(service.get_info())
test.py
import unittest
import app
class TestService(unittest.TestCase):
def test_growing(self):
with app.Service() as service:
res = service.get_info()
self.assertTrue(res['items'][0]['new_active_users'] > 1)
if __== '__main__':
unittest.main()
En fonction de vos besoins, vous pouvez utiliser l'un ou l'autre ou une combinaison de setUp
/tearDown
et du gestionnaire de contexte, et vous débarrasser de cet avertissement, sans oublier une gestion plus explicite des ressources dans votre code!