@Pythonetc compilation, mai 2019



Ceci est la onzième sélection de conseils et de programmation Python de mon flux @pythonetc.

Collections précédentes


L' break bloque une exception si elle est appliquée dans un bloc finally , même s'il n'y a pas de bloc except :

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

Résultat:

 finally after while 

La même chose est vraie pour continue , cependant cette expression ne peut être utilisée que jusqu'à la version 3.8 de Python:

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


Vous pouvez ajouter des caractères Unicode aux littéraux de chaîne non seulement par leurs index, mais aussi par leur nom.

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

Cette méthode est également compatible avec les lignes f:

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


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

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

Si l'une de ces méthodes n'est pas définie ou renvoie NotImplemented , les règles suivantes s'appliquent:

  • a.__lt__(b) identique à b.__gt__(a)
  • a.__le__(b) identique à b.__ge__(a)
  • a.__eq__(b) le même que not a.__ne__(b) (notez que dans ce cas a et b n'ont pas changé de place)

Cependant, les conditions a >= b et a != b ne signifient pas automatiquement que a > b . Le décorateur functools.total_ordering crée les six méthodes basées sur __eq__ et l'une d'elles: __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 devez utiliser les versions décorée et non décorée de la fonction. La façon la plus simple d'y parvenir est de ne pas utiliser de syntaxe de décoration spéciale (avec le symbole @ ) et de créer une fonction de décoration 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 

Ou vous pouvez écrire un décorateur qui décore la fonction, tout en conservant la version originale dans son attribut orig :

 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 vos décorateurs sont créés via functools.wraps , vous pouvez utiliser l'attribut __wrapped__ pour accéder à une 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 

Mais rappelez-vous que cette approche ne fonctionne pas pour les fonctions décorées avec plusieurs décorateurs: vous devez vous référer à __wrapped__ chacun des décorateurs appliqués:

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

Résultat:

 [4] ['4'] '4' 

Le décorateur @saving_orig mentionné ci-dessus prend un autre décorateur comme argument. Et si elle sera paramétrée? Puisqu'un décorateur paramétré est une fonction qui renvoie un vrai décorateur, cette situation est traitée 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 @saving_orig pas ce que nous voulons si plusieurs décorateurs sont appliqués à la fonction. Ensuite, pour chacun d'eux, vous devez appeler 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"'))) 

Résultat:

 [42] ['X'] 'X' 

Cela peut être saving_orig en prenant en charge un nombre arbitraire de décorateurs comme arguments pour 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"'))) 

Résultat:

 [42] 'X' 

Il existe une autre solution: faites en saving_orig que saving_orig passe 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) 

Lorsque le décorateur devient trop compliqué, il est préférable de le convertir 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 de nommer la classe dans le boîtier Camel et d'enregistrer le nom du décorateur dans le boîtier Snake.

Au lieu de convertir la fonction décorée, vous pouvez créer une autre classe appelée et renvoyer des instances de la fonction au lieu de la 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 

Tout le code est disponible ici.

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


All Articles