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