dimanche 9 avril 2023

Les deux dispositifs de gestion mémoire de CPython

 CPython est la la machine virtuelle de référence d'exécution des programmes Python. CPython compile et interprète les sources d'une programme python pour son exécution.

A ce titre, il prend en charge la gestion de la mémoire pour les variables ou les objets Python. 

Dans un but de rationnaliser l'empreinte mémoire, il va mettre en place deux mécanismes distincts pour gérer au mieux cette mémoire.

Un objet est composé de deux choses: un nom (1) de variable qui fait référence à une zone mémoire (2).

Quand un objet est détruit (commande del ?, fin de bloc ?) , le nom de l'objet (variable) est enlevé de l'espace de nommage mais la zone mémoire ne sera forcement rendue disponible. C'est à ce moment que les deux mécanismes évoqués vont entrer en jeu.

 1) La gestion par compteur de référence.

Ce dispositif est à la fois simple, robuste et efficace.

Chaque fois un objet est référencé par un autre, un compteur de référence est incrémenté de 1. Le mécanisme inverse s'applique chaque fois qu'un objet est déréférencé par un autre objet. Quand le compteur arrive à 0 , l'espace mémoire occupé par l'objet se récupéré.

Remarque : une opération comme celle-ci : a = b * 4 va incrémenter de 1 le compteur de référence de la variable b puis à la fin de l'operation le décrémenter. 


Exemple: utilisation de getrefcount pour afficher le nombre de référence.

import sys
>
a = 50000
b = ['etoile', a, 'neige']
c = (a, b)
d ='srer'
print('a', sys.getrefcount(a))
print('b', sys.getrefcount(b))
print('c', sys.getrefcount(c))
print('d', sys.getrefcount(d))
 
=>
a 4
b 3
c 2
d 2   

On supprime un objet qui en référençait un autre
del(c)
print('a', sys.getrefcount(a))
print('b', sys.getrefcount(b))
print('d', sys.getrefcount(d))
 
=> 
a 3
b 2
d 2
(mise en forme avec https://highlight.hohli.com/)

Mais parfois le dispositif peut être en pris en défaut par la présence de références cycliques:)

Soit deux listes
y = ['un' , 'deux' ]
z = [3, 5, 7]
print('y', sys.getrefcount(y))
print('z', sys.getrefcount(z))
 
=>
y 2
z 2
On ajoute à chaque liste , l'autre liste.
y.append(z)
z.append(y)
print('y', sys.getrefcount(y))
print('z', sys.getrefcount(z))
 
=>
y 3
z 3
On supprime la liste 'z'
del(z)
 
 
print('y', sys.getrefcount(y))
print(y)
 
=>
y 3
['un', 'deux', [3, 5, 7, [...]]]
Le compteur de référence sur y n'a pas bougé car même si la variable n'est plus accessible,  son contenu persiste en mémoire et il pointe toujours sur y.

image de https://pythontutor.com/
Avant la suppression de la liste Z



Apres suppression de Z , l nom de variable 'z' nest plus reconnu mais la zone mémoire est toujours utilisée.

On peut avoir le même phénomène avec un objet qui se référence lui même:


Exemple ici en faisant:
x =[]
x.append(x)

Pour pallier à cette carence, CPython utilise un deuxième dispositif: le garbage collector.

 2) Le garbage collector de CPython.


Il est doté de trois collections ou conteneurs  permettant de stocker des éléments du plus récent (collection 0) au plus ancien (collection 2): il est dit 'générationnel' .

Chaque fois qu'un objet devient  injoignable et que le dispositif de comptage n'a pas pu libérer la zone, il sera collecté et examiné finement type par type pour résoudre le dilemme.  

Le module gc permet de manipuler le garbage collector.
La méthode get_threshold() retourne les seuils pour les 3 générations: lorsque le nombre d'objet d'une génération  dépasse le seuil, le garbage collector se met en œuvre pour examiner chaque objet et résoudre les incohérences (références circulaires) . Un objet récent passera de la collection 0 , à la collection 1 puis enfin à la 2 qui contiendra de fait les objets les plus anciens. 

import gc
gc.get_threshold()
 
=>
(700, 10, 10)
 
gc.get_stats()
 
=>
[{'collections': 370, 'collected': 32693, 'uncollectable': 0},
 {'collections': 33, 'collected': 3952, 'uncollectable': 0},
 {'collections': 3, 'collected': 1561, 'uncollectable': 0}]
La méthode get_stats() retourne le nombre d'objet surveillé par génération.
Pour illustration:
On va créer une référence circulaire , puis supprimer l'objet et voir ainsi le travail du garbage collector
Le garbage collector est placé en mode debug.
# Préparation 
gc.set_debug( gc.DEBUG_COLLECTABLE| gc.DEBUG_SAVEALL )
n = gc.collect()
zorro = [1, 2,'autre chose']
maliste = ['ert', 4, 'divers']
 
# Création d'une référence circulaire
zorro.append(zorro)
# suppression de l'objet
del zorro
 
