Trucs et astuces de ma chaîne Telegram @pythonetc, mai 2019



C'est une nouvelle sélection de trucs et astuces sur Python et la programmation de mon canal Telegram @pythonetc.

Publications précédentes


break instruction break supprime l'exception si elle est utilisée dans la clause finally même lorsque le bloc except n'est pas présenté:

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

Sortie:

 finally after while 

La même chose est vraie pour continue , mais elle ne peut pas être utilisée finally jusqu'à Python 3.8:

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


Vous pouvez ajouter des caractères Unicode dans un littéral de chaîne non seulement par son numéro, mais aussi par son nom.

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

Il est également compatible avec les chaînes f:

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


Il existe six méthodes magiques pour les objets Python qui définissent des règles de comparaison:

  • __lt__ pour <
  • __gt__ pour >
  • __le__ pour <=
  • __ge__ pour >=
  • __eq__ pour ==
  • __ne__ pour !=

Si certaines de ces méthodes ne sont pas définies ou renvoient NotImplemented , les règles suivantes s'appliquent:

  • a.__lt__(b) est le même que b.__gt__(a)
  • a.__le__(b) est le même que b.__ge__(a)
  • a.__eq__(b) est le même que not a.__ne__(b) (sachez que a et b ne sont pas échangés dans ce cas)

Cependant, a >= b et a != b n'impliquent pas automatiquement a > b . Le décorateur functools.total_ordering crée les six méthodes basées sur __eq__ et l'une des suivantes: __lt__ , __gt__ , __le__ ou __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') 


Parfois, vous souhaitez utiliser à la fois des versions décorées et non décorées d'une fonction. La façon la plus simple d'y parvenir est de renoncer à la syntaxe spéciale du décorateur (celle avec @ ) et de créer la fonction décorée manuellement:

 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 

Alternativement, vous pouvez écrire un autre décorateur, qui décore une fonction tout en préservant sa version d'origine dans l'attribut orig de la nouvelle:

 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 tous les décorateurs avec functools.wraps vous travaillez sont créés via functools.wraps vous pouvez utiliser l'attribut __wrapped__ pour accéder à la fonction non décorée:

 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 

Sachez cependant que cela ne fonctionne pas pour les fonctions décorées par plusieurs décorateurs: vous devez accéder à __wrapped__ pour chaque décorateur appliqué:

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

Sortie:

 [4] ['4'] '4' 

Le @saving_orig mentionné ci-dessus accepte un autre décorateur comme argument. Et si ce décorateur pouvait être paramétré? Eh bien, comme le décorateur paramétré est une fonction qui renvoie un décorateur réel, ce cas est géré automatiquement:

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

Le décorateur @saving_orig ne fait pas vraiment ce que nous voulons s'il y a plus d'un décorateur appliqué à une fonction. Nous devons appeler orig pour chacun de ces décorateurs:

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

Sortie:

 [42] ['X'] 'X' 

Nous pouvons le corriger en prenant en charge un nombre arbitraire de décorateurs en tant 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"'))) 

Sortie:

 [42] 'X' 

Une autre solution consiste à rendre saving_orig suffisamment intelligent pour passer l' orig d'une fonction décorée à une autre:

 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 décorateur que vous écrivez devient trop compliqué, il peut être raisonnable de le transformer d'une fonction en classe avec la méthode __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 dernière ligne vous permet à la fois de nommer la classe avec un étui à chameau et de conserver le nom du décorateur dans l'étui à serpent.

Au lieu de modifier la fonction décorée, vous pouvez créer une autre classe appelable pour renvoyer ses instances au lieu d'une fonction:

 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 

Voir l'intégralité du code ici

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


All Articles