Partage
  • Partager sur Facebook
  • Partager sur Twitter

Problème avec l'encapsulation

    28 février 2016 à 19:59:26

    Bonjour,

    Désolé de poser une question aussi basique mais j'ai cherché sur le net et sur le forum, je n'ai pas trouvé ce qui coince. Lorsque j'essaie d'utiliser la fonction property, il m'arrive systématiquement la même chose :

    class Classe:
    	def __init__(self, attribut):
    		self._attribut = attribut
    		
    	def _get_attribut(self):
    		print('on est dans _get_attribut')
    		return self._attribut
    		
    	def _set_attribut(self, attribut):
    		print('on est dans _set_attribut')
    		self._attribut = attribut
    		
    	attribut = property(_get_attribut, _set_attribut)



    >>> instance = Classe(0)
    >>> instance.attribut
    on est dans _get_attribut
    0
    >>> instance.attribut = 3
    >>> instance.attribut
    3
    

    L'encapsulation marche pour _get_attribut, ne marche pas pour _set_attribut et ne marche plus pour _get_attribut une fois que j'ai essayé d'assigner une valeur à mon attribut.

    Merci d'avance

    PS : l'exemple du cour copié collé me fait le même genre de soucis. J'ai vu sur le net dans les sites anglosaxons une autre syntaxe, @property mais je ne m'en sers pas correctement je pense.

    -
    Edité par Zebolt 28 février 2016 à 20:00:44

    • Partager sur Facebook
    • Partager sur Twitter
      28 février 2016 à 20:31:37

      Je m'y connais absolument pas en property, mais j'ai fait un petit test et effectivement ton code ne fonctionne pas. Masi seulement dans la version 2 de python. Dans cette version il distingue les variables _attribut et attribut lors de l'affectation (ce qui me semble être assez logique) mais apparemment le comportement à changer dans la version 3 (à approfondir). Pour t'en rendre compte, il suffit d'utiliser la méthode spéciale __dict__ (je l'ai utilisé après avoir redéfini la valeur de attribut):

      #En python 3
      instance.__dict__
      {'_attribut': 3}
      
      
      #Alors qu'en version 2
      instance.__dict__
      {'_attribut': 0, 'attribut': 3}


      Bref, pourquoi? comment? J'en sais rien, mais c'est de la d'où vient ton problème. Les autres pourront sûrement mieux t'éclairer ^^

      • Partager sur Facebook
      • Partager sur Twitter
      Précepte: Le mieux est l'ennemi du bien
        28 février 2016 à 20:44:01

        Merci beaucoup pour ta réponse.
        Du coup que dois-je faire? Passer au python3 ? Ne pas encapsuler mes données? Les encapsuler d'une autre façon?
        • Partager sur Facebook
        • Partager sur Twitter
          28 février 2016 à 20:59:44

          Passer à python 3 c'est sûr (problème d'encapsulation ou pas) :D.

          Après pour l'encapsulation (je suis pas sûr que ça s'appelle ainsi), je ne pense pas que ce soit la philosophie de python. Disons qu'on indique de manière implicite (avec effectivement l'utilisation de l'underscore) qu'il faut éviter de toucher à un attribut ou une méthode (en général parce que son utilisation est interne à la classe) mais on ne le fait pas de manière explicite (getter, setter, deleter). Bon après je suppose qu'il existe une philosophie pour leur utilisation (sinon pourquoi les avoir intégrer?), mais j'avoue ne pas la connaître :p

          • Partager sur Facebook
          • Partager sur Twitter
          Précepte: Le mieux est l'ennemi du bien
            28 février 2016 à 21:06:22

            Ok merci beaucoup, je passe à python3. Je révise le python dans le but d'utiliser Kivy, et quand j'essayais de faire tourner leurs exemples avec python3 ça marchait pas tout le temps, du coup j'étais pas très motivé. Je verrais ça plus tard finalement.
            Bonne soirée:D
            • Partager sur Facebook
            • Partager sur Twitter
              28 février 2016 à 21:35:21

              Les properties ne fonctionnent en Python 2 que pour les new-style classes, c'est à dire celle qui héritent de object.

              Mais migrer vers Python 3 reste une très bonne idée.

              -
              Edité par entwanne 28 février 2016 à 21:36:20

              • Partager sur Facebook
              • Partager sur Twitter
                29 février 2016 à 11:50:15

                Olygrim a écrit:

                Après pour l'encapsulation (je suis pas sûr que ça s'appelle ainsi), je ne pense pas que ce soit la philosophie de python.

                Tu as raison Olygrim, en Python on expose directement les attributs d'instance. Cependant les programmeurs Java te diront que tu vas te casser les dents, car si tu veux changer le comportement de ta classe lors de l'affectation d'un attribut de classe (par exemple) et bien tu es coincé. Tu ne sauras plus le faire. Imaginons que tu as créé une classe Cercle comme ceci

                class Cercle:
                
                def __init__(self, rayon):
                    self.rayon = rayon
                
                </pre>

                Imagine que tu veux maintenant ajouter comme logique que si l'utilisateur veut changer la dimension du rayon, ton programme limite le rayon à une valeur supérieure ou égale à 1. Te voilà coincé, car les utilisateurs de ta classe ont déjà dans leur code des lignes ressemblant à ça:

                mon_cerlce = Cercle(10)
                mon_cercle.rayon -= 20 # Argh un cercle avec un rayon négatif
                

                Donc il semble être trop tard pour rectifier le tir sans casser le code des utilisateurs... Et pourtant, non ! Python a eu l'excellente idée d'utiliser les Descripteurs pour accéder aux attributs d'instances. Ca veut dire quoi ? Sans entrer dans les détails que tu peux trouver dans le lien donné, ou encore ici, ça veut dire que tu peux changer la manière ton python accède au rayon avec une propriété get, set, et/ou delete.

                En pratique, en changeant ton code comme ceci, tu pourras limiter les valeurs données à rayon à des valeurs supérieures ou égale à 1:

                class Cercle:
                
                def __init__(self, rayon):
                    self.rayon = rayon 
                    # Ceci appelle notre setter qui va créer
                    # un attribut self._rayon
                
                def _get_rayon(self):
                    print("Dans le getter")
                    return self._rayon
                
                def _set_rayon(self, value):
                    print("Dans le setter.")
                    value = max(1, value)
                    self._rayon = value
                
                rayon = property(_get_rayon, _set_rayon, doc="Rayon du cercle. Toujours 1 ou plus.")
                # Note que rayon est devenu un attribut de classe !
                

                mon_cercle = Cercle(10) mon_cercle.rayon -= 10 print(mon_cercle.rayon)

                </pre>

                C'est pour ça qu'en Python, on ne doit pas faire des getter / setter si leur comportement est juste de lire ou écrire la valeur donnée. Si plus de logique est nécessaire par la suite, on pourra utiliser property.

                Note : on peut utiliser des décorateurs au lieu de la fonction property pour obtenir le même résultat.

                class Cercle:
                
                def __init__(self, rayon):
                    self.rayon = rayon 
                    # Ceci appelle notre setter qui va créer
                    # un attribut self._rayon
                
                @property
                def rayon(self):
                    "Rayon du cercle. Toujours 1 ou plus."
                    print("Dans le getter")
                    return self._rayon
                
                @rayon.setter
                def rayon(self, value):
                    print("Dans le setter.")
                    value = max(1, value)
                    self._rayon = value
                
                </pre>

                -
                Edité par Dan737 29 février 2016 à 11:53:12

                • Partager sur Facebook
                • Partager sur Twitter
                  29 février 2016 à 12:41:47

                  Merci beaucoup Dan, ton explication est extrêmement claire :).

                  Quand je pense classe, j'ai l'habitude de voir le changement d'un attribut au travers d'une méthode (d'où on peut contrôler le-dit changement). Mais effectivement si le changement se fait directement (affectation), alors le contrôle ne peut pas se faire. Et c'est là que property est utile.

                  Et donc si je comprends bien, rayon est à la base un attribut d'instance, mais en utilisant un property ça devient un attribut de classe et un nouvel attribut d'instance est créé qui s'appelle _rayon. Et en fait c'est ce dernier qui est modifier/renvoyer/effacer quand on utilise mon_cercle.rayon par l'intermédiaire de l'attribut de classe rayon (qui est un property object) o_O

                  Faut garder la tête froide quand on utilise ce mécanisme :lol:

                  -
                  Edité par Olygrim 29 février 2016 à 12:44:39

                  • Partager sur Facebook
                  • Partager sur Twitter
                  Précepte: Le mieux est l'ennemi du bien
                    29 février 2016 à 13:06:23

                    Olygrim a écrit:

                    Et donc si je comprends bien, rayon est à la base un attribut d'instance, mais en utilisant un property ça devient un attribut de classe et un nouvel attribut d'instance est créé qui s'appelle _rayon. Et en fait c'est ce dernier qui est modifier/renvoyer/effacer quand on utilise mon_cercle.rayon par l'intermédiaire de l'attribut de classe rayon (qui est un property object) o_O

                    C'est ça.

                    L'attribut _rayon est secondaire, tu pourrais avoir une property qui ne soit reliée à aucun attribut réel de l'objet. Ici, _rayon sert simplement à stocker une valeur associée à la property rayon.

                    -
                    Edité par entwanne 29 février 2016 à 13:06:33

                    • Partager sur Facebook
                    • Partager sur Twitter
                      29 février 2016 à 13:32:17

                      Effectivement _rayon ne sert qu'à stocker une valeur. Voilà pourquoi il est créé à l'utilisation du setter. Si j'essaie d'accéder à rayon avant d'avoir fait une affectation(dans la classe ou à l'extérieur), python me renvoie une erreur:

                      class Cercle:
                          def _get_rayon(self):
                              print("Dans le getter")
                              return self._rayon
                      
                          def _set_rayon(self, value):
                              print("Dans le setter")
                              value = max(1, value)
                              self._rayon = value
                      
                          rayon = property(_get_rayon, _set_rayon, doc="Rayon du cercle. Toujours 1 ou plus.")
                      
                      mon_cercle = Cercle()
                      #mon_cercle.rayon = 12  #Si pas d'affectation, python renvoie un AttributeError
                      print(mon_cercle.rayon)


                      Mais quelle est l'utilité d'avoir un property si ce n'est pas pour y attacher (à un moment ou un autre) une valeur? (À moins que je n'ai pas compris ce que tu voulais dire par: «tu pourrais avoir une property qui ne soit reliée à aucun attribut réel de l'objet»)

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Précepte: Le mieux est l'ennemi du bien
                        29 février 2016 à 13:38:34

                        Je veux dire par là qu'il n'y a pas forcément de correspondance exacte entre une propriété et un attribut réel.

                        Dans cet exemple, j'utilise deux propriétés pour un mettre attribut, et aucun attribut ne stocke la température en degrés Fahrenheit. La valeur retournée peut être le résultat d'un calcul.

                        -
                        Edité par entwanne 29 février 2016 à 13:39:48

                        • Partager sur Facebook
                        • Partager sur Twitter
                          29 février 2016 à 14:51:43

                          Ok, j'ai compris. celsius et fahrenheit sont deux propriétés attachées à l'attribut value.

                          En fait je n'avais pas remarqué que Dan dans son setter avait fait un self._rayon = value. Je pensais que c'était le setter qui créait automatiquement cette variable. C'est pour ça que je ne comprenais pas pourquoi dans ton code je ne trouvais pas _celsius ou _value :p. J'avais mal compris le fonctionnement, mais c'est une bonne chose car en fait c'est beaucoup plus simple à comprendre.

                          En fait on peut voir l'utilisation de property comme une redéfinition individuelle des méthodes spéciales __get__, __set__ et __delete__ pour chaque attribut concerné (mais toujours au niveau de la classe). Bon peut-être que cette dernière phrase n'est pas claire, mais en tout cas j'ai bien compris le fonctionnement :lol:.

                          EDIT: @entwanne: Les exemples progressifs de ton cours permettent de bien comprendre ce qui se passe. Merci ^^

                          -
                          Edité par Olygrim 29 février 2016 à 14:56:01

                          • Partager sur Facebook
                          • Partager sur Twitter
                          Précepte: Le mieux est l'ennemi du bien

                          Problème avec l'encapsulation

                          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                          • Editeur
                          • Markdown