mardi 14 février 2023

Il y a autre chose que les classes en Python

 Le langage Python offre un éventail de dispositif pour structurer et organiser les données.

ci dessous un aperçu: 


Les slices. 

Ainsi déjà avec une simple donnée string, il est possible de donner un nom à des tranches de chaine pour simplifier les manipulations:


Les tuples nommés.

Ils ne sont pas toujours appréciés à leur juste valeur pourtant ils permettent des simplifications :

Attention, un tuple n'est pas modifiable ! 
Il est possible de créer des tuples nommés à partir d'une structure iterable comme une liste ou un dictionnaire. Pour ces derniers l'opérations inverse est possible.
 



Un tuple nommé est très utile pour présenter le contenu d'un flux comme celui d'une requete SQL

Sans le tuple nommé, pour accéder au nom de l'acteur il faudrait indiquer le nom de la colonne:

row[1]


Avec un tuple nommé : On manipulera le résultat par le nom de la colonne.


Les classes vides et le dispositif  Simplenamespace.

Quand on désire construire une classe dynamiquement pour n'utiliser que ses attributs, la solution la plus connue est de créer une classe vide (class A:  pass). Il existe pourtant une autre solution:  simplenamespace.



Ce système est utilisable pour convertir à la volée, une ligne d'un fichier JSON:

Exemple : avec une fonction object_hook


Comme c'est un objet, il est modifiable.

Et pour les dataclasses ?: Elles feront l'objet d'un prochain billet, car elles sont très puissantes et elles sont de plus en plus utilisées.


samedi 11 février 2023

Python: fonctionnement du modèle objet

 Python propose un modèle objet assez simple par rapport aux autres langages. Il se situe à mi-chemin entre le dispositif mis en place pour Perl et celui plus sophistiqué de Java.

En Python, il n'y a pas à proprement parlé d'attribut privé. Il existe la notion d'attribut protégé qui s'obtient en préfixant  le nom de l'attribut par deux  '_'   , exemple:  __monAttribut .

Un attribut protégé est toutefois accessible par l'ajout du nom de la classe , exemple :  _maclasse__monAttribut . Cette protection est donc toute relative.

Comment Python recherche une méthode ou un attribut.

Pour connaitre le cheminement, il est possible de redéfinir ses propres méthodes.

Celle à implémenter dans une classe sont: 

    def __getattribute__(self, attr):
        print(f"attribut demandé : {attr}")
        return super().__getattribute__(attr)
    
    def __getattr__(self, attr):
        print(f"attribut demandé non trouve : {attr}")
        return super().__getattribute__(attr)
    
    def __setattr__(self, attr,val):
        print(f"attribut a changer : {attr} avec {val}")
        super().__setattr__(attr,val)          

Recherche d'un attribut ou d'une méthode.


En  faisant quelques essais, on est capable de dresser le schéma suivant: 




Quand un attribut ou une méthode est recherché dans l'instance d'un objet, la méthode interne __getattribute__ est appelée.
  • En cas d'échec de la recherche au sein de l'objet, une recherche est relancée au niveau de la classe elle même pour retourner une attribut de classe par exemple. 
  • En cas d'échec la methode __getattr__ est sollicitée en dernier recours.

Mise à jour d'un attribut.


Le circuit est simplifié: 


Quand une demande de mise à jour est lancée, la méthode __setattr__ est activée, si l'attribut n'est pas trouvé au sein de l'objet , la création de l'attribut se fera au niveau de l'objet.

Ecarts de comportement.

Cette illustration permet de comprendre pourquoi il est possible de créer des attributs à la volée. Il explique aussi le comportement de Python face aux variables de classe ou aux attributs protégés.
Dans ces deux cas de figure, un nouvel attribut sera crée interférant avec les autres. 

Illustration:  Une classe avec une variable de classe et un attribut protégé


Jusque là tout va bien:
Apres quelques manipulations de la variable de classe: 

Idem pour l'attribut protégé:



Après ces manipulations, l'objet est un peu en vrac. Aussi pour laisser les choses dans l'ordre, deux règles sont à observer:

  • Ne pas manipuler une variable de classe par le biais des objets instanciés.
  • Ne pas manipuler directement les attributs protégés 

vendredi 13 janvier 2023

Quelques liens Python utiles



J ai pioché dedans : prospector , il fait une analyse du code de projet et formule des recommandations.

  • Python datascience handbook (lien ici) : livre gratuit sur Numpy et Pandas
  • 10 antisèches Python. (lien ici)
  • Le site refactoric guru: (lien ici)  de la refactorisation et du design pattern
  • Embarquer un tableur dans Pandas avec mito.
  • Une nouvelle génération de dictionnaire : benedict:(lien ici) 
  • Tout sur Python (verbe, librairie etc) : lien ici 
  • Une base de données sous forme de dictionnaire (lien ici)  dictDataBase


Des articles

Les origines du langage Python (lien ici) 

Architecture hexagonale en Python.(lien ici)



lundi 19 décembre 2022

