Es una nueva selección de consejos y trucos sobre Python y la programación de mi canal de Telegram @pythonetc.
←
Publicaciones anterioresbreak
instrucción
break
suprime la excepción si se usa en la cláusula final incluso cuando el bloque
except
no se presenta:
for i in range(10): try: 1 / i finally: print('finally') break print('after try') print('after while')
Salida:
finally after while
Lo mismo es cierto para
continue
, sin embargo, no se puede usar
finally
hasta Python 3.8:
SyntaxError: 'continue' not supported inside 'finally' clause
Puede agregar caracteres Unicode en un literal de cadena no solo por su número, sino también por su nombre.
>>> '\N{EM DASH}' '—' >>> '\u2014' '—'
También es compatible con cadenas f:
>>> width = 800 >>> f'Width \N{EM DASH} {width}' 'Width — 800'
Existen seis métodos mágicos para los objetos de Python que definen las reglas de comparación:
__lt__
para <
__gt__
para >
__le__
para <=
__ge__
para >=
__eq__
para ==
__ne__
for !=
Si alguno de estos métodos no está definido o devuelve
NotImplemented
, se
NotImplemented
las siguientes reglas:
a.__lt__(b)
es lo mismo que b.__gt__(a)
a.__le__(b)
es lo mismo que b.__ge__(a)
a.__eq__(b)
es lo mismo que not a.__ne__(b)
(tenga en cuenta que a
y b
no se intercambian en este caso)
Sin embargo,
a >= b
a != b
no implican automáticamente
a > b
. El decorador
functools.total_ordering
crea los seis métodos basados en
__eq__
y uno de los siguientes:
__lt__
,
__gt__
,
__le__
o
__ge__
.
from functools import total_ordering @total_ordering class User: def __init__(self, pk, name): self.pk = pk self.name = name def __le__(self, other): return self.pk <= other.pk def __eq__(self, other): return self.pk == other.pk assert User(2, 'Vadim') < User(13, 'Catherine')
En ocasiones, desea utilizar versiones decoradas y no decoradas de una función. La forma más fácil de lograrlo es renunciar a la sintaxis especial del decorador (la que tiene
@
) y crear la función decorada manualmente:
import json def ensure_list(f): def decorated(*args, **kwargs): result = f(*args, **kwargs) if isinstance(result, list): return result else: return [result] return decorated def load_data_orig(string): return json.loads(string) load_data = ensure_list(load_data_orig) print(load_data('3'))
Alternativamente, puede escribir otro decorador, que decora una función mientras conserva su versión original en el atributo
orig
del nuevo:
import json def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) decorated.orig = f return decorated return decorator def ensure_list(f): ... @saving_orig(ensure_list) def load_data(string): return json.loads(string) print(load_data('3'))
Si todos los decoradores con los que está trabajando se crean a través de
functools.wraps
, puede usar el atributo
__wrapped__
para acceder a la función sin decorar:
import json from functools import wraps def ensure_list(f): @wraps(f) def decorated(*args, **kwargs): result = f(*args, **kwargs) if isinstance(result, list): return result else: return [result] return decorated @ensure_list def load_data(string): return json.loads(string) print(load_data('3'))
Sin embargo, tenga en cuenta que no funciona para funciones que están decoradas por más de un decorador: debe acceder a
__wrapped__
para cada decorador aplicado:
def ensure_list(f): ... def ensure_ints(f): @wraps(f) def decorated(*args, **kwargs): result = f(*args, **kwargs) return [int(x) for x in result] return decorated @ensure_ints @ensure_list def load_data(string): return json.loads(string) for f in ( load_data, load_data.__wrapped__, load_data.__wrapped__.__wrapped__, ): print(repr(f('"4"')))
Salida:
[4] ['4'] '4'
@saving_orig
mencionado anteriormente acepta otro decorador como argumento. ¿Qué pasa si ese decorador puede ser parametrizado? Bueno, dado que el decorador parametrizado es una función que devuelve un decorador real, este caso se maneja automáticamente:
import json from functools import wraps def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) decorated.orig = f return decorated return decorator def ensure_ints(*, default=None): def decorator(f): @wraps(f) def decorated(*args, **kwargs): result = f(*args, **kwargs) ints = [] for x in result: try: x_int = int(x) except ValueError: if default is None: raise else: x_int = default ints.append(x_int) return ints return decorated return decorator @saving_orig(ensure_ints(default=0)) def load_data(string): return json.loads(string) print(repr(load_data('["2", "3", "A"]'))) print(repr(load_data.orig('["2", "3", "A"]')))
El decorador
@saving_orig
realmente no hace lo que queremos si hay más de un decorador aplicado a una función. Tenemos que llamar a
orig
para cada decorador:
import json from functools import wraps def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) decorated.orig = f return decorated return decorator def ensure_list(f): ... def ensure_ints(*, default=None): ... @saving_orig(ensure_ints(default=42)) @saving_orig(ensure_list) def load_data(string): return json.loads(string) for f in ( load_data, load_data.orig, load_data.orig.orig, ): print(repr(f('"X"')))
Salida:
[42] ['X'] 'X'
Podemos solucionarlo al admitir un número arbitrario de decoradores como argumentos
saving_orig
:
def saving_orig(*decorators): def decorator(f): decorated = f for d in reversed(decorators): decorated = d(decorated) decorated.orig = f return decorated return decorator ... @saving_orig( ensure_ints(default=42), ensure_list, ) def load_data(string): return json.loads(string) for f in ( load_data, load_data.orig, ): print(repr(f('"X"')))
Salida:
[42] 'X'
Otra solución es hacer que
saving_orig
sea
saving_orig
suficientemente inteligente como para pasar el
orig
de una función decorada a otra:
def saving_orig(another_decorator): def decorator(f): decorated = another_decorator(f) if hasattr(f, 'orig'): decorated.orig = f.orig else: decorated.orig = f return decorated return decorator @saving_orig(ensure_ints(default=42)) @saving_orig(ensure_list) def load_data(string): return json.loads(string)
Si un decorador que está escribiendo se vuelve demasiado complicado, puede ser razonable transformarlo de una función a una clase con el método
__call__
class SavingOrig: def __init__(self, another_decorator): self._another = another_decorator def __call__(self, f): decorated = self._another(f) if hasattr(f, 'orig'): decorated.orig = f.orig else: decorated.orig = f return decorated saving_orig = SavingOrig
La última línea le permite a ambos nombrar la clase con el caso de camello y mantener el nombre del decorador en el caso de la serpiente.
En lugar de modificar la función decorada, puede crear otra clase invocable para devolver sus instancias en lugar de una función:
class CallableWithOrig: def __init__(self, to_call, orig): self._to_call = to_call self._orig = orig def __call__(self, *args, **kwargs): return self._to_call(*args, **kwargs) @property def orig(self): if isinstance(self._orig, type(self)): return self._orig.orig else: return self._orig class SavingOrig: def __init__(self, another_decorator): self._another = another_decorator def __call__(self, f): return CallableWithOrig(self._another(f), f) saving_orig = SavingOrig
Ver el código completo
aquí