
Ceci est la huitième sélection de conseils et de programmation Python de mon flux @pythonetc.
Sélections précédentes:
Deux méthodes de classe implicites
Pour créer une méthode de classe, vous devez utiliser le décorateur
@classmethod
. Ensuite, cette méthode peut être appelée directement à partir de la classe, et non à partir de ses instances, et elle prendra la classe comme premier argument (elle est généralement appelée par
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 comme s'ils étaient également décorés à l'aide de
@classmethod
, bien que ce ne soit pas le cas (
__new__
crée de nouvelles instances de la classe, et
__init_subclass__
est le hook qui est appelé lorsque la 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 que le gestionnaire de contexte suspende la coroutine lors de l'entrée ou de la sortie du contexte, utilisez des gestionnaires asynchrones. Ensuite, au lieu d'appeler
m.__enter__()
et
m.__exit__()
Python attendra sur
m.__aenter__()
et
m.__aexit__()
respectivement.
Les gestionnaires de contexte asynchrones doivent être utilisés avec la syntaxe
async with
:
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 d'un gestionnaire de contexte asynchrone
À partir de Python 3.7,
contextlib
fournit un décorateur
asynccontextmanager
qui vous permet de définir un 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())
Dans les anciennes versions de la langue, vous pouvez utiliser
@asyncio_extras.async_contextmanager
.
Opérateur unaire plus
Python n'a pas d'opérateur
++
; à la place,
x += 1
. Mais en même temps, la syntaxe
++x
est valide (mais
x++
ne l'est plus).
L'astuce est que Python a un opérateur unaire plus, et
++x
est en fait
x.__pos__().__pos__()
. Cela peut être abusé et faire fonctionner
++
comme un incrément (mais je ne recommanderais pas de le faire):
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)
Objet MagicMock
L'objet
MagicMock
permet de prendre n'importe quel attribut et d'appeler n'importe quelle méthode. Avec cette méthode d'accès, une nouvelle maquette est retournée. De plus, vous obtenez le même objet stub 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'>
Évidemment, ce code fonctionnera avec un accès séquentiel aux attributs à n'importe quelle profondeur. Dans ce cas, les arguments des méthodes sont 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'>
Et si vous définissez une valeur pour un attribut, le talon ne renverra plus:
>>> mabcd = 42 >>> mabcd 42 >>> mxreturn_value.y.return_value = 13 >>> mx().y() 13
Cependant, cela ne fonctionne pas avec
m[1][2]
. Le fait est que
MagicMock
ne gère pas l'appel à l'élément, c'est juste 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