[PYTHON] Technologie qui prend en charge Jupyter: Tralets (histoire d'essayer de déchiffrer)

Article du calendrier de l'Avent 2019 Python 23.

introduction

Connaissez-vous une bibliothèque appelée traitlets? Je regardais l'implémentation de jupyter notebook il y a quelque temps et j'ai découvert son existence. Cela semble être une bibliothèque née à l'origine et séparée du développement d'IPython. Donc, si vous utilisez IPython ou jupyter notebook, je vous suis redevable et je l'utilise sans rien savoir.

C'est parce que jupyter notebook et les fichiers de configuration IPython sont chargés à l'aide de traitlets, par exemple en éditant jupyter_notebook_config.py [^ 1] et ʻipython_config.py` [^ 2].

#Basique commenté
c.Application.log_datefmt = '%Y-%m-%d %H:%M:%S'

Il se peut que des personnes aient vu ou modifié la description de cette manière.

[^ 1]: Celui généré est dans le répertoire pointé par config: (en gros, .jupyter directement sous le home), qui est tapé dans jupyter --path. Si vous ne l'avez pas défini, vous pouvez le générer avec jupyter notebook --generate-config.

[^ 2]: S'il y a un répertoire de profil (commençant par profile) sous le répertoire qui apparaît dans ʻipython Locate, il est là. Si vous ne l'avez pas créé, vous pouvez le créer avec ʻi python profile create (si vous ne spécifiez pas de nom).

En fait, le mystérieux «c» qui apparaît ici est une instance de la classe Config de traitlets. Et quand vous écrivez c.Application.log_datefmt = ..., cela s'appelle log_datefmt de la classe Application gérée par la classe Configurable qui lit le fichier de configuration dans lequel cette description est écrite (en fait, la classe Application qui les regroupe?). Les variables membres reçoivent la valeur «...».

En fait, la classe NotebookApp (Definition), qui peut être considérée comme la classe de base de jupyterApp. Définition](https://github.com/jupyter/jupyter_core/blob/b129001bb88abf558df5dfab791a5aeeba79e25c/jupyter_core/application.py#L61)), mais cette classe JupyterApp hérite de la classe Application des traitslets ([ici]](https://github.com/jupyter/jupyter_core/blob/b129001bb88abf558df5dfab791a5aeeba79e25c/jupyter_core/application.py#L30)).

J'ai fait un peu de recherche sur le type de bibliothèque de ces traitlets, mais je ne pouvais pas les comprendre correctement car la documentation était si désordonnée, alors j'ai écrit cet article dans le but de répandre l'existence.

Et ce qui suit est un document de traduction + contenu dilué de Comment utiliser.

Comment utiliser les Traitlets

Puisque le soi-disant «type» est déterminé dynamiquement en python, il est possible d'attribuer des valeurs arbitraires aux attributs de classe (variables membres) sauf indication contraire. Je comprends que l'un des rôles fournis par les traitlets est de taper correctement les attributs de cette classe afin que des fonctions de vérification plus détaillées puissent être appelées facilement. En fait, la lecture du fichier de paramètres, qui est également utilisé dans l'implémentation jupyter et ipython, semble être la fonction principale ...

Fonction de vérification de type

Définissez la sous-classe HasTraits Foo comme suit:

from traitlets import HasTraits, Int

class Foo(HasTraits):
    bar = Int()

Il donne à une classe appelée Foo un attribut appelé bar, tout comme une classe normale. Cependant, contrairement aux variables de classe régulières, il s'agit d'un attribut spécial appelé ** trait **. En particulier, cette barre est un type de trait appelé int, et comme son nom l'indique, c'est un trait qui stocke des valeurs entières.

Créons en fait une instance:

> foo = Foo(bar=3)
> print(foo.bar)
3
> foo.bar = 6
> print(foo.bar)
6

Et foo a un attribut appelé barre de valeur entière "type", et il est possible de changer la valeur.

D'un autre côté, qu'en est-il de donner une corde?

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

Vous devriez recevoir un message d'erreur indiquant qu'une telle attribution de type est incorrecte. Cela élimine le besoin d'implémenter la vérification de type vous-même, comme avec __setattr__.

Seul le type Int est introduit ici, mais certains sont disponibles, y compris des types de conteneurs tels que List, et vous pouvez définir les vôtres. Veuillez consulter la documentation pour plus de détails.

Réglage de la valeur par défaut

Le traitlet vous permet de spécifier dynamiquement des valeurs par défaut lors de l'instanciation. À propos, dans le type de trait ʻInt` ci-dessus, 0 est défini comme valeur par défaut si rien n'est spécifié:

> foo = Foo()
> print(foo.bar)
0

Dans l'exemple suivant, la date du jour est stockée dans un trait appelé aujourd'hui.

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)

À propos, comme vous pouvez le voir en un coup d'œil dans le code, le type de trait actuel est un tuple composé de trois valeurs entières. Notez que la valeur par défaut de Tuple est (), donc si vous ne spécifiez pas la valeur par défaut ou ne spécifiez pas la valeur au moment de l'instanciation, le type sera différent et une erreur d'allocation se produira.

Je pense que cela équivaut probablement à écrire ce qui suit, mais le premier est clairement plus facile à lire du point de vue de l'isolement logique:

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)

Validation de la valeur

Ensuite, nous allons introduire la fonction de vérification d'attribution de valeur. Même si la vérification de type devient possible, je ne sais pas si la valeur est appropriée. Par exemple, lorsqu'un trait (disons qu'il représente le nombre de quelque chose) doit être un entier non négatif, ʻInt` seul ne suffit pas.

D'ailleurs, cette limitation peut dépendre d'un autre trait. Par exemple, si vous avez un mois qui stocke un mois et un jour qui stocke un jour, la plage de jours autorisée dépend de la valeur de month. C'est «validate» qui effectue une telle vérification.

Ici, nous ne le mettrons en œuvre qu'en novembre et décembre.

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

Si plusieurs variables de trait se réfèrent les unes aux autres, la modification d'une valeur peut entraîner une erreur de vérification en cours de route. Dans de tels cas, vous devez ignorer la validation jusqu'à ce que tous les traits aient changé. Ceci peut être réalisé dans la portée hold_trait_notifications. Regardons l'exemple suivant:

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

Dans cet exemple, deux traits a et b sont définis, mais leur produit doit être non négatif. Ensuite, même si les deux valeurs sont négatives, cette vérification réussira, mais si une seule est modifiée, une erreur de vérification se produira. D'un autre côté, si vous modifiez les valeurs des traits a et b dans hold_trait_notifications, la vérification du retard sera effectuée à la fin de cette portée, vous n'avez donc pas à vous en soucier.

Modifier la notification

Enfin, je voudrais introduire la fonction pour implémenter le modèle d'observateur dans le trait. Cela vous permet de faire quelque chose lorsque la valeur de trait spécifiée est réécrite (l'événement se produit).

class Foo(HasTraits):
    bar = Int()

    @observe('bar')
    def _observe_bar(self, change):
        ...

Ce n'est plus du code complet, mais lorsque la valeur du trait de barre change, la fonction _observe_bar est exécutée.


Comme mentionné ci-dessus, le contenu est incroyablement mince, mais pardonnez-moi de le publier pour la première fois dans un système de langage de programmation. Aussi, si vous êtes familier avec les traitlets, merci d'enrichir les documents trop solitaires et exemples ...

Recommended Posts

Technologie qui prend en charge Jupyter: Tralets (histoire d'essayer de déchiffrer)
L'histoire d'essayer de reconnecter le client
Histoire d'essayer d'utiliser Tensorboard avec Pytorch
Une technologie qui soutient les créateurs de caca ~ La transition d'état rêve-t-elle de caca?
L'histoire selon laquelle la version de python 3.7.7 n'était pas adaptée à Heroku
Une histoire qui prend en charge la notation électronique des examens avec reconnaissance d'image
L'histoire de la tentative de pousser SSH_AUTH_SOCK obsolète avec LD_PRELOAD à l'écran
Une histoire qui a souffert d'une différence de système d'exploitation lors de la tentative d'implémentation d'un article
Faisons un noyau jupyter
L'histoire de la mise en place de MeCab dans Ubuntu 16.04
Technologie prenant en charge le descripteur Python #pyconjp
L'histoire d'essayer deep3d et de perdre
Jupyter Notebook Principes d'utilisation
L'histoire du changement de pep8 en pycodestyle
L'histoire de l'adresse IPv6 que je souhaite conserver au minimum
Une bibliothèque pour la science des données "Je veux faire ça" sur le bloc-notes Jupyter
L'histoire d'un technicien de haut niveau essayant de prédire la survie du Titanic