ce notebook est le premier d’une série consacrée à l’accès aux attributs
on a déjà vu dans le chapitre sur les classes comment fonctionne l’accès aux attributs d’un objet dans le cas usuel; rappelez-vous, on avait dit qu’on se basait sur le contenu des espaces de nom, et que:
l’écriture d’un attribut se fait directement dans l’objet
la lecture se fait en cherchant dans l’objet, puis en remontant la chaine d’héritage
en fait, cette présentation est une simplification, certes très utile en première approche
mais déjà dans le même chapitre on a vu - sans s’appesantir - le cas des properties, où les choses ne se passent pas exactement selon cette vision simpliste
car en réalité le mécanisme général d’accès aux attributs est beaucoup plus complexe, et nous allons essayer de nous y retrouver :)
__getattr__¶
et pour commencer nous allons voir un mécanisme assez simple, qui permet de facilement gérer de manière programmable ce qu’on pourrait appeler la génération automatique d’attributs
il s’agit d’un mécanisme relativement simple, et très ancien dans le langage
avec __getattr__ et ses compagnons __setattr__ et __delattr__, œon veut qu’un objet puisse “faire semblant” de possèder un attribut, alors qu’en fait cet attribut n’est pas présent dans son namespace
pour quoi faire me direz-vous ? les usages sont nombreux:
use case 1: le wrapper¶
souvenez-vous de la classe Station:
stations = pd.read_csv("stations.csv")
class Station:
def __init__(self, station_id):
self.row = stations.iloc[station_id]et on avait vu comment utiliser une property pour exposer l’attribut latitude directement sur la classe Station
mais en fait, avec les properties il faudrait refaire le même travail pour chaque colonne dans la table !
ce serait plus économique si on pouvait dire:
quand on cherche un attribut sur une Station, on va le chercher dans self.row
use case 2: une API¶
une légère variation: vous implémentez un service d’API c’est-à-dire votre application est adossée à un service web, et vous avez besoin d’envoyer des requêtes à ce service web
pour cela vous créez une classe API
class API:
def __init__(self, url):
self.session = ... # the details of the connectionmais bon vous, ce que vous voulez pouvoir faire, c’est ceci
# l'occasion de dire qu'un context manager aussi serait utile ici
with API("http://webapi.com/") as api:
# mais bon ne nous égarons pas...
book = api.GET(book_id)
# do some changes
api.PUT(book)
...ce qui naïvement impliquerait d’implémenter possiblement plein de méthodes comme GET dans la classe API
mais en fait ces méthodes, elles vont sans doute faire plus ou moins toutes la même chose! et donc c’est tentant de factoriser tout ça, mais comment faire ?
le code¶
la logique, c’est que si l’attribut n’est pas trouvé par la logique usuelle, on va utiliser en dernier recours ces méthodes, si elles existent
ce qui, dans le cas de la classe Station, nous donne quelque chose comme
import pandas as pd
stations = pd.read_csv("../data/stations.txt", index_col='station_id')
class Station:
def __init__(self, station_id):
self.row = stations.loc[station_id]
def __getattr__(self, attribute):
print("in __getattr__")
if hasattr(self.row, attribute):
# we could just as well simply call getattr()
return getattr(self.row, attribute)
else:
# but it's better to make it explicit
# that __getattr__ is expected to raise this exception when relevant
# i.e. do not return None, it would mean the attribute exists and is None
raise AttributeError(attribute)
s = Station(2122)
s.latitude, s.longitudein __getattr__
in __getattr__
(np.float64(48.86685615476034), np.float64(2.2900380734450088))affectation / destruction¶
avec les méthodes spéciales compagnon que sont __setattr__ et __delattr__ on peut également capturer les écritures et destructions d’attributs
juste une précision par rapport à ce qu’on a pu dire, et qui est toujours vrai:
l’écriture d’un attribut se fait directement dans l’objet
dans ce contexte il faut l’interpréter comme: on appellera toujours __setattr__ ou __delattr__ sur l’objet, et jamais sur sa classe
conclusion¶
ce mécanisme - somme toute très simple - peut rendre de grands services, et il est à la portée des programmeurs intermédiaires, on peut l’utiliser sans connaitre le reste de la ménagerie (properties, descripteurs, __getattribute__)
dans les sections suivantes par contre, on va commencer à voir des détails qui sont réservés aux codeurs avancés voire très avancés:)
exercices¶
attr-dynamic-propertiesattr-proxy