<jupyter_start><jupyter_text>Propriété de Python () : ajoutez des attributs gérés à vos classes Avec la propriété () de Python, vous pouvez créer des attributs gérés dans vos classes. Vous pouvez utiliser des attributs gérés, également appelés propriétés, lorsque vous devez modifier leur implémentation interne sans changer l'API publique de la classe. Fournir des API stables peut vous aider à éviter de casser le code de vos utilisateurs lorsqu'ils s'appuient sur vos classes et objets.Les propriétés sont sans doute le moyen le plus populaire de créer des attributs gérés rapidement et dans le plus pur style Pythonic.Dans cette première partie, vous apprendrez à :* Créez des attributs ou des propriétés gérés dans vos classes* Effectuer une évaluation des attributs paresseux et fournir des attributs calculés* Évitez les méthodes setter et getter pour rendre vos classes plus pythoniques* Créer des propriétés en lecture seule, en lecture-écriture et en écriture seule* Créez des API cohérentes et rétrocompatibles pour vos classesVous allez également expérimenter quelques exemples pratiques qui utilisent property() pour valider les données d'entrée, calculer les valeurs d'attribut de manière dynamique, enregistrer votre code, etc. Pour tirer le meilleur parti de ce didacticiel, vous devez connaître les bases de la programmation orientée objet et des décorateurs en Python.  Gestion des attributs dans vos classesLorsque vous définissez une classe dans un langage de programmation orienté objet, vous vous retrouverez probablement avec des attributs d'instance et de classe. En d'autres termes, vous vous retrouverez avec des variables accessibles via l'instance, la classe ou même les deux, selon la langue. Les attributs représentent ou contiennent l'état interne d'un objet donné, auquel vous aurez souvent besoin d'accéder et de muter.En règle générale, vous disposez d'au moins deux manières de gérer un attribut. Soit vous pouvez accéder à l'attribut et le modifier directement, soit vous pouvez utiliser des méthodes. Les méthodes sont des fonctions attachées à une classe donnée. Ils fournissent les comportements et les actions qu'un objet peut effectuer avec ses données et attributs internes.Si vous exposez vos attributs à l'utilisateur, ils font alors partie de l'API publique de vos classes. Votre utilisateur y accédera et les mutera directement dans son code. Le problème survient lorsque vous devez modifier l'implémentation interne d'un attribut donné.Supposons que vous travaillez sur une classe Circle. L'implémentation initiale a un seul attribut appelé `.radius`. Vous finissez de coder la classe et la mettez à disposition de vos utilisateurs finaux. Ils commencent à utiliser Circle dans leur code pour créer de nombreux projets et applications.Supposons maintenant que vous ayez un utilisateur important qui vous présente une nouvelle exigence. Ils ne veulent plus que Circle stocke le rayon. Ils ont besoin d'un attribut public `.diameter`.À ce stade, supprimer `.radius` pour commencer à utiliser `.diameter` pourrait casser le code de certains de vos utilisateurs finaux. Vous devez gérer cette situation autrement que par la suppression de `.radius`.Les langages de programmation tels que Java et C++ vous encouragent à ne jamais exposer vos attributs pour éviter ce genre de problème. Au lieu de cela, vous devez fournir des méthodes getter et setter, également appelées accesseurs et mutateurs, respectivement. Ces méthodes offrent un moyen de modifier l'implémentation interne de vos attributs sans modifier votre API publique.  Quelle aproche getter/setter en PythonCréeon notre première aproche naive (mais correcte) utilisant la notion de getter et setter<jupyter_code>class Point:
    def __init__(self, x, y):
        self._x = x
        self._y = y

    def get_x(self):
        return self._x

    def set_x(self, value):
        self._x = value

    def get_y(self):
        return self._y

    def set_y(self, value):
        self._y = value<jupyter_output><empty_output><jupyter_text>Dans cet exemple, vous créez un point avec deux attributs non publics `._x` et `._y` pour contenir les coordonnées cartésiennes du point à portée de main. > **Note** : Python n'a pas la notion de modificateurs d'accès, tels que private, protected et public, pour restreindre l'accès aux attributs et aux méthodes. En Python, la distinction se fait entre les membres de classe publics et non publics.> Si vous souhaitez signaler qu'un attribut ou une méthode donnée n'est pas publique, vous devez utiliser la convention Python bien connue consistant à préfixer le nom avec un trait de soulignement (_). C'est la raison derrière la dénomination des attributs `._x` et `._y`.> Notez qu'il ne s'agit que d'une convention. Cela ne vous empêche pas, vous et les autres programmeurs, d'accéder aux attributs en utilisant la notation par points, comme dans `obj._attr`. Cependant, violer cette convention est une mauvaise pratique.<jupyter_code>point = Point(12, 5)
point.get_x()
point.get_y()
point.set_x(42)
point.get_x()
# # Les attributs non publics sont toujours accessibles (mais mauvaise paratique faux pas le faire!)~
 print(point._x,  point._y)<jupyter_output><empty_output><jupyter_text>Une meilleure aproche pythoniqueL'approche la plus commune consiste à transformer vos attributs en propriétés.Les propriétés représentent une fonctionnalité intermédiaire entre un attribut simple (ou champ) et une méthode. En d'autres termes, ils vous permettent de créer des méthodes qui se comportent comme des attributs. Avec les propriétés, vous pouvez modifier la façon dont vous calculez l'attribut cible chaque fois que vous en avez besoin.Par exemple, vous pouvez transformer à la fois `.x` et `.y` en propriétés. Avec ce changement, vous pouvez continuer à y accéder en tant qu'attributs. Vous aurez également une méthode sous-jacente contenant `.x` et `.y` qui vous permettra de modifier leur implémentation interne et d'effectuer des actions juste avant que vos utilisateurs n'y accèdent et les mute.  Premiers pas avec la `property()` de PythonLa `property()` de Python est le moyen Pythonique d'éviter les méthodes getter et setter formelles dans votre code. Cette fonction vous permet de transformer des attributs de classe en propriétés ou en attributs gérés. Puisque property() est une fonction intégrée, vous pouvez l'utiliser sans rien importer. De plus, property() a été implémenté en C pour garantir des performances optimales. Avec property(), vous pouvez attacher des méthodes getter et setter à des attributs de classe donnés. De cette façon, vous pouvez gérer l'implémentation interne de cet attribut sans exposer les méthodes getter et setter dans votre API. Vous pouvez également spécifier un moyen de gérer la suppression d'attributs et fournir une docstring appropriée pour vos propriétés. Voici la signature de cette fonction `property(fget=None, fset=None, fdel=None, doc=None)`Les deux premiers arguments prennent des objets fonction qui joueront le rôle de méthodes getter (fget) et setter (fset). Voici un résumé de ce que fait chaque argument : | Paramètre | Description || --------- | ------------| | fget | Fonction qui renvoie la valeur de l'attribut géré | | fset | Fonction qui permet de définir la valeur de l'attribut géré | |fdel | Fonction pour définir comment l'attribut géré gère la suppression | |doc | Chaîne représentant la docstring de la propriété |  Un exemple pour mieux comprendre<jupyter_code>class Circle:
    def __init__(self, radius):
        self._radius = radius

    def _get_radius(self):
        print("Get radius")
        return self._radius

    def _set_radius(self, value):
        print("Set radius")
        self._radius = value

    def _del_radius(self):
        print("Delete radius")
        del self._radius

    radius = property(
        fget=_get_radius,
        fset=_set_radius,
        fdel=_del_radius,
        doc="The radius property."
    )<jupyter_output><empty_output><jupyter_text>Dans cet extrait de code, vous créez Circle. L'initialiseur de classe, `.__init__()`, prend rayon comme argument et le stocke dans un attribut non public appelé `._radius`. Ensuite, vous définissez trois méthodes non publiques :* `._get_radius()` renvoie la valeur actuelle de `._radius`* `._set_radius()` prend la valeur comme argument et l'affecte à `._radius`* `._del_radius()` supprime l'attribut d'instance  `._radius`Une fois ces trois méthodes en place, vous créez un attribut de classe appelé `.radius` (sans l'_)  pour stocker l'objet de propriété. Pour initialiser la propriété, vous passez les trois méthodes comme arguments à property(). Vous transmettez également une docstring appropriée pour votre propriété.Dans cet exemple, vous utilisez des arguments de mot-clé pour améliorer la lisibilité du code et éviter toute confusion. De cette façon, vous savez exactement quelle méthode entre dans chaque argument.<jupyter_code>circle = Circle(42.0)
circle.radius

circle.radius = 100.0
circle.radius

del circle.radius
circle.radius<jupyter_output>Get radius<jupyter_text>Tout ceci est un peu lourd, autre possibilités les décorateursNotre code devient<jupyter_code>class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        """The radius property."""
        print("Get radius")
        return self._radius

    @radius.setter
    def radius(self, value):
        print("Set radius")
        self._radius = value

    @radius.deleter
    def radius(self):
        print("Delete radius")
        del self._radius<jupyter_output><empty_output><jupyter_text>Ce code semble assez différent de l'approche des méthodes getter et setter. Circle semble maintenant plus Pythonic et propre. Vous n'avez plus besoin d'utiliser des noms de méthode tels que ._get_radius(), ._set_radius() et ._del_radius(). Vous disposez maintenant de trois méthodes avec le même nom d'attribut propre et descriptif. Comment est-ce possible?L'approche du décorateur pour créer des propriétés nécessite de définir une première méthode utilisant le nom public de l'attribut géré sous-jacent, qui est `.radius` dans ce cas. Cette méthode doit implémenter la logique getter. Le @property<EMAIL> définit la méthode setter pour `.radius`. Dans ce cas, la syntaxe est assez différente. Au lieu d'utiliser à nouveau @property, vous utilisez @radius.setter.enfin @radius.deleter permet de suprimer l'attribut<jupyter_code>circle = Circle(43.0)
print("circle.radius", circle.radius)

circle.radius = 101.0
print("circle.radius", circle.radius)

del circle.radius
print("circle.radius", circle.radius)<jupyter_output>Get radius
circle.radius 43.0
Set radius
Get radius
circle.radius 101.0
Delete radius
Get radius<jupyter_text>Que sont les descripteurs Python ?Les descripteurs sont des objets Python qui implémentent une méthode du protocole de descripteur, qui vous donne la possibilité de créer des objets qui ont un comportement spécial lorsqu'ils sont accessibles en tant qu'attributs d'autres objets. Ici vous pouvez voir la définition correcte du protocole de descripteur :Protocole descripteur : Un attribut ne doit pas être manipulé directement main à travers une méthode, en voici la liste du protocole descripteur, en python on aurait:```python__get__(self, obj, type=None) -> object__set__(self, obj, value) -> None__delete__(self, obj) -> None__set_name__(self, owner, name)``` Si votre descripteur implémente uniquement `.__get__()`, alors il s'agit d'un descripteur d'une "non-données". S'il implémente `.__set__()` ou `.__delete__()`, on dit qu'il s'agit d'un descripteur de données. Notez que cette différence ne concerne pas seulement le nom, mais c'est aussi une différence de comportement. C'est parce que les descripteurs de données ont la priorité pendant le processus de recherche, comme vous le verrez plus tard.Jetez un œil à l'exemple suivant, qui définit un descripteur qui enregistre quelque chose sur la console lors de l'accès :<jupyter_code># descriptors.py
class Verbose_attribute():
    def __get__(self, obj, type=None) -> object:
        print("accessing the attribute to get the value")
        return 42
    def __set__(self, obj, value) -> None:
        print("accessing the attribute to set the value")
        raise AttributeError("Cannot change the value")

class Foo():
    attribute1 = Verbose_attribute()

my_foo_object = Foo()
x = my_foo_object.attribute1
print(x)<jupyter_output>accessing the attribute to get the value
42
{'fraction_non_alphanumeric': 0.060564845187615006, 'fraction_numerical': 0.002560204816385311, 'mean_word_length': 4.679236710586097, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '130683', 'license': 'MIT', 'n_tokens_mistral': 3748, 'n_tokens_neox': 3745, 'n_words': 1625, 'path': 'Notebook/objets/Python_attributs_descripteurs.ipynb', 'repo_name': 'opentrainingcamp/python', 'size': '12506'}