
C'est une nouvelle sélection de trucs et astuces sur Python et la programmation de mon canal Telegram @pythonetc.
Publications précédentes:
Deux méthodes de classe implicites
Pour créer une méthode de classe, vous devez utiliser le décorateur
@classmethod
. Cette méthode peut être appelée directement à partir de la classe, pas à partir de ses instances, et accepte la classe comme premier argument (généralement appelé
cls
, pas
self
).
Cependant, il existe deux méthodes de classe implicites dans le modèle de données Python:
__new__
et
__init_subclass__
. Ils fonctionnent exactement comme s'ils étaient décorés de
@classmethod
sauf qu'ils ne le sont pas. (
__new__
crée de nouvelles instances d'une classe,
__init_subclass__
est un hook qui est appelé lorsqu'une classe dérivée est créée.)
class Foo: def __new__(cls, *args, **kwargs): print(cls) return super().__new__( cls, *args, **kwargs ) Foo()
Gestionnaires de contexte asynchrones
Si vous souhaitez qu'un gestionnaire de contexte suspende la coroutine à l'entrée ou à la sortie du contexte, vous devez utiliser des gestionnaires de contexte asynchrones. Au lieu d'appeler
m.__enter__()
et
m.__exit__()
Python attend respectivement
m.__aenter__()
et attend
m.__aexit__()
.
Les gestionnaires de contexte asynchrones doivent être utilisés avec async avec la syntaxe:
import asyncio class Slow: def __init__(self, delay): self._delay = delay async def __aenter__(self): await asyncio.sleep(self._delay / 2) async def __aexit__(self, *exception): await asyncio.sleep(self._delay / 2) async def main(): async with Slow(1): print('slow') loop = asyncio.get_event_loop() loop.run_until_complete(main())
Définition du gestionnaire de contexte asynchrone
Depuis Python 3.7,
contextlib
fournit le décorateur
asynccontextmanager
qui vous permet de définir le gestionnaire de contexte asynchrone de la même manière que le
contextmanager
:
import asyncio from contextlib import asynccontextmanager @asynccontextmanager async def slow(delay): half = delay / 2 await asyncio.sleep(half) yield await asyncio.sleep(half) async def main(): async with slow(1): print('slow') loop = asyncio.get_event_loop() loop.run_until_complete(main())
Pour les versions plus anciennes, vous pouvez utiliser
@asyncio_extras.async_contextmanager
.
Opérateur unaire plus
Il n'y a pas d'opérateur
++
en Python,
x += 1
est utilisé à la place. Cependant, même
++x
est toujours une syntaxe valide (mais pas
x++
).
Le hic est que Python a l'opérateur unaire plus, et
++x
est en fait
x.__pos__().__pos__()
. Nous pouvons abuser de ce fait et faire fonctionner ++ comme incrément (non recommandé cependant):
class Number: def __init__(self, value): self._value = value def __pos__(self): return self._Incrementer(self) def inc(self): self._value += 1 def __str__(self): return str(self._value) class _Incrementer: def __init__(self, number): self._number = number def __pos__(self): self._number.inc() x = Number(4) print(x)
L'objet magicmock
L'objet
MagicMock
vous permet d'en obtenir n'importe quel attribut ou d'appeler n'importe quelle méthode. Une nouvelle maquette sera retournée lors de cet accès. De plus, vous obtenez le même objet factice si vous accédez au même attribut (ou appelez la même méthode):
>>> from unittest.mock import MagicMock >>> m = MagicMock() >>> a = ma >>> b = mb >>> a is ma True >>> mx() is mx() True >>> mx() <MagicMock name='mock.x()' id='139769776427752'>
Cela fonctionnera évidemment avec l'accès séquentiel aux attributs de n'importe quelle profondeur. Les arguments de méthode sont cependant ignorés:
>>> mabcd <MagicMock name='mock.abcd' id='139769776473480'> >>> mabcd <MagicMock name='mock.abcd' id='139769776473480'> >>> mx().y().z() <MagicMock name='mock.x().y().z()' id='139769776450024'> >>> mx(1).y(1).z(1) <MagicMock name='mock.x().y().z()' id='139769776450024'>
Une fois que vous avez défini une valeur pour un attribut, il ne renvoie plus de maquette:
>>> mabcd = 42 >>> mabcd 42 >>> mxreturn_value.y.return_value = 13 >>> mx().y() 13
Cependant, cela ne fonctionne pas avec
m[1][2]
. La raison en est que l'accès aux objets n'est pas traité spécialement par
MagicMock
, c'est simplement un appel de méthode:
>>> m[1][2] = 3 >>> m[1][2] <MagicMock name='mock.__getitem__().__getitem__()' id='139769776049848'> >>> m.__getitem__.return_value.__getitem__.return_value = 50 >>> m[1][2] 50