Ceci est la onzième sélection de conseils et de programmation Python de mon flux @pythonetc.
←
Collections précédentesL'
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'))
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'))
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'))
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.