@ Pythonetc-Zusammenstellung, Mai 2019



Dies ist die elfte Auswahl an Python-Tipps und -Programmierungen aus meinem @ pythonetc-Feed.

Frühere Sammlungen


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

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

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

__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 .

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


All Articles