Tipps und Tricks von meinem Telegramm-Kanal @pythonetc, Mai 2019



Es ist eine neue Auswahl von Tipps und Tricks zu Python und Programmierung von meinem Telegramm-Kanal @pythonetc.

Frühere Veröffentlichungen


break 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')) # [3] print(load_data_orig('4')) 4 

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')) # [3] print(load_data.orig('4')) # 4 

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')) # [3] print(load_data.__wrapped__('4')) # 4 

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

Source: https://habr.com/ru/post/de454648/


All Articles