@ Pythonetc Aug 2018



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

Vorherige Auswahl:



Fabrikmethode


Wenn Sie neue Objekte in __init__ , ist es zweckmäßiger, sie als Argumente zu übergeben und das Objekt mit der Factory-Methode zu erstellen. Dadurch wird die Geschäftslogik von der technischen Implementierung der Erstellung von Objekten getrennt.

In diesem Beispiel akzeptiert __init__ host und port als Argumente zum Erstellen einer Datenbankverbindung:

 class Query: def __init__(self, host, port): self._connection = Connection(host, port) 

Refactoring-Option:

 class Query: def __init__(self, connection): self._connection = connection @classmethod def create(cls, host, port): return cls(Connection(host, port)) 

Dieser Ansatz hat mindestens die folgenden Vorteile:

  • Die Bereitstellung ist einfach. In Tests können Sie eine Query(FakeConnection()) .
  • Eine Klasse kann so viele Factory-Methoden haben, wie Sie möchten. Sie können eine Verbindung nicht nur über host und port erstellen, sondern auch eine andere Verbindung klonen, die Konfigurationsdatei lesen, die Standardverbindung verwenden usw.
  • Ähnliche Factory-Methoden können in asynchrone Funktionen umgewandelt werden, was mit __init__ absolut unmöglich ist.

super oder weiter


Mit der Funktion super() können Sie auf die Basisklasse verweisen. Dies kann sehr nützlich sein, wenn die abgeleitete Klasse der Implementierung der Methode etwas hinzufügen möchte, anstatt sie vollständig zu überschreiben.

 class BaseTestCase(TestCase): def setUp(self): self._db = create_db() class UserTestCase(BaseTestCase): def setUp(self): super().setUp() self._user = create_user() 

Der Name Super bedeutet nichts „Super“. In diesem Zusammenhang bedeutet es "höher in der Hierarchie" (zum Beispiel wie im Wort "Superintendent"). Gleichzeitig verweist super() nicht immer auf die Basisklasse, sondern kann leicht eine untergeordnete Klasse zurückgeben. Es wäre also korrekter, den Namen next() , da die nächste Klasse gemäß dem MRO zurückgegeben wird.

 class Top: def foo(self): return 'top' class Left(Top): def foo(self): return super().foo() class Right(Top): def foo(self): return 'right' class Bottom(Left, Right): pass # prints 'right' print(Bottom().foo()) 

Denken Sie daran, dass super() unterschiedliche Ergebnisse liefern kann, je nachdem, woher die Methode ursprünglich aufgerufen wurde.

 >>> Bottom().foo() 'right' >>> Left().foo() 'top' 


Benutzerdefinierter Namespace zum Erstellen einer Klasse


Eine Klasse wird in zwei großen Schritten erstellt. Zunächst wird der Hauptteil der Klasse wie der Hauptteil einer Funktion ausgeführt. Im zweiten Schritt wird der resultierende Namespace (der von locals() ) von der Metaklasse verwendet (der Standardwert ist type ), um ein Objekt der Klasse zu erstellen.

 class Meta(type): def __new__(meta, name, bases, ns): print(ns) return super().__new__( meta, name, bases, ns ) class Foo(metaclass=Meta): B = 2 

Dieser Code zeigt {'__module__': '__main__', '__qualname__':'Foo', 'B': 3} .

Offensichtlich, wenn Sie etwas wie B = 2; B = 3 einführen B = 2; B = 3 B = 2; B = 3 , dann sieht die Metaklasse nur B = 3 , da nur dieser Wert in ns . Diese Einschränkung ergibt sich aus der Tatsache, dass die Metaklasse erst nach Ausführung des Körpers zu arbeiten beginnt.

Sie können jedoch in die Ausführungsprozedur eingreifen, indem Sie Ihren eigenen Namespace verschieben. Standardmäßig wird ein einfaches Wörterbuch verwendet. Sie können jedoch ein eigenes Objekt bereitstellen, das einem Wörterbuch ähnelt, wenn Sie die Methode __prepare__ aus der Metaklasse verwenden.

 class CustomNamespace(dict): def __setitem__(self, key, value): print(f'{key} -> {value}') return super().__setitem__(key, value) class Meta(type): def __new__(meta, name, bases, ns): return super().__new__( meta, name, bases, ns ) @classmethod def __prepare__(metacls, cls, bases): return CustomNamespace() class Foo(metaclass=Meta): B = 2 B = 3 

Ergebnis der Codeausführung:

 __module__ -> __main__ __qualname__ -> Foo B -> 2 B -> 3 

