Compilación @Pythonetc, mayo de 2019



Esta es la undécima selección de consejos y programación de Python de mi feed @pythonetc.

Colecciones anteriores


La break bloquea una excepción si se aplica en un bloque finally , incluso si no hay un bloque except :

 for i in range(10): try: 1 / i finally: print('finally') break print('after try') print('after while') 

Resultado:

 finally after while 

Lo mismo es cierto para continue , sin embargo, esta expresión solo se puede usar finally hasta Python versión 3.8:

 SyntaxError: 'continue' not supported inside 'finally' clause 


Puede agregar caracteres Unicode a los literales de cadena no solo por sus índices, sino también por su nombre.

 >>> '\N{EM DASH}' '—' >>> '\u2014' '—' 

Este método también es compatible con líneas f:

 >>> width = 800 >>> f'Width \N{EM DASH} {width}' 'Width — 800' 


Hay 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 aplican las siguientes reglas:

  • a.__lt__(b) lo mismo que b.__gt__(a)
  • a.__le__(b) lo mismo que b.__ge__(a)
  • a.__eq__(b) lo mismo que not a.__ne__(b) (tenga en cuenta que en este caso b no cambiaron de lugar)

Sin embargo, las condiciones a >= b a != b no significan automáticamente que a > b . El decorador functools.total_ordering crea los seis métodos basados ​​en __eq__ y uno de estos: __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') 


Algunas veces necesita usar las versiones decoradas y no decoradas de la función. La forma más fácil de lograr esto es si no utiliza una sintaxis de decoración especial (con el símbolo @ ) y crea una función de decoración 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 

O puede escribir un decorador que decore la función, conservando la versión original en su atributo original:

 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 sus decoradores se crean a través de functools.wraps , puede usar el atributo __wrapped__ para acceder a una 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 

Pero recuerde que este enfoque no funciona para funciones que están decoradas con más de un decorador: deberá referirse a __wrapped__ cada uno de los decoradores aplicados:

 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"'))) 

Resultado:

 [4] ['4'] '4' 

El decorador @saving_orig mencionado anteriormente toma otro decorador como argumento. ¿Y si será parametrizado? Dado que un decorador parametrizado es una función que devuelve un decorador real, esta situación se procesa 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 no hará lo que queremos si se aplican múltiples decoradores a la función. Luego, para cada uno de ellos, debe llamar a orig :

 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"'))) 

Resultado:

 [42] ['X'] 'X' 

Esto se puede saving_orig admitiendo un número arbitrario de decoradores como argumentos para 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"'))) 

Resultado:

 [42] 'X' 

Hay otra solución: hacer que saving_orig pase 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) 

Cuando el decorador se vuelve demasiado complicado, es mejor convertirlo 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 nombrar la clase en el caso Camel y guardar el nombre del decorador en el caso Snake.

En lugar de convertir la función decorada, puede crear otra clase llamada y devolver instancias de la función en lugar de la 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 

Todo el código está disponible aquí.

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


All Articles