IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Une fonction crée_vaisseau qui ne trouve pas un attribut d'une classe.


Sujet :

Python

  1. #1
    Membre confirmé
    Homme Profil pro
    sans
    Inscrit en
    Mai 2023
    Messages
    316
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : sans

    Informations forums :
    Inscription : Mai 2023
    Messages : 316
    Par défaut Une fonction crée_vaisseau qui ne trouve pas un attribut d'une classe.
    Salut


    Ma classe Humain contient bien l'attribut Camp mais une fonction ne trouve pas cet attibut.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class Humain(C_player) :
        def __init__(self) :
            super().__init__()
            # ***************** Caractéristiques ***************
            self.Nom="Humain"
            self.Camp=0
    La fonction qui ne sait pas trouver l'attribut Camp :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def Cree_vaisseau(nom,arme) :
     
        # Attribue la classe Vaisseau()
        vaisseau = Vaisseau
     
        vaisseau.Nom=nom
     
        # Définit les armes voulues.
        vaisseau.Armes=arme
     
        if joueur.Camp==0 :
            # Etc....
    Le message d'erreur :

    if joueur.Camp==0 :
    AttributeError: type object 'Humain' has no attribute 'Camp'


    EDIT :

    Apparement, j'ai plus le message d'erreur après avoir ajouté des parenthèses ici : vaisseau = Vaisseau()

    Qu'en dites-vous ?

  2. #2
    Membre Expert
    Homme Profil pro
    Inscrit en
    Avril 2004
    Messages
    1 107
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 1 107
    Par défaut
    J'en dit que les majuscules sont mal utilisées et que ça serait plus clair si on avait une idée du type de jeu concerné.
    Quelle est l'utilité d'une fonction cree_vaisseau s'il existe déjà une classe Vaisseau ?

  3. #3
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 992
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 4 992
    Par défaut
    Hello,

    Le message d’erreur donne une indication importante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    AttributeError: type object 'Humain' has no attribute 'Camp'
    La partie type object 'Humain' signifie que joueur référence probablement la classe Humain elle-même, et non une instance de cette classe.

    Vous devez sans doute avoir quelque chose de ce genre :

    Dans ce cas, la méthode init() n’est jamais exécutée, donc l’attribut Camp n’est pas créé.

    Il faut instancier la classe avec des parenthèses :

    Votre attribut étant défini ainsi :

    il appartient aux instances de Humain, et non directement à la classe Humain.

    C’est exactement le même problème avec cette ligne :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    vaisseau = Vaisseau
    Elle ne crée pas de vaisseau : elle place simplement la classe Vaisseau dans la variable vaisseau.

    La bonne écriture est donc bien :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    vaisseau = Vaisseau()
    Mais cette correction ne peut pas, à elle seule, corriger l’erreur concernant joueur.Camp. Il faut également vérifier comment la variable joueur est initialisée.

    Votre fonction pourrait par exemple être écrite ainsi :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    def cree_vaisseau(nom, arme, joueur):
        vaisseau = Vaisseau()
        vaisseau.nom = nom
        vaisseau.armes = arme
     
        if joueur.camp == 0:
            # ...
            pass
     
        return vaisseau
    Et appelée de cette manière :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    joueur = Humain()
    vaisseau = cree_vaisseau("Mon vaisseau", arme, joueur)
    À noter également que, selon les conventions Python, on utilise généralement :

    • Humain et Vaisseau pour les noms de classes ;
      cree_vaisseau, joueur, nom, armes et camp pour les fonctions, variables et attributs.


    Enfin, concernant la remarque de josmiley, une fonction cree_vaisseau() peut être utile si elle joue le rôle de fabrique et contient une logique de création particulière. Si elle ne fait qu’appeler Vaisseau() et affecter deux attributs, il serait probablement plus simple de passer ces informations directement au constructeur :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    class Vaisseau:
        def init(self, nom, armes):
            self.nom = nom
            self.armes = armes
    Puis :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    vaisseau = Vaisseau("Mon vaisseau", arme)
    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

  4. #4
    Membre confirmé
    Homme Profil pro
    sans
    Inscrit en
    Mai 2023
    Messages
    316
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : sans

    Informations forums :
    Inscription : Mai 2023
    Messages : 316
    Par défaut
    merci beaucoup Fred, comme chaque fois tes réponses sont toujours des supers cours où j'apprend des choses !!! J'ai bien fais de poser la question

    Joesmiley, salut !
    c'est un shooter, le jeu, et l'utilité de la fonction vaisseau est de créer des instances d'un type de vaisseau mais avec une logique particulière comme l'a ainsi nommée Fred.
    vala, vala...

  5. #5
    Membre Expert
    Homme Profil pro
    Inscrit en
    Avril 2004
    Messages
    1 107
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 1 107
    Par défaut
    Je veux bien s'il s'agit de créer un vaisseau de façon interactive, le joueur choisi les caractéristiques par exemple, sinon je ne vois pas vraiment. La fonction retournera une instance de Vaisseau normalement, à moins qu'elle initialise en plus des variables plus globales, auquel cas elle est mal nommée.
    Mais bon, je chipote 😁

  6. #6
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 992
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 4 992
    Par défaut
    Citation Envoyé par josmiley
    Je veux bien s'il s'agit de créer un vaisseau de façon interactive, le joueur choisi les caractéristiques par exemple, sinon je ne vois pas vraiment. La fonction retournera une instance de Vaisseau normalement, à moins qu'elle initialise en plus des variables plus globales, auquel cas elle est mal nommée.
    Mais bon, je chipote :mouais:
    Tu ne chipotes pas du tout, c'est même une excellente remarque d'architecture logicielle !

    En effet, utiliser une fonction externe comme cree_vaisseau() uniquement pour instancier un objet et lui assigner des attributs va à l'encontre des bonnes pratiques en Programmation Orientée Objet (POO). Le rôle de définir l'état initial d'un objet (comme son nom, ses armes, etc.) revient de droit à son constructeur, à savoir la méthode magique __init__.

    Plutôt que d'avoir une fonction qui fait ceci (d'ailleurs, profitons-en pour appliquer la norme PEP8 avec des variables et fonctions en minuscules) :

    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    def cree_vaisseau(nom, arme):
        vaisseau = Vaisseau()
        vaisseau.nom = nom
        vaisseau.armes = arme
        return vaisseau

    Il est beaucoup plus propre, clair et "Pythonique" d'intégrer cette logique directement à l'initialisation de la classe Vaisseau :

    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    class Vaisseau:
        def __init__(self, nom, armes):
            self.nom = nom
            self.armes = armes
     
    # L'instanciation se fait alors naturellement, de manière bien plus concise :
    mon_vaisseau = Vaisseau("Faucon Millenium", "Lasers")

    Il n'y a finalement que quelques cas où une fonction de création séparée se justifie réellement :

    1. Le Design Pattern "Factory" (Fabrique) : Si la fonction implique une logique complexe pour déterminer quelle sous-classe instancier. Par exemple :

      Code python : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
       
      class Vaisseau:
          def __init__(self, nom):
              self.nom = nom
       
      class Chasseur(Vaisseau):
          pass
       
      class Croiseur(Vaisseau):
          pass
       
      def fabrique_vaisseau(nom, arme):
          # La fonction "masque" la logique complexe de création
          if arme == "Lasers lourds":
              return Croiseur(nom)
          return Chasseur(nom)
       
      # L'utilisateur ne se soucie pas de la classe exacte à instancier
      mon_vaisseau = fabrique_vaisseau("Faucon Millenium", "Lasers légers")
    2. L'interaction et les effets de bord : Comme tu le soulignes, si la fonction interagit avec le joueur via des inputs ou si elle modifie des états globaux en plus de la création (comme enregistrer le vaisseau nouvellement créé dans le gestionnaire global de la partie). Dans ce cas, cree_vaisseau gagne en effet à être renommée pour clarifier son rôle.


    Donc ta remarque tape dans le mille : si la fonction sert juste à construire l'objet de manière statique, c'est le constructeur de la classe qui doit s'en charger !
    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

  7. #7
    Membre Expert
    Homme Profil pro
    Inscrit en
    Avril 2004
    Messages
    1 107
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 1 107
    Par défaut
    @fred1599

    c'est là que je diverge un peu:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Vaisseau:
     
        class Chasseur:
            pass
     
        class Croiseur:
            pass
     
        def __new__(cls,nom, arme):
            if arme == "Lasers lourds":
                vaisseau = cls.Croiseur()
            else:
                vaisseau = cls.Chasseur()
            vaisseau.nom = nom
            return vaisseau
     
    # L'utilisateur ne se soucie pas de la classe exacte à instancier
    mon_vaisseau = Vaisseau("Faucon Millenium", "Lasers légers")
    J'utiliserais plutôt ton code un peu modifié si les classes de vaisseaux se trouvaient dans un module "vaisseaux"

  8. #8
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 992
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 4 992
    Par défaut
    Citation Envoyé par josmiley
    c'est là que je diverge un peu:

    class Vaisseau:

    class Chasseur:
    pass

    class Croiseur:
    pass

    def __new__(cls,nom, arme):
    if arme == "Lasers lourds":
    vaisseau = cls.Croiseur()
    else:
    vaisseau = cls.Chasseur()
    vaisseau.nom = nom
    return vaisseau

    # L'utilisateur ne se soucie pas de la classe exacte à instancier
    mon_vaisseau = Vaisseau("Faucon Millenium", "Lasers légers")

    J'utiliserais plutôt ton code un peu modifié si les classes de vaisseaux se trouvaient dans un module "vaisseaux"
    C'est une approche très élégante d'utiliser __new__ ! C'est effectivement le moyen le plus transparent pour l'utilisateur final d'implémenter le pattern Factory en Python.

    La seule petite mise en garde avec cette implémentation via des classes imbriquées, c'est qu'en Python, contrairement à d'autres langages, la classe imbriquée n'hérite pas automatiquement de la classe englobante.
    Dans ton exemple, Chasseur et Croiseur sont de simples espaces de noms internes. Par conséquent, un isinstance(mon_vaisseau, Vaisseau) renverra False, et on perd la possibilité de factoriser des méthodes communes dans la classe mère (puisqu'on ne peut pas écrire class Chasseur(Vaisseau) alors que Vaisseau est en cours de définition).

    Mais sur le principe de déléguer la Factory à la méthode magique __new__, je te rejoins à 100%, c'est très pythonique.

    Si on sort les classes pour préserver le polymorphisme et l'héritage, cela donne le pattern Factory "ultime" (et transparent) :

    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
     
    class Vaisseau:
        def __new__(cls, nom, arme):
            # Si l'utilisateur instancie la classe mère directement, on redirige
            if cls is Vaisseau:
                if arme == "Lasers lourds":
                    return super().__new__(Croiseur)
                else:
                    return super().__new__(Chasseur)
            # Sinon (si on instancie déjà une sous-classe), on crée l'objet normalement
            return super().__new__(cls)
     
        def __init__(self, nom, arme):
            self.nom = nom
            self.arme = arme
     
    class Chasseur(Vaisseau):
        pass
     
    class Croiseur(Vaisseau):
        pass
     
    # Totalement transparent pour l'utilisateur :
    mon_vaisseau = Vaisseau("Faucon Millenium", "Lasers légers")
     
    # Et l'héritage est préservé :
    # isinstance(mon_vaisseau, Vaisseau) -> True
    # isinstance(mon_vaisseau, Chasseur) -> True

    D'ailleurs, pour aller plus loin : si demain le projet grandit et qu'on se retrouve avec 10 ou 20 classes de vaisseaux, le bloc if/elif/else va vite devenir ingérable et violer le principe Ouvert/Fermé (SOLID). Dans ce cas, la meilleure pratique est de coupler __new__ avec le pattern Registry (Registre) :

    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    class Vaisseau:
        _registre = {}
     
        @classmethod
        def enregistrer(cls, type_arme):
            def wrapper(sous_classe):
                cls._registre[type_arme] = sous_classe
                return sous_classe
            return wrapper
     
        def __new__(cls, nom, arme):
            if cls is Vaisseau:
                sous_classe = cls._registre.get(arme)
                if not sous_classe:
                    raise ValueError(f"Arme inconnue : {arme}")
                return super().__new__(sous_classe)
            return super().__new__(cls)
     
        def __init__(self, nom, arme):
            self.nom = nom
            self.arme = arme
     
    @Vaisseau.enregistrer("Lasers légers")
    class Chasseur(Vaisseau): pass
     
    @Vaisseau.enregistrer("Lasers lourds")
    class Croiseur(Vaisseau): pass
     
    @Vaisseau.enregistrer("Torpilles")
    class Bombardier(Vaisseau): pass
     
    # L'instanciation reste toujours aussi magique, sans aucun if/else !
    mon_vaisseau = Vaisseau("B-Wing", "Torpilles")

    Dans tous les cas on est tout à fait sur la même longueur d'onde sur le fond : l'instanciation est la responsabilité de la classe (ou d'un registre dédié), et non d'une fonction procédurale perdue au milieu du script principal !
    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

  9. #9
    Membre Expert
    Homme Profil pro
    Inscrit en
    Avril 2004
    Messages
    1 107
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 1 107
    Par défaut
    Atta.... On peut décorer des class !? 😲
    Mais !..... J'aurais tellement de codes dégueulasses à reprendre, je préfère oublier ce que je viens de voir.

  10. #10
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 992
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 4 992
    Par défaut
    Eh oui, on peut décorer des classes en Python exactement de la même manière qu'on décore des fonctions. C'est possible depuis très longtemps (depuis Python 2.6 avec la PEP 3129), mais c'est souvent une fonctionnalité méconnue car on a tendance à associer les décorateurs uniquement aux méthodes et fonctions.

    Le gros avantage, comme tu as pu le voir avec le pattern Registry, c'est que ça te permet d'enregistrer, de modifier ou d'analyser une classe au moment même de sa définition, sans avoir à écrire de code "procédural" pour l'ajouter à un gestionnaire global. Ça rend l'architecture extrêmement propre et modulaire.

    Garde l'astuce sous le coude pour tes futurs projets ou tes prochaines sessions de refactoring. Ça te sauvera vraiment la vie sur les grosses architectures !
    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [XL-2013] Fonction Find qui ne trouve pas
    Par cdrouaud dans le forum Macros et VBA Excel
    Réponses: 19
    Dernier message: 26/07/2016, 17h57
  2. [PHP 5.0] Existe-il une fonction shuffle qui ne réinitialise pas les clés ?
    Par Dsphinx dans le forum Langage
    Réponses: 2
    Dernier message: 09/02/2012, 16h09
  3. une fonction supprime qui ne marche pas!
    Par hindou90 dans le forum C
    Réponses: 17
    Dernier message: 06/02/2010, 12h26
  4. Une fonction calcul qui ne calcule pas
    Par tranenengel dans le forum C
    Réponses: 3
    Dernier message: 18/06/2008, 12h11
  5. Une fonction settimeout qui s'execute pas
    Par stepon dans le forum Général JavaScript
    Réponses: 3
    Dernier message: 03/08/2006, 11h55

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo