C'est une nouvelle sélection de trucs et astuces sur Python et la programmation de mon canal Telegram @pythonetc.
←
Publications précédentesbreak 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'))
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'))
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'))
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