
Esta es la octava selección de consejos y programación de Python de mi feed @pythonetc.
Selecciones anteriores:
Dos métodos de clase implícitos.
Para crear un método de clase, debe usar el decorador
@classmethod
. Entonces, este método se puede invocar directamente desde la clase, y no desde sus instancias, y tomará la clase como primer argumento (generalmente se llama por
cls
, no por
self
).
Sin embargo, hay dos métodos de clase implícitos en el modelo de datos de Python:
__new__
y
__init_subclass__
. Funcionan como si también estuvieran decorados con
@classmethod
, aunque este no es el caso (
__new__
crea nuevas instancias de la clase, y
__init_subclass__
es el gancho que se llama cuando se crea la clase derivada).
class Foo: def __new__(cls, *args, **kwargs): print(cls) return super().__new__( cls, *args, **kwargs ) Foo()
Administradores de contexto asíncrono
Si desea que el administrador de contexto pause la rutina al entrar o salir del contexto, use administradores asincrónicos. Luego, en lugar de llamar a
m.__enter__()
y
m.__exit__()
Python esperará en
m.__aenter__()
y
m.__aexit__()
respectivamente.
Los gestores de contexto asíncronos deben usarse con el
async with
sintaxis:
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())
Definición de un administrador de contexto asíncrono
Comenzando con Python 3.7,
contextlib
proporciona un decorador
asynccontextmanager
que le permite definir un administrador de contexto asíncrono de la misma manera que el
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())
En versiones anteriores del lenguaje, puede usar
@asyncio_extras.async_contextmanager
.
Operador unario plus
Python no tiene un operador
++
; en cambio,
x += 1
. Pero al mismo tiempo, la sintaxis
++x
es válida (pero
x++
ya no lo es).
El truco es que Python tiene un operador unario más, y
++x
es en realidad
x.__pos__().__pos__()
. Se puede abusar de esto y hacer que
++
funcione como un incremento (pero no recomendaría hacer esto):
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)
Objeto MagicMock
El objeto
MagicMock
permite tomar cualquier atributo y llamar a cualquier método. Con este método de acceso, se devuelve un nuevo simulacro. Además, obtiene el mismo objeto de código auxiliar si accede al mismo atributo (o llama al mismo método):
>>> 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'>
Obviamente, este código funcionará con acceso secuencial a los atributos a cualquier profundidad. En este caso, los argumentos de los métodos son ignorados:
>>> 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'>
Y si establece un valor para cualquier atributo, el código auxiliar ya no devolverá:
>>> mabcd = 42 >>> mabcd 42 >>> mxreturn_value.y.return_value = 13 >>> mx().y() 13
Sin embargo, esto no funciona con
m[1][2]
. El hecho es que
MagicMock
no maneja la llamada al elemento, es solo una llamada al método:
>>> 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