Dies ist die elfte Auswahl an Python-Tipps und -Programmierungen aus meinem @ pythonetc-Feed.
←
Frühere SammlungenDie
break
blockiert eine Ausnahme, wenn sie in einem
finally
Block angewendet wird, auch wenn es keinen
except
:
for i in range(10): try: 1 / i finally: print('finally') break print('after try') print('after while')
Ergebnis:
finally after while
Gleiches gilt für
continue
, dieser Ausdruck kann jedoch nur bis zur Python-Version 3.8 verwendet werden:
SyntaxError: 'continue' not supported inside 'finally' clause
Sie können Zeichenfolgenliteralen Unicode-Zeichen nicht nur anhand ihrer Indizes, sondern auch anhand ihres Namens hinzufügen.
>>> '\N{EM DASH}' '—' >>> '\u2014' '—'
Diese Methode ist auch mit f-Linien kompatibel:
>>> width = 800 >>> f'Width \N{EM DASH} {width}' 'Width — 800'
Es gibt sechs "magische" Methoden für Python-Objekte, die die Vergleichsregeln definieren:
__lt__
für <
__gt__
für >
__le__
für <=
__ge__
für >=
__eq__
für ==
__ne__
für !=
Wenn eine dieser Methoden nicht definiert ist oder
NotImplemented
, gelten die folgenden Regeln:
a.__lt__(b)
das gleiche wie b.__gt__(a)
a.__le__(b)
das gleiche wie b.__ge__(a)
a.__eq__(b)
dasselbe wie not a.__ne__(b)
(beachten Sie, dass in diesem Fall a
und b
die Plätze not a.__ne__(b)
)
Die Bedingungen
a >= b
und
a != b
bedeuten jedoch nicht automatisch, dass
a > b
. Der Dekorator
functools.total_ordering
erstellt alle sechs Methoden basierend auf
__eq__
und einer davon:
__lt__
,
__gt__
,
__le__
oder
__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')
Manchmal müssen Sie sowohl die dekorierte als auch die nicht dekorierte Version der Funktion verwenden. Der einfachste Weg, dies zu erreichen, ist, wenn Sie keine spezielle Dekorationssyntax (mit dem
@
-Symbol) verwenden und manuell eine Dekorationsfunktion erstellen:
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'))
Oder Sie können einen Dekorateur schreiben, der die Funktion dekoriert, während die Originalversion in ihrem ursprünglichen Attribut erhalten
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'))
Wenn alle Ihre Dekorateure über
functools.wraps
, können Sie mit dem Attribut
__wrapped__
auf eine nicht dekorierte Funktion zugreifen:
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'))
__wrapped__
Sie jedoch daran, dass dieser Ansatz nicht für Funktionen funktioniert, die mit mehr als einem Dekorateur dekoriert sind: Sie müssen sich auf
__wrapped__
jedes angewendeten Dekorators beziehen:
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"')))
Ergebnis:
[4] ['4'] '4'
Der
@saving_orig
erwähnte
@saving_orig
Dekorateur nimmt einen anderen Dekorateur als Argument. Und ob es parametriert wird? Da ein parametrisierter Dekorator eine Funktion ist, die einen echten Dekorator zurückgibt, wird diese Situation automatisch verarbeitet:
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"]')))
Der
@saving_orig
Dekorator macht nicht das, was wir wollen, wenn mehrere Dekoratoren auf die Funktion angewendet werden. Dann müssen Sie für jeden von ihnen
orig
anrufen:
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"')))
Ergebnis:
[42] ['X'] 'X'
Dies kann
saving_orig
werden, indem eine beliebige Anzahl von Dekoratoren als Argumente für
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"')))
Ergebnis:
[42] 'X'
Es gibt noch eine andere Lösung:
saving_orig
orig
von einer dekorierten Funktion zur nächsten übergeben:
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)
Wenn der Dekorator zu kompliziert wird, konvertieren Sie ihn am besten mit der Methode
__call__
von einer Funktion in eine Klasse:
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
In der letzten Zeile können Sie die Klasse im Fall Camel benennen und den Namen des Dekorateurs im Fall Snake speichern.
Anstatt die dekorierte Funktion zu konvertieren, können Sie eine andere aufgerufene Klasse erstellen und Instanzen der Funktion anstelle der Funktion zurückgeben:
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
Der gesamte Code ist
hier verfügbar
.