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- ay- bno 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í