# on importe numpy, une bibliothèque spécialisée dans le calculs impliquant de grands # jeux de nombres et très utilisée par les scientifiques import numpy as np # On créé une classe qui hérite du type np.ndarray qui est un type d'array # de taille fixe, et dont tous les objets doivent être du même type, # mais qui peut avoir plusieurs dimensions et qui est très rapide à manipuler # Notez que par convention NumPy n'utilise pas de majuscule pour le nom de # ces types pour matcher str, int, unicode, etc. Pour rester congruent, # la classe enfant ne le fait pas non plus. Ne prenez pas cette habitude, # c'est un cas particulier. class cbarray ( np. ndarray ) : # __new__ est la seule méthode de classe par défaut, sans déclaration de @classmethode, # donc le premier argument sera la classe en court. Normalement la convention # est d'appeler cet argument cls, mais ici l'auteur fait à sa sauce... # # Le reste des arguments sont les mêmes que pour __init__: ce sont ceux # attendus à l'instanciation de la classe cbarray: # * data sont les données qu'on veut mettre dans l'array # * cb est un callback qu'on appelera à chaque mise à jour de l'array # * dtype est le type des données à mettre dans l'array (si on veut bypasser l'autodétection) # * copy pour présicer si on veut copier les données, ou juste liér les données existantes # # __new__ est appelée avant __init__: elle prend les paramètres qu'attend __init__, # fabrique une instance, la retournepuis __init__ est appelée avec l'instance. # On a rarement besoin d'overrider __new__, en générale __init__ est un meilleur choix # car c'est plus simple. Mais certains types NumPy ne permettent pas de faire # autrement. def __new__ ( subtype , data , cb = None , dtype = None , copy = False ) : # On attache le callback à la CLASSE, et non à l'instance. # Ca peut paraitre étrange, mais on verra plus bas pour subtype.__defaultcb = cb # Selon que l'on souhaite copier les données, ou juste les lier # on créé une instance d'un type différent. # Notez que l'absence d'espace atour de "," et "=" n'est pas recommandé # pas le PEP8 if copy : data = np. array ( data , dtype = dtype ) else : data = np. asarray ( data , dtype = dtype ) # Cette astuce permet d'utiliser un des deux types du dessus # mais au travers de l'API du type "subtype", c'est à dire notre # classe. data = data. view ( subtype ) # On retourne l'instance ainsi créé. # Quand l'utilisateur fera cbarray(....), c'est cette instance # qu'il recevra return data # Juste une méthode qui appelle le callback si il existe def _notify ( self ) : if self . cb is not None : self . cb ( ) # Une propriété qui retourne un attribut de l'objet # en s'assurant qu'on utilise celui du parent. # C'est une supposition mais je pense que shape est une propriété du # parent, qu'elle n'est pas dispo sur le type array ou asarray, et # que l'astuce data.view ne suffit pas à faire un proxy de celle-ci. # Donc je pense que ça sert à donner accès à cette donnée. def _get_shape ( self ) : return super ( cbarray , self ) . shape shape = property ( _get_shape ) # __setitem__ est une méthode "magique", appelée automatiquement qu'on # fait array[item] = val # Ici on l'utilise pour appeler _notify() à chaque mise à jour de l'array # et donc d'appeler le callback à chaque mise à jour. # Il est dommage de ne pas passer de paramètre à la méthode, comme # l'ancienne valeur, l'item et la nouvelle valeur. Le callback va être # du coup assez limité. Mais ça suffira si par exemple tout ce qu'on # veut faire c'est écrire dans un fichier à chaque modification. def __setitem__ ( self , item , val ) : np. ndarray . __setitem__ ( self , item , val ) self ._notify ( ) # NumPy permet de créer des sous types à partir du type de base, c'est # d'ailleurs une manière très courrante de créer des nouveaux conteneurs # de données. Mais ce faisant, NumPy bypass le mécanisme d'instanciation, # et __new__ et __init__ ne sont donc pas appelées. Pour y pallier, NumPy # ajoute la méthode __array_finalize__ qui est toujours appelée quand # un array est prêt à être utilisé. Elle peut être utilisée pour effectuer # un traitement pour chaque array créé, quelque soit sa provenance. # Ici, on l'utilise pour attacher le callback à "l'instance". # Souvenez-vous, plus haut on avait attaché le callback à la CLASSE. # Cette classe peut derrière être la source de nombreux arrays même si # __init__ n'est pas appelé pour eux :-( # La solution de l'auteur est donc de passer le callback à __new__, de # l'attacher à la classe, et à travers __array_finalize__, de l'attacher # à "l'instance". Il faut garder en tête que tout nouvel appel à __new__ # écrasera le callback pour toutes les instances suivantes. Mis à part # cela, ceci garanti que tout array aura le callback, et donc que # _notify aura accès au callback, et donc que __setitem__ déclenchera le # callback, et donc que la fonction sera bien appelée à chaque mise à jour # de l'array def __array_finalize__ ( self , obj ) : if not hasattr ( self , "cb" ) : # The object does not yet have a `.cb` attribute self . cb = getattr ( obj , 'cb' , self .__defaultcb ) # Encore une méthode "magique" ajoutée par NumPy. Elle est appelée # quand on sérialise l'array et retourne des informations sur l'état # de l'array def __reduce__ ( self ) : object_state = list ( np. ndarray .__reduce__ ( self ) ) subclass_state = ( self . cb , ) object_state [ 2 ] = ( object_state [ 2 ] , subclass_state ) return tuple ( object_state ) # inverse de __reduce__ def __setstate__ ( self , state ) : nd_state , own_state = state np. ndarray .__setstate__ ( self , nd_state ) cb , = own_state self . cb = cb # Le callback qui doit être appelé à chaque mise à jour de l'array # Je ne pense pas que ça puisse marcher, car il attend un argument, # ce que _notify() ne lui passe pas. def callback ( arg ) : print 'array changed to' , arg # une petit démo du code si on run le script au lieu de l'importer if __name__ == '__main__' : x = cbarray ( [ 1 , 2 , 3 ] , cb = callback ) x [ [ 0 , 1 ] ] = 1.0