Utilisation avancée des groupes dans une regex Python

 Dans une regex les parenthèses servent  à capturer des caractères qui seront stockés dans l'objet Match.

Mais leurs fonctionnalités ne sont pas toujours bien connues.

Reference à un groupe : numerique ou nommée

Chaque parenthèse ouvrante provoque la création d'un groupe qui sera accessible par son numéro.

La numérotation commence à 1 , le texte capturé dans le groupe '1' pourra etre rappelé plus loin dans la

regex par '\1' .

La méthode groups() de l'objet match, retournera la liste des textes capturés. Le groupe 0 qui est un emplacement libre, contiendra tout le texte capturé.


Comme on  peut le voir ci-dessous, les groupes peuvent se voir attribuer un nom pour plus de lisibilité

Cette opération se réalise par le motif (?P=<NOM> .... ) 



La référence à une capture d'un groupe nommé se fait par le motif (?P=nom)
Ce nom se retrouvera dans les appels de méthode Match.group ou Match.span




Le site regex101 peut aider à la mise au point des regex.


La recherche en regardant en avant ou en arrière  (lookahead / lookbehind) .

C'est une jeu de motif très puissant qui permet de réaliser plusieurs contrôles ou d'enchainer plusieurs conditions sans consommer des caractères.

Exemples de recherche avant: on veut tronçonner la chaine ('123456789') par groupe de 2 en partant de la fin.

Avant de capturer un groupe, on va chercher à former des paires complète en avant du groupe à capturer.

Recherche en avant

Pour cela on utilise la séquence de groupe (?=...) ou sa forme négative  (?!...)

En ajoutant aux bons endroits le motif "(?:)" on ne capture plus les groupes inutiles

Le motif (?:...) permet d'effectuer de regroupement mais ne conserve pas la capture dans un groupe


Recherche en arrière

De manière symétrique à la recherche en avant, il est possible de faire dépendre la recherche d'un motif sur du texte situé en arrière (lookbehind).

Cette opération se réalise avec le motif (?<=...) ou par sa négation (?<!...)



L'avantage des regards avant ou arrière (look...) est qu'ils ne consomment pas de caractère. Il est donc possible de chainer les conditions




Les liens utiles:

lundi 5 décembre 2022

Visualiser un fichier RST

Le format RST  ( reStructuredTex) est un langage de balisage simplifié destiné à documenter facilement du code. Il fait partie intégrante du langage Python (PEP 287 – reStructuredText Docstring Format).

Aussi, il est pratique d'utiliser du RST dans de la docstring d'un module ou d'une classe. Des générateurs de documentation comme SPHINX pourront s'en servir. 

Le site github en fait un usage important dans la présentation des projets en mettant en avant le fichier README.rst des projets (exemple ici avec pycobol) .

Le moyen le plus simple pour avoir une idée du rendu est d'installer l'utilitaire Python Restview:
pip install restview

Puis lancer la commande: Restview <mon_fichier.rst>
Un mini serveur web sera lancé.

Le navigateur ouvrira une page html avec votre RST mis en forme.
Les modifications dans le fichier RST seront immédiatement visibles dans le navigateur

Les balises basiques les plus usitées sont:
Le '=' sous toute un texte pour l'afficher comme un titre.
Le '-' sous toute un texte pour l'afficher comme un sous-titre
Le '*'  Pour commencer une liste







mercredi 30 novembre 2022

Cohabitation de Jupyter avec un projet POETRY

 Poetry est pour moi le meilleur outil pour la gestion technique d'un projet Python.

Cet article va vous expliquer comment utiliser Jupyter dans un projet POETRY.

L'objectif est d'avoir un nouveau kernel (noyau) Python basé sur votre environnement virtuel.

Cet environnement virtuel peut être créé par la commande : poetry new:

exemple : > poetry new monprojet

 Aller dans le répertoire 'monprojet'  (cd monprojet)

Puis lancer l'ajout d'un nouveau kernel Jupyter par la commande:

> poetry run ipython kernel install --user  --name kmonprojet

La commande ipython kernel install ... est lancée via l'environnement virtuel de votre projet.

La derniere option permet d'indiquer quel nom sera utilisé dans l'interface Jupyter pour identifier le kernel.

Puis toujours depuis le contexte Poetry, lancez jupyter par la commande:

> poetry run jupyter notebook


Le navigateur se lance et permet de choisir un kernel propre à votre projet:



Avant de se lancer dans cette manipulation, peut être faut il se poser la question suivante: 

Pourquoi avoir besoin d'un notebook Jupyter dans un dispositif de type poetry ?

Réponse au prochaine billet


samedi 29 octobre 2022

Refactorisations assistées par des DOCTEST

 Python permet facilement de passer d'un programme structuré en fonction à un programme utilisant des classes et des objets.

C'est à mon avis une des forces de Python. Pas besoin de tout réécrire le gros du travail est conservé. 

Aussi , Python favorise la  refactorisation de son code au fur et à mesure de la progression du développement.

