@ Pythonetc September 2018


Dies ist die vierte Sammlung von Python-Tipps und -Programmierungen aus meinem @ pythonetc-Feed .


Vorherige Auswahl:



Überschreiben und Überladen


Es gibt zwei Konzepte, die leicht zu verwechseln sind: Überschreiben und Überladen.


Das Überschreiben erfolgt, wenn eine untergeordnete Klasse eine Methode definiert, die bereits von den übergeordneten Klassen bereitgestellt wurde, und diese dadurch ersetzt. In einigen Sprachen muss die überschreibende Methode explizit markiert werden (in C # wird der override verwendet), und in einigen Sprachen erfolgt dies wie gewünscht ( @Override Annotation in Java). Python erfordert keinen speziellen Modifikator und bietet keine Standardkennzeichnung für solche Methoden (aus Gründen der Lesbarkeit verwendet jemand den benutzerdefinierten @override Dekorator, der nichts tut).


Überlastung ist eine andere Geschichte. Dieser Begriff bezieht sich auf eine Situation, in der mehrere Funktionen mit demselben Namen, aber unterschiedlichen Signaturen vorhanden sind. Überladen ist in Java und C ++ möglich und wird häufig verwendet, um Standardargumente bereitzustellen:


 class Foo { public static void main(String[] args) { System.out.println(Hello()); } public static String Hello() { return Hello("world"); } public static String Hello(String name) { return "Hello, " + name; } } 

Python unterstützt die Suche nach Funktionen nicht nach Signatur, sondern nur nach Namen. Natürlich können Sie Code schreiben, der die Typen und die Anzahl der Argumente explizit analysiert. Dies sieht jedoch umständlich aus, und diese Vorgehensweise wird am besten vermieden:


 def quadrilateral_area(*args): if len(args) == 4: quadrilateral = Quadrilateral(*args) elif len(args) == 1: quadrilateral = args[0] else: raise TypeError() return quadrilateral.area() 

Wenn Sie @overload benötigen, verwenden Sie das @overload mit dem @overload Dekorator:


 from typing import overload @overload def quadrilateral_area( q: Quadrilateral ) -> float: ... @overload def quadrilateral_area( p1: Point, p2: Point, p3: Point, p4: Point ) -> float: ... 

Automatische Belebung


collections.defaultdict können Sie ein Wörterbuch erstellen, das einen Standardwert zurückgibt, wenn der angeforderte Schlüssel fehlt (anstatt einen KeyError ). Um ein defaultdict zu erstellen defaultdict Sie nicht nur einen Standardwert, sondern eine Factory solcher Werte defaultdict .


Sie können also ein Wörterbuch mit einer praktisch unendlichen Anzahl verschachtelter Wörterbücher erstellen, mit dem Sie Konstrukte wie d[a][b][c]...[z] .


 >>> def infinite_dict(): ... return defaultdict(infinite_dict) ... >>> d = infinite_dict() >>> d[1][2][3][4] = 10 >>> dict(d[1][2][3][5]) {} 

Dieses Verhalten wird als "automatische Belebung" bezeichnet, ein Begriff, der von Perl stammt.


Instanziierung


Die Instanziierung von Objekten umfasst zwei wichtige Schritte. Zunächst wird die Methode __new__ von der Klasse aufgerufen, die ein neues Objekt erstellt und zurückgibt. Dann ruft Python die Methode __init__ , die den Anfangszustand dieses Objekts festlegt.


__init__ wird jedoch nicht aufgerufen, wenn __new__ ein Objekt zurückgibt, das keine Instanz der ursprünglichen Klasse ist. In diesem Fall könnte das Objekt von einer anderen Klasse erstellt werden, was bedeutet, dass __init__ bereits für das Objekt aufgerufen wurde:


 class Foo: def __new__(cls, x): return dict(x=x) def __init__(self, x): print(x) # Never called print(Foo(0)) 

__new__ bedeutet auch, dass Sie nicht dieselbe Klasse in __new__ mit dem regulären Konstruktor ( Foo(...) ) instanziieren sollten. Dies kann zu einer wiederholten Ausführung von __init__ oder sogar zu einer unendlichen Rekursion führen.


Unendliche Rekursion:


 class Foo: def __new__(cls, x): return Foo(-x) # Recursion 

Doppelte Ausführung __init__ :


 class Foo: def __new__(cls, x): if x < 0: return Foo(-x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x 

Der richtige Weg:


 class Foo: def __new__(cls, x): if x < 0: return cls.__new__(cls, -x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x 

Der Operator [] und Slices


In Python können Sie den Operator [] überschreiben, indem Sie die magische Methode __getitem__ definieren. So können Sie beispielsweise ein Objekt erstellen, das praktisch unendlich viele sich wiederholende Elemente enthält:


 class Cycle: def __init__(self, lst): self._lst = lst def __getitem__(self, index): return self._lst[ index % len(self._lst) ] print(Cycle(['a', 'b', 'c'])[100]) # 'b' 

Das Ungewöhnliche dabei ist, dass der Operator [] eine eindeutige Syntax unterstützt. Mit ihm können Sie nicht nur [2] , sondern auch [2:10] , [2:10:2] , [2::2] und sogar [:] . Die Semantik des Operators lautet: [start: stop: step]. Sie können ihn jedoch auf andere Weise verwenden, um benutzerdefinierte Objekte zu erstellen.


Aber wenn Sie __getitem__ mit dieser Syntax aufrufen, was wird es als Indexparameter erhalten? Genau aus diesem Grund gibt es Slice-Objekte.


 In : class Inspector: ...: def __getitem__(self, index): ...: print(index) ...: In : Inspector()[1] 1 In : Inspector()[1:2] slice(1, 2, None) In : Inspector()[1:2:3] slice(1, 2, 3) In : Inspector()[:] slice(None, None, None) 

Sie können sogar die Syntax von Tupeln und Slices kombinieren:


 In : Inspector()[:, 0, :] (slice(None, None, None), 0, slice(None, None, None)) 

slice macht nichts, speichert nur die Attribute start , stop und step .


 In : s = slice(1, 2, 3) In : s.start Out: 1 In : s.stop Out: 2 In : s.step Out: 3 

Asyncio Coroutine unterbrechen


Jede ausführbare Coroutine asyncio kann mit der Methode cancel() abgebrochen werden. In diesem Fall wird ein abgebrochener Fehler an die Coroutine gesendet. Infolgedessen werden diese und alle damit verbundenen Coroutinen unterbrochen, bis der Fehler abgefangen und unterdrückt wird.


CancelledError ist eine Unterklasse von Exception , was bedeutet, dass es versehentlich mit der Kombination try ... except Exception abgefangen werden kann, um "Fehler" abzufangen. Um einen Fehler für eine Coroutine sicher zu erkennen, müssen Sie Folgendes tun:


 try: await action() except asyncio.CancelledError: raise except Exception: logging.exception('action failed') 

Ausführungsplanung


Um die Ausführung eines Codes zu einem bestimmten Zeitpunkt zu asyncio erstellt asyncio normalerweise eine Aufgabe, die ausgeführt wird und await asyncio.sleep(x) :


 import asyncio async def do(n=0): print(n) await asyncio.sleep(1) loop.create_task(do(n + 1)) loop.create_task(do(n + 1)) loop = asyncio.get_event_loop() loop.create_task(do()) loop.run_forever() 

Das Erstellen einer neuen Aufgabe kann jedoch teuer sein, und Sie müssen dies nicht tun, wenn Sie keine asynchronen Vorgänge ausführen möchten (wie in meinem Beispiel do Funktion do ). Stattdessen können Sie die Funktionen loop.call_later und loop.call_at , mit denen Sie einen asynchronen Rückruf planen können:


 import asyncio def do(n=0): print(n) loop = asyncio.get_event_loop() loop.call_later(1, do, n+1) loop.call_later(1, do, n+1) loop = asyncio.get_event_loop() do() loop.run_forever() 

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


All Articles