Es ist eine neue Auswahl von Tipps und Tricks zu Python und Programmierung von meinem Telegramm-Kanal @pythonetc.
←
Frühere Veröffentlichungenbreak
Anweisung unterdrückt die Ausnahme, wenn sie in der
finally
Klausel verwendet wird, auch wenn der
except
nicht dargestellt wird:
for i in range(10): try: 1 / i finally: print('finally') break print('after try') print('after while')
Ausgabe:
finally after while
Das Gleiche gilt für
continue
, kann jedoch erst in Python 3.8
finally
:
SyntaxError: 'continue' not supported inside 'finally' clause
Sie können Unicode-Zeichen in einem Zeichenfolgenliteral nicht nur anhand ihrer Nummer, sondern auch anhand ihres Namens hinzufügen.
>>> '\N{EM DASH}' '—' >>> '\u2014' '—'
Es ist auch kompatibel mit F-Strings:
>>> width = 800 >>> f'Width \N{EM DASH} {width}' 'Width — 800'
Es gibt sechs magische Methoden für Python-Objekte, die Vergleichsregeln definieren:
__lt__
für <
__gt__
für >
__le__
für <=
__ge__
für >=
__eq__
für ==
__ne__
für !=
Wenn einige dieser Methoden nicht definiert sind oder
NotImplemented
, gelten die folgenden Regeln:
a.__lt__(b)
ist dasselbe wie b.__gt__(a)
a.__le__(b)
ist dasselbe wie b.__ge__(a)
a.__eq__(b)
ist dasselbe wie not a.__ne__(b)
(beachten Sie, dass a
und b
in diesem Fall nicht getauscht werden)
a >= b
und
a != b
implizieren jedoch nicht automatisch
a > b
. Der Dekorator
functools.total_ordering
erstellt alle sechs Methoden basierend auf
__eq__
und einer der folgenden Methoden:
__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öchten Sie sowohl dekorierte als auch nicht dekorierte Versionen einer Funktion verwenden. Der einfachste Weg, dies zu erreichen, besteht darin, auf die spezielle Decorator-Syntax (die mit
@
) zu verzichten und die dekorierte Funktion manuell zu 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'))
Alternativ können Sie einen anderen Dekorateur schreiben, der eine Funktion dekoriert, während die ursprüngliche Version im
orig
Attribut der neuen beibehalten wird:
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 Dekorateure, mit denen Sie arbeiten, über
functools.wraps
, können Sie mit dem Attribut
__wrapped__
auf die 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'))
Beachten Sie jedoch, dass dies nicht für Funktionen funktioniert, die von mehr als einem Dekorateur dekoriert wurden: Sie müssen für jeden angewendeten Dekorateur auf
__wrapped__
zugreifen:
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"')))
Ausgabe:
[4] ['4'] '4'
Das
@saving_orig
erwähnte
@saving_orig
akzeptiert einen anderen Dekorateur als Argument. Was ist, wenn dieser Dekorateur parametrisiert werden kann? Nun, da der parametrisierte Dekorator eine Funktion ist, die einen tatsächlichen Dekorator zurückgibt, wird dieser Fall automatisch behandelt:
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 wirklich das, was wir wollen, wenn mehr als ein Dekorator auf eine Funktion angewendet wird. Wir müssen
orig
für jeden solchen Dekorateur 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"')))
Ausgabe:
[42] ['X'] 'X'
Wir können das
saving_orig
beheben, indem wir eine beliebige Anzahl von Dekoratoren als
saving_orig
Argumente unterstützen:
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"')))
Ausgabe:
[42] 'X'
Eine andere Lösung besteht darin,
saving_orig
intelligent zu machen, dass
orig
von einer dekorierten Funktion an eine andere übergeben wird:
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 ein Dekorator, den Sie schreiben, zu kompliziert wird, kann es sinnvoll sein, ihn mit der Methode
__call__
von einer Funktion in eine Klasse
__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
In der letzten Zeile können Sie sowohl die Klasse mit dem Kamelkoffer benennen als auch den Namen des Dekorateurs im Schlangenkoffer aufbewahren.
Anstatt die dekorierte Funktion zu ändern, können Sie eine andere aufrufbare Klasse erstellen, um ihre Instanzen anstelle einer Funktion zurückzugeben:
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
Den gesamten Code finden Sie
hier