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