Python Adventskalender 2019 Tag 23 Artikel.
Kennen Sie eine Bibliothek namens Traitlets? Ich habe mir vor einiger Zeit die Implementierung des Jupyter-Notebooks angesehen und von seiner Existenz erfahren. Es scheint eine Bibliothek zu sein, die ursprünglich geboren und von der Entwicklung von IPython getrennt wurde. Wenn Sie also ein IPython- oder Jupyter-Notebook verwenden, bin ich Ihnen zu Dank verpflichtet und verwende es, ohne etwas zu wissen.
Dies liegt daran, dass die Konfigurationsdateien für das Jupiter-Notebook und das IPython mithilfe von Traitlets geladen werden, z. B. durch Bearbeiten von "jupyter_notebook_config.py" [^ 1] und "ipython_config.py" [^ 2].
#Basic auskommentiert
c.Application.log_datefmt = '%Y-%m-%d %H:%M:%S'
Möglicherweise gibt es Personen, die die Beschreibung wie folgt gesehen oder bearbeitet haben.
[^ 1]: Das generierte Verzeichnis befindet sich in dem Verzeichnis, auf das config:
(im Grunde genommen .jupyter direkt unter der Startseite) verweist und das in
jupyter --path` eingegeben wird. Wenn Sie es nicht eingestellt haben, können Sie es mit "jupyter notebook --generate-config" generieren.
[^ 2]: Wenn sich unter dem Verzeichnis, das in "ipython find" angezeigt wird, ein Profilverzeichnis (beginnend mit "profile") befindet, ist es dort vorhanden. Wenn Sie es nicht erstellt haben, können Sie es mit "ipython profile create" erstellen (wenn Sie keinen Namen angeben).
Tatsächlich ist das mysteriöse "c", das hier erscheint, eine Instanz der Config-Klasse von Traitlets. Und wenn Sie "c.Application.log_datefmt = ..." schreiben, wird dies als log_datefmt der Anwendungsklasse bezeichnet, die von der konfigurierbaren Klasse verwaltet wird, die die Konfigurationsdatei liest, in der diese Beschreibung geschrieben ist (tatsächlich die Anwendungsklasse, die sie bündelt?). Mitgliedsvariablen wird der Wert "..." zugewiesen.
Tatsächlich ist die NotebookApp-Klasse (Definition), von der gesagt werden kann, dass sie die Kernklasse von Jupy ist. Definition](https://github.com/jupyter/jupyter_core/blob/b129001bb88abf558df5dfab791a5aeeba79e25c/jupyter_core/application.py#L61)), aber diese JupyterApp-Klasse erbt die Application-Klasse von Merkmalen (hier).
Ich habe ein wenig nachgeforscht, um welche Art von Bibliothek es sich bei diesen Merkmalen handelt, aber ich konnte sie nicht richtig verstehen, weil die Dokumentation so chaotisch war. Deshalb habe ich diesen Artikel geschrieben, um die Existenz zu verbreiten.
Das Folgende ist ein mit Übersetzung und Inhalt verwässertes Dokument von How to use.
Da der sogenannte "Typ" in Python dynamisch bestimmt wird, ist es möglich, Klassenattributen (Mitgliedsvariablen) beliebige Werte zuzuweisen, sofern nicht ausdrücklich angegeben. Ich verstehe, dass eine der von Traitlets bereitgestellten Rollen darin besteht, die Attribute dieser Klasse richtig einzugeben, damit detailliertere Überprüfungsfunktionen einfach aufgerufen werden können. Tatsächlich scheint das Lesen der Konfigurationsdatei, die auch in der Implementierung von Jupyter und Ipython verwendet wird, die Hauptfunktion zu sein ...
Definieren Sie die HasTraits-Unterklasse Foo wie folgt:
from traitlets import HasTraits, Int
class Foo(HasTraits):
bar = Int()
Es gibt einer Klasse namens Foo ein Attribut namens bar, genau wie einer regulären Klasse. Im Gegensatz zu regulären Klassenvariablen ist dies jedoch ein spezielles Attribut namens ** Merkmal **. Insbesondere ist dieser Balken eine Art von Merkmal namens int, und wie der Name schon sagt, ist es ein Merkmal, das ganzzahlige Werte speichert.
Lassen Sie uns tatsächlich eine Instanz erstellen:
> foo = Foo(bar=3)
> print(foo.bar)
3
> foo.bar = 6
> print(foo.bar)
6
Und foo hat ein Attribut namens bar des ganzzahligen Werts "type", und es ist möglich, den Wert zu ändern.
Was ist dagegen mit einer Zeichenfolge?
> foo = Foo(bar="3")
TraitError: The 'bar' trait of a Foo instance must be an int, but a value of '3' <class 'str'> was specified.
Sie sollten eine Fehlermeldung erhalten, die besagt, dass eine solche Typzuweisung falsch ist. Dadurch entfällt die Notwendigkeit, die Typprüfung selbst durchzuführen, z. B. mit __setattr__
.
Hier wird nur der Int-Typ vorgestellt, es sind jedoch einige verfügbar, einschließlich Containertypen wie List, und Sie können Ihren eigenen definieren. Weitere Informationen finden Sie in der Dokumentation.
Mit dem Traitlet können Sie Standardwerte während der Instanziierung dynamisch angeben. Übrigens wird im vorherigen Merkmalstyp "Int" 0 als Standardwert festgelegt, wenn nichts angegeben ist:
> foo = Foo()
> print(foo.bar)
0
Im folgenden Beispiel wird das heutige Datum in einem Merkmal gespeichert, das heute heißt.
from traitlets import Tuple
class Foo(HasTraits):
today = Tuple(Int(), Int(), Int())
@default("today")
def default_today(self):
import datetime
today_ = datetime.datetime.today()
return (today_.year, today_.month, today_.day)
> foo = Foo()
> foo.today
(2019, 12, 22)
Übrigens, wie Sie auf einen Blick aus dem Code sehen können, ist der heutige Merkmalstyp ein Tupel, das aus drei ganzzahligen Werten besteht. Beachten Sie, dass der Standardwert von "Tupel" "()" ist. Wenn Sie also zum Zeitpunkt der Instanziierung weder den Standardwert noch den Wert angeben, ist der Typ unterschiedlich und es tritt ein Zuordnungsfehler auf.
Ich denke, dies ist wahrscheinlich gleichbedeutend mit dem Schreiben des Folgenden, aber das erstere ist aus der Perspektive der logischen Isolation deutlich leichter zu lesen:
class Foo(HasTraits):
today = Tuple(Int(), Int(), Int())
def __init__(self):
import datetime
today_ = datetime.datetime.today()
self.today = (today_.year, today_.month, today_.day)
Als nächstes werden wir die Funktion zur Überprüfung der Wertzuweisung einführen. Selbst wenn eine Typprüfung möglich wird, weiß ich nicht, ob der Wert angemessen ist. Wenn zum Beispiel ein Merkmal (sagen wir, es repräsentiert die Anzahl von etwas) eine nicht negative ganze Zahl sein muss, reicht "Int" allein nicht aus.
In diesem Fall kann diese Einschränkung von einem anderen Merkmal abhängen. Wenn Sie beispielsweise einen Monat haben, in dem ein Monat und ein Tag einen Tag speichern, hängt der zulässige Bereich der Tage vom Wert des Monats ab. Es ist "validieren", das eine solche Prüfung durchführt.
Hier werden wir es erst im November und Dezember umsetzen.
from traitlets import validate
class Foo(HasTraits):
today = Tuple(Int(), Int(), Int())
@validate('today')
def _valid_month_day(self, proposal):
year, month, day = proposal['value']
if month not in [11,12]:
raise TraitError('invalid month')
if month == 11 and day not in range(1,31):
raise TraitError('invalid day')
elif month == 12 and day not in range(1,32):
raise TraitError('invalid day')
return proposal['value']
> foo = Foo(today=(2000,12,1))
> foo.today
(2000, 12, 1)
> foo.today = (2000,13,1)
TraitError: invalid month
> foo.today = (2000,12,31)
> foo.today = (2000,12,32)
TraitError: invalid day
Wenn sich mehrere Merkmalsvariablen aufeinander beziehen, kann das Ändern eines Werts unterwegs zu einem Überprüfungsfehler führen. In solchen Fällen sollten Sie die Validierung überspringen, bis sich alle Merkmale geändert haben. Dies kann im Bereich "hold_trait_notifications" erreicht werden. Schauen wir uns das folgende Beispiel an:
class Foo(HasTraits):
a, b = Int(), Int()
@validate('a')
def _valid_a(self, proposal):
if proposal['value'] * self.b <= 0:
raise TraitError("invalid a")
return proposal['value']
@validate('b')
def _valid_b(self, proposal):
if proposal['value'] * self.a <= 0:
raise TraitError("invalid b")
return proposal['value']
> foo = Foo(a=1,b=1)
> foo.a = -1
> foo.b = -1
TraitError: invalid a
> with foo.hold_trait_notifications():
> foo.a = -1
> foo.b = -1
> print(foo.a, foo.b)
-1 -1
In diesem Beispiel werden zwei Merkmale a und b definiert, deren Produkt jedoch nicht negativ sein muss. Selbst wenn beide Werte negativ sind, besteht diese Überprüfung. Wenn jedoch nur einer geändert wird, tritt ein Überprüfungsfehler auf. Wenn Sie andererseits die Werte der Merkmale a und b in "hold_trait_notifications" ändern, wird die Verzögerungsüberprüfung am Ende dieses Bereichs durchgeführt, sodass Sie sich darüber keine Gedanken machen müssen.
Abschließend möchte ich die Funktion zur Implementierung des Beobachtermusters in das Merkmal einführen. Dies kann etwas bewirken, wenn der Wert des angegebenen Merkmals neu geschrieben wird (Ereignis tritt ein).
class Foo(HasTraits):
bar = Int()
@observe('bar')
def _observe_bar(self, change):
...
Ist kein vollständiger Code mehr, aber wenn sich der Wert des Balkenmerkmals ändert, wird eine Funktion namens _observe_bar ausgeführt.
Wie oben erwähnt, ist der Inhalt wahnsinnig dünn, aber verzeihen Sie mir, dass ich ihn zum ersten Mal in einem Programmiersprachen-System gepostet habe. Wenn Sie mit Traitlets vertraut sind, bereichern Sie bitte die zu einsamen Dokumente und Beispiele ...
Recommended Posts