Partage
  • Partager sur Facebook
  • Partager sur Twitter

Conseil pour les classes

Anonyme
    23 février 2013 à 12:27:44

    Dans la documentation, dis moi où tu vois l'absence de self dans une méthode en utilisant les property?

    Ensuite, pourquoi utilises-tu cette forme (fonctions dans une fonction)? En python on l'utilise souvent pour faire de la décoration de fonction.

    Je n'ai pas testé et je te fais confiance si tu dis que ça fonctionne, mais par convention, la lecture de ton code me choque, d'ailleurs qu'est-ce qui fonctionne, parce-que je ne vois pas trop le but de ce code finalement?

    Je ne vois par exemple pas l'intérêt de retourner locals()

    L'espace entre nom de méthode et parenthèse ouvrante me gêne...

    next_count et next_count() c'est différent, voici un exemple

    >>> class A:
    ...     def method(self):
    ...         pass
    ... 
    >>> a = A()
    >>> a.method
    <bound method A.method of <__main__.A object at 0x7ff9aa9c7a90>>
    >>> a.method()
    >>> 
    
    >>> type(a.method)
    <class 'method'>
    >>> type(a.method())
    <class 'NoneType'>
    

    Le principe est de ne pas distinguer attribut et requête à l'extérieur de la classe.

    Certes comment pourrait-il en être autrement?

    Le fait que ce soit un attribut ou une requête est une affaire d'implémentation.

    Il va vraiment falloir que tu sois clair sur l'interprétation du mot requête, j'ai l'impression qu'on ne parle pas de la même chose. Est-ce une action, c'est à dire une fonction, voir plus précisément une méthode?

    Rare son les langages (j'en connais deux) qui l'applique sur attribut, requête et commande.

    Mais applique quoi?
    • Partager sur Facebook
    • Partager sur Twitter
      23 février 2013 à 14:20:40

      J'aime bien l'idée de ta métafonction Property. Même si elle impose quelques contortions en Python, ton code fonctionne parfaitement chez moi avec Python 3.2.

      Cela dit, un code un peu plus pythonique serait peut-être celui-ci :

      class Counter:
          def __init__ (self, v):
              self._count = 0
              self._step = v
       
          @property
          def count(self):
              return self._count
          
          @count.setter
          def count(self, v):
              self._count = v
       
          @property
          def step(self):
              return self._step
       
          @property
          def peek(self):
              return self._count + self._step
       
          def __next__(self):
              self._count += self._step
              return self._count
      
          def increment(self):
              self._count = self.next_count
      
      if __name__ == '__main__':
          a = Counter(3)
          print(a.step)   # 3
          a.count = 4
          print(a.count)  # 4
          print(next(a))  # 7
          print(a.peek)   # 10
          print(a.count)  # 7
          print(next(a))  # 10
      

      fred a écrit:

      Dans la documentation, dis moi où tu vois l'absence de self dans une méthode en utilisant les property?

      Cela n'a pas d'importance dans ce cas : la première définition de fonction (une fonction statique à l'intérieur du namespace de la classe) ne sert qu'à donner un nom à sa property. Son décorateur Property se charge de récupérer le getter et le setter (grâce au return locals()) et de créer le descripteur correspondant, à l'aide de la built-in property.

      C'est astucieux et plutôt bien vu. Ça permet de démarquer visuellement (grâce au niveau d'indentation) les définitions des properties de la classe, même si ça ne change rien sémantiquement. Le seul moyen de faire moins verbeux et plus lisible serait de créer une méta-classe qui fasse plus ou moins la même chose que sa fonction Property, ce qui éviterait d'avoir à se taper le return locals() (qu'on peut facilement oublier), ou bien peut-être plus simple encore, d'en faire un décorateur de classe.

      Le fait que ce soit un attribut ou une requête est une affaire d'implémentation.

      Il va vraiment falloir que tu sois clair sur l'interprétation du mot requête, j'ai l'impression qu'on ne parle pas de la même chose. Est-ce une action, c'est à dire une fonction, voir plus précisément une méthode?

      Ce qu'il entend par "requête", c'est qu'en POO, lorsque tu appelles une méthode, on peut voir ça comme un passage de message plutôt qu'un appel de fonction. La différence entre les deux est simplement le point de vue que tu prends : dans le premier cas, tu envoies un message à ton objet (=une requête) pour qu'il te fournisse un service (ici, la valeur actuelle de l'une de ses propriétés), dans le second, tu as le point de vue du développeur, qui sait que dans tel langage, on envoie un message à une instance en appelant l'une de ses méthodes.

      En théorie, il ne devrait y avoir aucune différence entre la récupération d'un attribut ou l'appel d'une méthode : les deux sont des requêtes que tu envoies à un objet pour qu'il te fournisse un service (et te retourne le résultat le cas échéant). D'un point de vue abstrait, ce sont deux choses identiques.

      Dans la réalité, pour que les langages objets fonctionnent et soient pratiques à utiliser — et aussi parce que depuis C et Pascal, l'énorme majorité des développeurs habitués aux langages impératifs fait instinctivement la différence entre un membre d'une structure et une fonction ou une procédure —, on continue à faire la distinction en laissant apparents ces détails d'implémentation.

      Si tu es curieux de voir un langage qui respecte cette abstraction, tu devrais jeter un oeil à SmallTalk (le plus vieux langage orienté objet encore vivant à ce jour).

      -
      Edité par nohar 23 février 2013 à 14:27:55

      • Partager sur Facebook
      • Partager sur Twitter
      Zeste de Savoir, le site qui en a dans le citron !
      Anonyme
        23 février 2013 à 14:45:36

        @nohar

        Ton code est lisible, je comprend tout à fait et ça change tout pour moi, mais...

        D'un point de vue abstrait, ce sont deux choses identiques.

        Eh bien justement, le point de vue abstrait et la POO ça fait pas très bon ménage, certes l'appel est très ressemblant, certes la requête se fait de la même manière, mais concrètement, l'un et l'autre sont tout à fait différent et n'ont pas le même rôle.

        • Partager sur Facebook
        • Partager sur Twitter
          23 février 2013 à 14:55:48

          fred1599 a écrit:

          @nohar

          Ton code est lisible, je comprend tout à fait et ça change tout pour moi, mais...

          D'un point de vue abstrait, ce sont deux choses identiques.

          Eh bien justement, le point de vue abstrait et la POO ça fait pas très bon ménage, certes l'appel est très ressemblant, certes la requête se fait de la même manière, mais concrètement, l'un et l'autre sont tout à fait différent et n'ont pas le même rôle.

          C'est parce que tu es habitué au data model de Python, qui prend quelques raccourcis pour simplifier le boulot des développeurs (et qui fait d'ailleurs que j'apprécie ce langage plus que les autres).

          Pour simplifier, les deux n'ont pas le même rôle pour le développeur.

          L'utilisateur de la classe, lui, il s'en fout : son périmètre d'action s'arrête (en théorie, mais Python nous laisse plus libres que ça) à tout ce qu'un objet peut faire pour lui une fois qu'il l'a instancié, à savoir les services que l'objet peut lui rendre. En d'autres termes, tout ce qui l'intéresse c'est l'interface publique de son objet, au regard de laquelle appeler une méthode ou récupérer un attribut sont la même chose.

          -
          Edité par nohar 23 février 2013 à 14:57:17

          • Partager sur Facebook
          • Partager sur Twitter
          Zeste de Savoir, le site qui en a dans le citron !
          Anonyme
            23 février 2013 à 17:41:15

            @nohar

            Ok, merci, je vois mieux son point de vue, c'est vrai que j'ai pensé à l'autre côté de la force... ;)

            • Partager sur Facebook
            • Partager sur Twitter
              23 février 2013 à 20:25:23

              Le principe d'accès uniforme est fort bien décrit ici. Peu de langage le supportent. L'appliquer revient à s'interdire d'avoir des attributs dans les classes d'interfaces. Mais l'article explique aussi qu'avec les property, on peut coder en Python sans se prendre le chou avec ces questions

              • Partager sur Facebook
              • Partager sur Twitter
                24 février 2013 à 12:31:22

                C'est vrai que l'exemple de nohar est plus lisible et plus simple à comprendre. Je voulez juste illustrer le principe d’accès uniforme. La solution que j'ai utilisé demande à être travaillée, mais comme le souligne nohar:

                    Ça permet de démarquer visuellement (grâce au niveau d'indentation) les définitions des properties de la classe, même si ça ne change rien sémantiquement.

                fred1599

                    L'espace entre nom de méthode et parenthèse ouvrante me gêne...

                J'ai découvert ce style d'écriture dans des langages et je l'ai approuvé pour deux raisons :
                - Respect des conventions de la langue anglaise
                Lisibilité parfois accrue 

                Je me suis donc forcé à le faire systématiquement, c'st maintenant une habitude ...

                fred1599

                    Il va vraiment falloir que tu sois clair sur l'interprétation du mot requête, j'ai l'impression qu'on ne parle pas de la même chose. Est-ce une action, c'est à dire une fonction, voir plus précisément une méthode ?

                On l'a très bien dit dans un post précédent :

                    nohar

                        En théorie, il ne devrait y avoir aucune différence entre la récupération d'un attribut ou l'appel d'une méthode : les deux sont des requêtes que tu envoies à un objet pour qu'il te fournisse un service (et te retourne le résultat le cas échéant). D'un point de vue abstrait, ce sont deux choses identiques.

                Une caractéristique est soit un attribut ou une routine.

                Une routine est soit une fonction ou une procédure. Sachant que certains englobe la notion de procédure dans celle de fonction ...

                Une requête est appliquée sur un objet. Comme son nom l'indique elle constitue une question posée à un objet. Elle donne un résultat. Dans ce sens une requête est soit un attribut (requête mémorisée) ou une routine renvoyant un résultat (requête calculée). En théorie une requête ne devrait pas modifier l'état publique d'un objet.

                'a.count' et 'a.nexy_count' sont des requêtes. L'une est un attribut et l'autre une routine donnant un résultat.

                Une commande est une action appliquée sur un objet. Elle modifie l'état de l'objet.

                fred1599

                    je ne vois pas trop le but de ce code finalement.

                Ici il n'en a pas vraiment. Mais je vais te donner un cas d'utilisation :

                 Imaginons qu'on ait une classe A avec un attribut 'x'. Des programmes utilisent des objets de type A et, en bon pythoniste qu'ils sont, accèdent et modifient l'attribut directement : 

                class A:
                	def __init (self, v, u):
                		self.x = v
                		self.y = u
                a = A (2, 1)
                b = a.x  # b vaut 2
                a.x = 5
                b = a.x  # b vaut 5

                Imaginons maintenant que tu as une classe B qui hérite de A et que l'attribut 'x' n'a plus lieu d'être, car on peut et on souhaite le remplacer par une fonction. B respecte l'interface de A à l'exception de l'attribut 'x'. Conceptuellement il y a un sens de dire qu'un objet de type B est un objet de type A. L'ensemble des programmes utilisant des objets de type A devraient donc fonctionner avec des objets de type B.

                C'est pour cette raison que je conseillais d'utiliser des accesseurs/ mutateurs au lieu d'accéder/modifier directement l'attribut 'x'. Mais c'était sans connaître les décorateurs et notamment le décorateur 'property' !

                En effet grâce à ce décorateur tu peux conformer l'interface de B à celle de A:

                class B (A):
                	def __init__ (self, u):
                		self.y = u
                	@property
                	def x (self):
                		return self.y + 2
                	@x.setter
                	def x (self, v):
                		self.y -= v - 2

                Ainsi les programmes écrits pour des objets de type A fonctionne avec des objets de type B. L’accès et la modification directe d'attribut en Python est donc une pratique qui ne rentre pas en conflit avec la conformance d'interface entre hériter et classe mère (grâce à la notion de 'property').

                nohar

                    Dans la réalité, pour que les langages objets fonctionnent et soient pratiques à utiliser — et aussi parce que depuis C et Pascal, l'énorme majorité des développeurs habitués aux langages impératifs fait instinctivement la différence entre un membre d'une structure et une fonction ou une procédure —, on continue à faire la distinction en laissant apparents ces détails d'implémentation.

                En effet, (à mon grand regret).

                C'est pour cela que je dis rare son les langages appliquant le principe d’accès uniforme. J'en ai montré un sans le cité : Eiffel.

                Dans ce langage appeler un attribut ou une routine respecte la même syntaxe et la même sémantique :

                b := a.an_attribute
                b := a.a_function
                b := a.another_function ("x")
                a.a_procedure
                a.another_procedure (1)

                 mps

                     L'appliquer revient à s'interdire d'avoir des attributs dans les classes d'interfaces

                Et pourquoi donc ?

                Justement l'intérêt lorsque le langage le respecte est aussi de pouvoir rendre effective une caractéristique abstraite comme attribut ou de remplacer une fonction par un attribut ou inversement.

                -
                Edité par Conaclos 24 février 2013 à 12:47:03

                • Partager sur Facebook
                • Partager sur Twitter
                  24 février 2013 à 17:21:24

                  Conaclos a écrit:

                  La solution que j'ai utilisé demande à être travaillée

                  Il en faut assez peu en fait :

                  def Property(cls):
                  
                  fget = cls.__dict__.get('getter', None)
                  fset = cls.__dict__.get('setter', None)
                  fdel = cls.__dict__.get('deleter', None)
                  return property(fget, fset, fdel, cls.__doc__)
                  

                  class Counter:

                  def __init__ (self, v):
                      self._count = 0
                      self._step = v
                  
                  @Property
                  class count:
                      def getter(obj):
                          return obj._count
                  
                      def setter(obj, val):
                          obj._count = val
                  
                  @property
                  def step(self):
                      return self._step
                  
                  @property
                  def peek(self):
                      return self._count + self._step
                  
                  def __next__(self):
                      self._count += self._step
                      return self._count
                  

                  if name == 'main':

                  a = Counter(3)
                  print(a.step)   # 3
                  a.count = 4
                  print(a.count)  # 4
                  print(next(a))  # 7
                  print(a.peek)   # 10
                  print(a.count)  # 7
                  print(next(a))  # 10
                  
                  </pre>

                  Le seul truc un peu gênant, c'est l'utilisation du mot-clé class qui ne veut rien dire dans ce contexte.

                  -
                  Edité par nohar 24 février 2013 à 17:35:01

                  • Partager sur Facebook
                  • Partager sur Twitter
                  Zeste de Savoir, le site qui en a dans le citron !
                    24 février 2013 à 22:57:31

                    Ta solution est très élégante, mais c'est vrai que l'utilisation du mot clé 'class' est un peu dérangente.

                    Un petit ajout pourrait être pas mal :

                    def Property(cls):
                    	name = cls.__name__
                    	fget = cls.__dict__.get('getter', eval ("lambda self: self._" + cls.__name__))
                    	fset = cls.__dict__.get('setter', None)
                    	fdel = cls.__dict__.get('deleter', None)
                    	return property(fget, fset, fdel, cls.__doc__)

                    ou sans évaluation dynamique :

                    def Property(cls):
                    	name = cls.__name__
                    	fget = cls.__dict__.get('getter', lambda self: self.__dict__ [name]))
                    	fset = cls.__dict__.get('setter', None)
                    	fdel = cls.__dict__.get('deleter', None)
                    	return property(fget, fset, fdel, cls.__doc__)

                    J'ai travaillé aussi de mon coté à une solution. Voilà ce que j'ai obtenu :

                    On a plusieurs cas de figure :
                    - L'attribut est accessible et mutable : pas besoin de décorateur
                    - L'attribut est accessible mais doit pouvoir être modifié de manière contrôlé : l'accesseur sera un simple getter
                    - Nous avons besoin de spécifier l’accesseur et le mutateur

                    C'est dans les deux derniers cas que notre décorateur est utile. On constate qu'il y aura toujours un setter.
                    En revanche l'accesseur peut-être inféré, ce qui permettrait de donner moins de travail au développeur.

                    def properties (*names):
                    	"""Require a setter with the property name prefixed with 'set_'.
                    	If a function is named as the property then it is the getter
                    	else create a new function given the field named like property prefixed with '_'.
                    	"""
                    	def decorator (a_class):
                    		features = a_class.__dict__
                    		for name in names:
                    			fset = features ["set_" + name]
                    			fdel = features.get ("del_" + name, None)
                    			fget = features.get (name, eval ("lambda self: self._" + name))
                    			exec ("a_class." + name + " = property (fget, fset, fdel)")
                    		return a_class
                    	return decorator

                    Il y a quelques inconvénients :

                    - la présence de l'évaluation dynamique de code ... mais je n'est pas trouvé d'autres solutions (si vous en avez ...).
                    - La convention de nommage avec un souligné pour les attributs. Mais cette convention est communément utilisée
                    - La convention de nommage pour les mutateurs
                    - La centralisation des définitions de propriétés
                    - On perd l'idée de départ : regrouper la déclaration de getter et setter 

                    Voici un exemple d'utilisation :

                    @properties ("x", "y")
                    class Test:
                    	def __init__ (self, v):
                    		self.set_x (v)
                    
                    	def set_x (self, v):
                    		self._x = v
                    
                    	def y (self):
                    		return self._x + 2
                    
                    	def set_y (self, v):
                    		self._x = v - 2
                    t = Test (2)
                    t.x = 0
                    t.y # 2
                    t.y = 3
                    t.x # 1





                    -
                    Edité par Conaclos 24 février 2013 à 23:46:31

                    • Partager sur Facebook
                    • Partager sur Twitter
                      25 février 2013 à 11:04:48

                      En fait, je pense que je me contenterais de ma solution avec le mot-clé class. Ce serait plus sympa si on avait un mot-clé namespace, de façon à pouvoir définir des espaces de noms autrement qu'en créant des modules ou des classes, mais ce n'est pas non plus si gênant que ça.

                      Ce n'est pas non plus aussi choquant qu'en C++ où une métafonction se définit avec le mot-clé struct... :D

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Zeste de Savoir, le site qui en a dans le citron !
                        25 février 2013 à 11:38:04

                        En effet et puis il y a bien le décorateur singleton qui substitue une classe par une fonction ...
                        On peut bien avoir un décorateur qui remplace une classe par une propriété.

                        Un autre avantage de ta solution est la documentation de la propriété qui est mal gérée sur celle que j'ai développée ...

                        • Partager sur Facebook
                        • Partager sur Twitter
                          25 février 2013 à 19:27:49

                          Camardes, j'aurai besoin de votre aide, je m'entraîne avec les classes et j'essaies ce code :

                          class Unite :
                            def __init__(self, case, nom):
                              self.case = case
                              self.nom = nom
                            def position(cls):
                              print("Votre {0} est sur la case {1}.".format(cls.nom, cls.case))
                            def deplacer(self):
                              self.case += 1
                          
                          

                          Python me dit no Docstring, see Docs lorsque je fais tank16 = Unite(...) ( Le seizième essaie d'unitée), le message no docstrings, see docs s'afiche lorsque je veux entrer une valeur dans les (...). Pour moi, il  n'y a pas de faute. Alors faudrait m'expliquer tout de même. Merce de vos futurs répones

                          Au fait, ne dites pas tout comme si c'était évident, mettez-vous à mon niveau et expliquez-moi point par point mon erreur.

                          • Partager sur Facebook
                          • Partager sur Twitter
                            25 février 2013 à 21:02:09

                            Ta méthode position n'est pas une méthode de classe mais une méthode d'instance, donc par convention tu ne devrais pas nommer son argument cls mais self. Par convention aussi (La PEP-8, mangez-en !), tu devrais passer une ligne entre tes déclarations de méthode (et FYI, entre deux déclarations de classe, c'est deux lignes qu'il est recommandé de passer). Ça ne coûte rien et ça rend ton code plus lisible et aéré.

                            Montre nous le reste de ton code, STP, parce que le problème ne vient certainement pas de cette classe.

                            -
                            Edité par nohar 25 février 2013 à 21:06:11

                            • Partager sur Facebook
                            • Partager sur Twitter
                            Zeste de Savoir, le site qui en a dans le citron !
                              25 février 2013 à 21:25:48

                              Je vais peut-être t'étonner mais figure-toi que je n'ai rien oublié de dire, d'ailleurs c'est sur IDLE que j'ai fait ce test. Je viens d'essayer, j'ai remplacé cls par self et cela a marché mais dès lors, j'ai une question : Quand doit-on mettre self ou cls ? (Je tiens à préciser que je ne suis pas encore au Tp : dictionnaire, mais que je relis régulièrement ce que j'ai vu sur les classes et que je tente de faire des test.

                              J'en viens à une autre question : Peut-on me conseiller sur l'héritage multiple ? C'est un point trop rapidement abordé à mon goût. Ainsi que sur l'hértage simple ,s'il vous plaît ?

                              • Partager sur Facebook
                              • Partager sur Twitter
                                25 février 2013 à 21:43:10

                                Rh4ck a écrit:

                                Je vais peut-être t'étonner mais figure-toi que je n'ai rien oublié de dire, d'ailleurs c'est sur IDLE que j'ai fait ce test. Je viens d'essayer, j'ai remplacé cls par self et cela a marché mais dès lors, j'ai une question : Quand doit-on mettre self ou cls ?

                                cls c'est lorsque l'argument est supposé être une classe et non une instance (= un objet). En gros, c'est utilisé presque uniquement lorsque tu définis une méthode de classe (avec le décorateur classmethod()), soit quasiment jamais.

                                J'en viens à une autre question : Peut-on me conseiller sur l'héritage multiple ?

                                Si tu crois que tu as besoin de l'héritage multiple, tu fais sûrement fausse route. Si tu es vraiment certain d'avoir besoin de l'héritage multiple, c'est sûrement la seule solution.

                                En Python, à ton niveau, ne l'utilise jamais et fuis-le comme la peste.

                                En fait, l'héritage tout court est lui-même rarement vraiment utile en Python. Il te permet surtout de définir une classe qui se comporte pratiquement comme une autre qui existe déjà sans en retaper le code. Il y aurait beaucoup de choses à en dire, mais il vaut mieux que tu poursuives ton cours jusqu'à ce que tu tombes sur le sujet.

                                -
                                Edité par nohar 25 février 2013 à 21:53:11

                                • Partager sur Facebook
                                • Partager sur Twitter
                                Zeste de Savoir, le site qui en a dans le citron !
                                  26 février 2013 à 22:53:38

                                  Rh4ck

                                      J'en viens à une autre question : Peut-on me conseiller sur l'héritage multiple ? C'est un point trop rapidement abordé à mon goût. Ainsi que sur l'hértage simple ,s'il vous plaît ?

                                  Dans un langage statiquement typé l'héritage est nécessaire, car il permet de donner une plus grande souplesse au typage.

                                  Cependant dans un duck-typing, comme celui de Python, l'intérêt est moins important. Tu conserve tout de même le deuxième avantage de l'héritage : la réutilisation de code déjà écrit.

                                  Je pense que nohar te conseil de l'éviter car il est souvent mal utilisé. Il est souvent préférable d'utiliser une relation cliente, elle offre davantage de flexibilité.

                                  L'utilisation de l'héritage multiple en Python peut également mener à un "conflit conceptuel" d'interface ...

                                  • Partager sur Facebook
                                  • Partager sur Twitter

                                  Conseil pour les classes

                                  × 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