# Mesures
avant = [ str(x) for x in  gc.garbage]
print(len(avant))
avant= set(avant)
n = gc.collect()
print(n)
apres =  [ str(x) for x in  gc.garbage]
print(len(apres))
apres = set(apres)
dif = apres- avant
print("result ", dif)
 
=> 
1753
1
1754
result  {"[1, 2, 'autre chose', [...]]"}
gc: collectable <list 0x0000017E6AFDD480>
La zone mémoire sera libérée et sera disponible pour un autre stockage.
 MAIS ce n'est pas pour autant que CPython rendra de l'espace mémoire au système d'exploitation.

Conclusion.

Il est possible de modifier le seuil de déclenchement du garbage collector (méthode set_threshold) 
ou encore de le désactiver. Il n'y aucune raison a priori de modifier le comportement par défaut.

vendredi 7 avril 2023

Exercice 1 : python initiation

Niveau: initiation : solution la semaine prochaine

Bon weeek-end.

On désire émuler un opérateur logique ET. 

1) Créer une classe EtLogique qui aura pour attribut:

  • Une entreeX  (valeur 0 ou 1, booléen etc )
  • Une entreeY  (valeur 0 ou 1, booléen etc )
  • Une sortieET
  • Une méthode pour mettre à jour entreeX ou entreeY
  • Une méthode pour connaitre l'état de la sortieET




Tester le dispositif avec les 4 cas différents.

2) Faire un circuite logique avec 2 opérateurs logique connectés un troisième operateur logique.

Tester le dispositif.


3) Faire un émulateur de l'operateur logique NonLogique 

Il inverse la valeur d'entrée



4) Faire un circuit pour simuler l'inverse du Et logique (NAND)  

vendredi 24 mars 2023

Collecte sur le web (2eme article)

 Apres un premier article listant mes découvertes sur le web (du python mais pas seulement) ,voici la seconde édition plus structurée.



Sujets : pour le manager.

Simon Wardley  est un chercheur britannique et ancien PDG surtout connu pour la création de la cartographie permettant de visualiser dynamiquement la maturité d'une innovation ou d'une transformation.
L'objectif est de faire une presentation de 20 slides de 20 secondes chacun. Pour un total de 6min 40.
Ce mode permet d'aller à l'essentiel et de ne pas assommer l'auditoire.

Python:


Sujets datasciences.

Sujets pour les administrateurs systèmes.

Sujets architectures.


mardi 21 mars 2023

5 choses à savoir sur les dataclasses Python

La souplesse et les facilités qu'apportent les dataclasses, me font penser qu'un jour les dataclasses se retrouveront mises en  avant dans Python et soient dans le futur, le moyen natif de créer des classes.

(le notebook ci-dessous se retrouve sur mon github)

1) Comment savoir quel sera le comportement du constructeur de la classe ?

Le constructeur de la classe est généré par la dataclasse et il n'est pas visible en l'état.

Pour comprendre ce qui va se passer, on utilisera le module 'dis' qui permet de désassembler le code Python. Un source Python est traduit en bycode qui sera exécuté par la VM Python. Le module 'dis' montre la traduction qui sera faite de ce bytecode.

Exemple: Soit une classe dont le constructeur attend deux paramètres ident et nom.


L'utilisation du module dis donnera: 



Sur le listing, on  retrouve sans trop de difficulté les deux opérations d'initialisation du constructeur.

Il y  toujours la possibilité de modifier l'instanciation d'un nouvel objet en implémentant la méthode :__post_init__(). 
Cette méthode est appelée après le constructeur.

2) Dans le cadre de l'héritage, Dataclasse prend en charge pour vous l'appel du constructeur de la classe mère.

Dans le cadre de l'héritage, il est parfois nécessaire de faire appel à super().__init__(...)  pour initialiser les attributs de la classe mère.



Dans la classe fille:

Avec les dataclasses c'est plus simple:

En reprenant la dataclasse Exemple, on voit que le constructeur prend en charge les paramètres pour la classe mère et la classe fille


Attention: si la classe mère propose des valeurs par défaut,  les attributs de la classe fille doivent aussi en fournir. On retrouve ici la règle des paramètres  sans valeur par défaut en premier. C'est pour cela que l'attribut prénom est prévu avec une valeur par défaut.


3) Comment déclarer une variable de classe.

On va devoir utiliser le module  typing 



Avec l'initialisation de la variable de classe

4) Utilisation d'un attribut mutable (exemple: une liste ) dans le constructeur.



Dans cet exemple : l'attribut mail ne sera pas initialisé par le constructeur. Par contre ma_liste va poser un problème et provoquera l'erreur suivante:


Il faudra passer par une fabrique :


5) Il est possible d'adapter le comportement général de la classe en passant des paramètres au décorateur.


Exemple 
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False,
           match_args=True, kw_only=False, slots=False, weakref_slot=False)



Génération du constructeur, de la méthode __repr__  etc.

Ne pas hésiter à consulter la documentation du module par la commande help('dataclasses')  ou directement dans une console pydoc dataclasses


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)