(le notebook est disponible sur : https://github.com/germanlinux/notebooks

(https://github.com/germanlinux/notebooks/blob/main/init/refactorisation.ipynb)

La refactorisation en  objet et classe consiste à :

  • Ajouter le paramètre 'self' dans les déclarations des fonctions 
  • Préfixer les variables locales par 'self'
  • Créer un cadre pour la classe
  • Améliorer le nommage des méthodes


Pour sécuriser ces opérations de refactorisation, j'utilise le dispositif  du DOCTEST pour m'assurer de la non régression du programme.

Le DOCTEST permet d'inclure des tests dans la documentation des méthodes ou des fonctions.

Il utilise pour cela le motif '>>>'  pour insérer des instructions Python dans la documentation. Le resultat de ces appels est comparé avec la ligne suivante sans préfixe.

Exemple:





Ces insertions n'auront aucun impact sur le fonctionnement des méthodes ou des fonctions.

Pour activer les tests il suffit d'ajouter l'appel au module de DOCTEST.


Dans cet exemple, la fonction à tester est indiquée. Il est possible de lancer tous les test présents dans un programme en remplaçant la methode run_docstring_examples(...) par testmod(..)

Lancement des tests 
sous condition 





Ci dessous un exemple complet (projet pycobol) 

class ZoneGroupe:
    ''' Cette classe prend en charge la creation d'une zone groupe. 
    Par nature cette zone est de type ALN.
    Sa longueur est la somme des longueurs des composants qui la composent
    >>> obj = ZoneGroupe('zoneessai', 1, 0)
    >>> obj.nom
    'zoneessai'
    >>> print(obj)
    >>> objfils = ZoneGroupe('zonefils', 2)
    >>> obj.ajout_fils_groupe(objfils)
    >>> len(obj.fils)
    1
    >>> objfils.pere # doctest: +ELLIPSIS
    ZoneGroupe(nom='zoneessai'...
    >>> objfils2 = ZoneGroupe('zonefils2', 1)
    >>> obj.ajout_fils_groupe(objfils2)  # doctest: +ELLIPSIS
    Traceback (most recent call last):
    ...
    >>> objfilsimp = ZoneFilsSimple('essaifils', 5, picture = '999')
    >>> obj.ajout_fils_simple(objfilsimp)
    >>> obj.longueur_utile
    3
    '''

La directive #doctest: +ELLIPSIS  permet de signaler au module DOCTEST d'arrêter la comparaison du résultat après les trois points (...) . C'est très utile pour tester une exception: la sortie complète de l'exception (trace de la pile d'exécution) sera  ignorée. Il est aussi utilisé ici pour des contenus un peu longs comme les objets.


class ZoneGroupe(builtins.object)
 |  ZoneGroupe(nom: str, rang: int, pere: int = 0, fils: list[int] = <factory>, son_type: str = 'GRP', usage: str = 'DISPLAY', longueur_utile: int = 0, valeur_interne: str = '', valeur_externe: str = '', section: str = 'NON RENSEIGNE') -> None
 |
 |  Cette classe prend en charge la creation d'une zone groupe
 |  par nature cette zone est de type ALN.
 |  Sa longueur est la somme des longueurs des composants qui la composent
 |  >>> obj = ZoneGroupe('zoneessai', 1, 0)
 |  >>> obj.nom
 |  'zoneessai'
 |  >>> print(obj)
 |  >>> objfils = ZoneGroupe('zonefils', 2)
 |  >>> obj.ajout_fils_groupe(objfils)
 |  >>> len(obj.fils)
 |  1
 |  >>> objfils.pere # doctest: +ELLIPSIS
 |  ZoneGroupe(nom='zoneessai'...
 |  >>> objfils2 = ZoneGroupe('zonefils2', 1)
 |  >>> obj.ajout_fils_groupe(objfils2)  # doctest: +ELLIPSIS
 |  Traceback (most recent call last):
 |  ...
 |  >>> objfilsimp = ZoneFilsSimple('essaifils', 5, picture = '999')
 |  >>> obj.ajout_fils_simple(objfilsimp)
 |  >>> obj.longueur_utile
 |  3

Résultat de l'instruction help 

Ci-dessous un résultat partiel d'exécution des tests

Trying:
    objfils = ZoneGroupe('zonefils', 2)
Expecting nothing
ok
Trying:
    obj.ajout_fils_groupe(objfils)
Expecting nothing
ok
Trying:
    len(obj.fils)
Expecting:
    1
ok
Trying:
    objfils.pere # doctest: +ELLIPSIS
Expecting:
    ZoneGroupe(nom='zoneessai'...
ok
Trying:
    objfils2 = ZoneGroupe('zonefils2', 1)
Expecting nothing
ok

Résultats des tests.

En conclusion.

Python nous donne la possibilité de se tromper et d'améliorer notre ouvrage par petites touches. 

Ces tests intégrés sont à compléter par des tests unitaires avec une couverture du code mesuré avec la commande coverage.