Auswahl @pythonetc, September 2018

Published on October 03, 2018

Auswahl @pythonetc, September 2018


    Dies ist die vierte Auswahl von Python-Tipps und -Programmierungen aus dem @pythonetc- Kanal meines Autors .


    Vorherige Auswahl:



    Override und Overload


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


    Das Überschreiben geschieht, wenn eine untergeordnete Klasse eine Methode definiert, die bereits von den übergeordneten Klassen bereitgestellt wird, und diese dadurch ersetzt. In einigen Sprachen muss die überschreibende Methode explizit markiert werden (der Modifikator wird in C # verwendet override). In einigen Sprachen erfolgt dies nach Belieben (Anmerkung @Overridein Java). Python erfordert keinen speziellen Modifikator und bietet keine Standardkennzeichnung für solche Methoden (aus Gründen der Lesbarkeit verwendet jemand einen benutzerdefinierten Dekorateur, @overrideder nichts unternimmt).


    Überladen ist eine andere Geschichte. Dieser Begriff bezieht sich auf den Fall, dass es mehrere Funktionen mit demselben Namen, jedoch mit unterschiedlichen Signaturen gibt. In Java und C ++ ist ein Überladen möglich. Es 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 keine Suchfunktionen nach Signatur, nur nach Namen. Natürlich können Sie Code schreiben, der die Typen und die Anzahl der Argumente explizit analysiert, aber es sieht 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 Hinweise zur typingEingabe benötigen, verwenden Sie das Dekorationsmodul @overload:


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

    Autovivification


    collections.defaultdictMit dieser Option können Sie ein Wörterbuch erstellen, das den Standardwert zurückgibt, wenn der angeforderte Schlüssel fehlt (anstatt ihn zu verwerfen KeyError). Zum Erstellen müssen defaultdictSie nicht nur einen Standardwert, sondern eine Factory mit solchen Werten bereitstellen.


    So können Sie ein Wörterbuch mit einer praktisch unbegrenzten Anzahl verschachtelter Wörterbücher erstellen, mit denen Sie Konstrukte wie das verwenden können 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 "Autovivification" bezeichnet, ein Begriff, der von Perl stammt.


    Instanziierung


    Das Instanziieren von Objekten umfasst zwei wichtige Schritte. Zunächst __new__wird eine Methode aus der Klasse aufgerufen , die ein neues Objekt erstellt und zurückgibt. Anschließend ruft Python die Methode auf __init__, die den Anfangszustand dieses Objekts festlegt.


    Jedoch __init__nicht , wenn die aufgerufen wird , __new__gibt ein Objekt , das nicht die ursprüngliche Instanz ist. In diesem Fall könnte das Objekt von einer anderen Klasse erstellt worden sein und wurde daher __init__bereits für das Objekt aufgerufen:


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

    Dies bedeutet auch, dass Sie nicht dieselbe Klasse __new__mit dem regulären Konstruktor ( Foo(...)) instanziieren sollten . Dies kann zu einer erneuten Ausführung __init__oder sogar zu einer endlosen 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

    Operator [] und Slices


    In Python können Sie einen Operator überschreiben, []indem Sie eine magische Methode definieren __getitem__. Sie können beispielsweise ein Objekt erstellen, das praktisch unendlich viele doppelte 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. Damit können Sie nicht nur [2], aber auch [2:10], [2:10:2], [2::2]und sogar [:]. Die Operatorsemantik lautet: [start: stop: step]. Sie können sie jedoch auch auf andere Weise verwenden, um benutzerdefinierte Objekte zu erstellen.


    Aber wenn Sie mit dieser Syntax aufrufen __getitem__, was wird es als Indexparameter erhalten? Dafür sind Slice-Objekte da.


    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 Syntaxen von Tupeln und Slices kombinieren:


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

    slicetut nichts, speichert nur die Attribute start, stopund step.


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

    Asyncio-Unterbrechung


    Jede gespielte Coroutine asynciokann mit der Methode unterbrochen werden cancel(). In diesem Fall wird Korutin gesendet CancelledError, wodurch dieses und alle zugehörigen Korutins unterbrochen werden, bis der Fehler abgefangen und unterdrückt wird.


    CancelledError- Eine Unterklasse Exception, die bedeutet, dass sie versehentlich mit einer Kombination abgefangen werden kann try ... except Exception, die "alle Fehler" abfängt. Um eine Coroutine sicher zu fangen, müssen Sie dies 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 planen asyncio, wird normalerweise eine Aufgabe erstellt, die Folgendes ausführt 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 durchführen möchten (wie in der Funktion doin meinem Beispiel). Stattdessen können Sie die Funktionen nutzen loop.call_laterund loop.call_atdass können Sie einen Anruf asynchrone Rückrufe planen:


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