
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)
__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)
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])
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()