Consejos y trucos de mi canal de Telegram @pythonetc, mayo de 2019



Es una nueva selección de consejos y trucos sobre Python y la programación de mi canal de Telegram @pythonetc.

Publicaciones anteriores


break 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')) # [3] print(load_data_orig('4')) 4 

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')) # [3] print(load_data.orig('4')) # 4 

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')) # [3] print(load_data.__wrapped__('4')) # 4 

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í

Source: https://habr.com/ru/post/454648/


All Articles