Somit ist enum.Enum vor Vervielfältigung geschützt.

matplotlib


matplotlib ist eine komplexe und flexible Python-Bibliothek für Diagramme. Viele Produkte unterstützen dies, einschließlich Jupyter und Pycharm. Hier ist ein Beispiel für das Rendern eines einfachen Fraktals mit matplotlib : https://repl.it/@VadimPushtaev/myplotlib (siehe Titelbild dieser Veröffentlichung).

Zeitzonenunterstützung


Python bietet eine leistungsstarke datetime zum Arbeiten mit Datums- und datetime . Es ist merkwürdig, dass datetime Objekte eine spezielle Schnittstelle zur Unterstützung von Zeitzonen haben (nämlich das tzinfo Attribut), aber dieses Modul unterstützt die erwähnte Schnittstelle nur begrenzt, sodass ein Teil der Arbeit anderen Modulen zugewiesen wird.

Der beliebteste von ihnen ist pytz . Tatsache ist jedoch, dass pytz die tzinfo Schnittstelle nicht vollständig tzinfo . Dies steht ganz am Anfang der pytz Dokumentation: „Diese Bibliothek unterscheidet sich von der dokumentierten Python-API für tzinfo-Implementierungen.“

Sie können keine pytz als tzinfo . Wenn Sie dies versuchen, riskieren Sie ein völlig verrücktes Ergebnis:

 In : paris = pytz.timezone('Europe/Paris') In : str(datetime(2017, 1, 1, tzinfo=paris)) Out: '2017-01-01 00:00:00+00:09' 

Beachten Sie den Offset +00: 09. Pytz sollte wie folgt verwendet werden:

 In : str(paris.localize(datetime(2017, 1, 1))) Out: '2017-01-01 00:00:00+01:00' 

Darüber hinaus müssen Sie nach arithmetischen Operationen eine normalize auf Ihre datetime Objekte datetime , um zu vermeiden, dass sich Offsets ändern (z. B. am Rand der Sommerzeit).

 In : new_time = time + timedelta(days=2) In : str(new_time) Out: '2018-03-27 00:00:00+01:00' In : str(paris.normalize(new_time)) Out: '2018-03-27 01:00:00+02:00' 

Wenn Sie Python 3.6 haben, wird in der Dokumentation empfohlen, dateutil.tz anstelle von pytz . Diese Bibliothek ist vollständig kompatibel mit tzinfo . Sie kann als Attribut übergeben werden und Sie müssen normalize nicht verwenden. Es stimmt, es funktioniert langsamer.

Wenn Sie wissen möchten, warum pytz die datetime API nicht unterstützt, oder weitere Beispiele datetime möchten, lesen Sie diesen Artikel.

Magic StopIteration


Bei jedem Aufruf von next(x) ein neuer Wert vom Iterator x bis eine Ausnahme ausgelöst wird. Wenn sich herausstellt, dass es sich um StopIteration , ist der Iterator erschöpft und kann keine Werte mehr bereitstellen. Wenn der Generator iteriert wird, wird am Ende des Körpers automatisch StopIteration :

 >>> def one_two(): ... yield 1 ... yield 2 ... >>> i = one_two() >>> next(i) 1 >>> next(i) 2 >>> next(i) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 

StopIteration kann automatisch von Tools verarbeitet werden, die next aufrufen:

 >>> list(one_two()) [1, 2] 

Das Problem ist jedoch, dass jede offensichtlich nicht erwartete StopIteration, die im Generatorkörper aufgetreten ist, stillschweigend als Zeichen für das Ende des Generators akzeptiert wird und nicht wie jede andere Ausnahme als Fehler:

 def one_two(): yield 1 yield 2 def one_two_repeat(n): for _ in range(n): i = one_two() yield next(i) yield next(i) yield next(i) print(list(one_two_repeat(3))) 

Hier ist die letzte yield ein Fehler: Eine ausgelöste StopIteration Ausnahme stoppt die Iteration der list(...) . Wir erhalten das Ergebnis [1, 2] . In Python 3.7 hat sich dieses Verhalten jedoch geändert. Alien StopIteration durch StopIteration ersetzt:

 Traceback (most recent call last): File "test.py", line 10, in one_two_repeat yield next(i) StopIteration The above exception was the direct cause of the following exception: Traceback (most recent call last): File "test.py", line 12, in <module> print(list(one_two_repeat(3))) RuntimeError: generator raised StopIteration 

Sie können __future__ import generator_stop , um dasselbe Verhalten seit Python 3.5 zu aktivieren.

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


All Articles