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 :

Décorateur automatisé d'__init__, et metaclass


Sujet :

Python

  1. #1
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2012
    Messages
    48
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2012
    Messages : 48
    Par défaut Décorateur automatisé d'__init__, et metaclass
    Bonjour.

    Tout d'abord, le code :
    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
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
     
    class BaseCreature(object):
        # C'est ici que je coince...
        def __init__(self, pv=1):
            self.pv = pv
     
     
    class Monstre(BaseCreature):
        def __init__(self, laideur=0):
            self.laideur = laideur
     
     
    class Heros(BaseCreature):
        def __init__(self, beaute=0):
            self.beaute = beaute
     
    def test():
        orc = Monstre(pv=4, laideur=3)
        belle_mere = Monstre(laideur=10)
        jean = Heros(beaute=5)
        sekigo = Heros(pv=100, beaute=100)
     
        assert getattr(sekigo, 'pv') == 100
        assert getattr(sekigo, 'beaute') == 100
        assert getattr(belle_mere, 'pv') == 1
        assert getattr(belle_mere, 'laideur') == 10
     
    if __name__ == "__main__":
        test()
    Bon, j'essaye de me "former" à l'utilisation des tests. Mais là n'est pas la question.

    Ce que je voudrais, c'est que des instances de classes soient déjà peuplés de la variable pv SANS avoir à écrire un truc comme cela :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    class Cailloux(BaseCreature):
        def __init__(self, pv=1, beaute=1):
            super(Cailloux, self).__init__(pv)
            self.beaute = beaute
    mais rester dans le style des classes Heros et Monstre.

    Ceci dit, ce n'est peut-être pas super correct de faire un truc comme cela. Je n'en sais rien. Mais en tout cas, ce serait vraiment plus rapide à écrire.

    En espérant que vous compreniez au moins le problème (je ne suis pas toujours bien clair dans les explications de mes problèmes).
    Et merci d'avoir déjà lu le sujet

  2. #2
    Membre Expert Avatar de PauseKawa
    Homme Profil pro
    Technicien Help Desk, maintenance, réseau, système et +
    Inscrit en
    Juin 2006
    Messages
    2 725
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Technicien Help Desk, maintenance, réseau, système et +
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 725
    Par défaut
    Je pense que je n'ai pas trop compris mais cela ressemble à l'utilisation des variables de classe.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class BaseCreature(object):
        pv = 1
        beaute = 1
        def __init__(self):
            pass
     
    class Cailloux(BaseCreature):
        def __init__(self, pv=2, beaute=0):
            self.beaute = beaute
            print(self.pv)
            print(self.beaute)
     
    c = Cailloux()

  3. #3
    Membre Expert

    Homme Profil pro
    Diverses et multiples
    Inscrit en
    Mai 2008
    Messages
    662
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Diverses et multiples

    Informations forums :
    Inscription : Mai 2008
    Messages : 662
    Par défaut
    En fait, je ne comprends pas bien ton problème, moi non plus…

    D’un, pourquoi tu ne veux pas appeler le “constructeur” de la classe parent*? C’est la procédure standard…

    De deux, si ton initialisation est aussi simple que ça, tu peux faire self.pv = 3.14 dans l’init de Monstre & Cie (ça aura de toute façon le même résultat que d’appeler le constructeur de BaseCreature*: self aura un membre pv…).

    De toute manière, l’héritage en python présente surtout un intérêt du côté des fonctions…

    De trois, je vais te dénoncer à la SPBM, moi*!

  4. #4
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 754
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 754
    Par défaut
    Salut,

    Vous pouvez utilisez __new__ ainsi:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class BaseCreature(object):
        def __new__(cls, pv=1, *args, **kwds):
            obj = object.__new__(cls)
            setattr(obj, 'pv', pv)
            return obj
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  5. #5
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2012
    Messages
    48
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2012
    Messages : 48
    Par défaut
    Hop, j'ai réussi à résoudre mon problème, en me basant sur un tutoriel pour apprendre Python et surtout, sur les sources Django.

    Bon, avec le code de solution en bas, ce sera plus simple à comprendre.
    Ce que je voulais, c'était avoir une étape intermédiaire entre la déclaration d'appel à la classe (pas sûr du terme, en gros, l'endroit où l'on dit que l'on veut une instance de la classe Machin) et l'initialisation de l'instance. Ceci, pour "nettoyer" les arguments passé.

    Le code :
    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
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
     
    class BaseCreature(type):
        def __new__(metacls, name, bases, dct):
            # Petit contrôle des arguments.
            def _initialize(method):
                def _control_arguments(self, *args, **kwargs):
                    pv = kwargs.pop('pv', 1)
                    self.pv = pv
                    return method(self, *args, **kwargs)
                return _control_arguments
     
            if dct.get('__init__', None):
                dct['__init__'] = _initialize(dct['__init__'])
            return type.__new__(metacls, name, bases, dct)
     
    class Creature(object):
        __metaclass__ = BaseCreature
     
     
     
    class Monstre(Creature):
        def __init__(self, laideur=0):
            self.laideur = laideur
     
    class Heros(Creature):
        def __init__(self, beaute=0):
            self.beaute = beaute
     
     
    def test():    
        orc = Monstre(pv=4, laideur=3)
        belle_mere = Monstre(laideur=10)
        jean = Heros(beaute=5)
        sekigo = Heros(pv=100, beaute=100)
     
        assert getattr(sekigo, 'pv') == 100
        assert getattr(sekigo, 'beaute') == 100
        assert getattr(belle_mere, 'pv') == 1
        assert getattr(belle_mere, 'laideur') == 10
     
     
    if __name__ == "__main__":
        test()
    Je ne pouvais pas utiliser les variables de classes. Je veux des variables propre à chaque instance crée avec la classe, je ne veux pas d'une variable de l'instance de classe.
    Et je ne pouvais pas faire un self.pv = 32 dans chaque init. Parce que si j'ai 30 classes qui dérive de la classe Creature, ça va vite me saouler.
    Évidemment, je peux appeler le constructeur de la classe parente. Mais si je modifie pour X ou Y raison un truc dans les arguments de la classe parente, je dois les faire répercuter sur toutes les classes filles.

    Bon, là, j'ai utiliser les metaclass, surtout parce que ça faisait un moment que je voulais commencer à apprendre à m'en servir. Mais je pense qu'on doit pouvoir le faire dans le __new__ de la classe Creature.
    Surtout que j'ai lu à de nombreuses reprises sur le net que les metaclass, dans 99% des cas où l'on pense en avoir besoin, c'est que l'on en a pas besoin...

    Merci de vos réponses ! J'éditerais quand j'aurais le code avec le __new__

  6. #6
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 754
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 754
    Par défaut
    Salut,
    la metaclass 'decore' __init__. C'est ce qui permet de filtrer la liste des arguments passés à l'__init__ "original".
    Cela 'automatise' la construction du code suivant:
    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
    19
    20
    class Creature(object): pass
     
    def initialize(**default):
        def inner_fct(f):
            def wrapper(self, *args, **kwds):
                for k, v in default.iteritems():
                    setattr(self, k, kwds.pop(k, v))
                f(self, *args, **kwds)
            return wrapper
        return inner_fct
     
    class Monstre(Creature):
        @initialize(pv=1)
        def __init__(self, laideur=0):
            self.laideur = laideur
     
    class Heros(Creature):
        @initialize(pv=1)
        def __init__(self, beaute=0):
            self.beaute = beaute
    Mais en fait, ce qu'on voudrait peut être c'est:
    1. Définir une hiérarchie de classe qui permette de définir attributs et valeurs par défaut,
    2. Prendre en compte la liste d'arguments passé au constructeur pour modifier ces défauts.

    En gros l'interface de la chose pourrait être:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Creature(object):
        __metaclass__ = _base_
        pv = field(int, 1)
     
    class Monstre(Creature): 
        laideur = field(int, 0)
     
    class Heros(Creature):
        beaute = field (int, 0)
    field est un descripteur défini ainsi:
    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
    class field(object):
        def __init__(self, factory, default=None):
            self._factory = factory
            self._default = default
            self.name = None
     
        def __get__(self, instance, klass=None):
            assert self.name
            if hasattr(instance, '_%s' % self.name):
                return getattr(instance, '_%s' % self.name)
            return self._default
     
        def __set__(self, instance, args):
            assert self.name
            v = self._factory(args)
            setattr(instance, '_%s' % self.name, v)
    Dans la metaclass, on doit faire 2 choses:
    • initialiser les descriptors,
    • initialiser les instances.


    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 _base_(type):
     
        def __new__(mcs, name, bases, dct):
            for k, v in dct.iteritems():
                if isinstance(v, field):
                    v.name = k
            return type.__new__(mcs, name, bases, dct)
     
        def __call__(cls, **kwds):
            obj = object.__new__(cls)
            for c in cls.__mro__:
                for k, v in c.__dict__.iteritems():
                    if isinstance(v, field):
                        setattr(obj, k, v._default)
            for k, v in kwds.items():
                if hasattr(obj, k):
                    setattr(obj, k, v)
            return obj
    Il y a des constructions qui ne me plaisent pas, mais les idées sont là et "çà fonctionne".

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  7. #7
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2012
    Messages
    48
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2012
    Messages : 48
    Par défaut
    Ah oui, c'est vraiment pas mal comme structuration. Je pense que je n'y aurais jamais vraiment pensé par moi-même, vu qu'il y a des choses que je ne connais pas encore (travailler directement dans l'appel d'un constructeur et surtout, le __mro__ dont je recherche l'utilité en même temps sur google).

    Bon, évidemment, les codes sont des exemples, mais ça va être assez proche. Je compte faire un petit jeu vidéo, surtout pour m'entraîner sur les choses que je ne connais pas encore en Python et pour apprendre à mieux structurer mon code (c'est vraiment le point qui me fait le plus défaut en programmation).

    Je pense que l'un des défauts d'une méthode comme celle-ci, c'est le coté "magique". Disons que si une personne extérieur décide d'utiliser le code et dérive Creature, ça risque de surprendre d'avoir des attributs lié à la classe qui travaille sans appel extérieur et qui peuplent la classe sans demander quoi que ce soit avant.
    Mais bon, vu que c'est un code pour apprendre et m'amuser, et que ça n'a pas destination à être trop diffusé ou maintenu, ça ne me dérange pas trop.

    Merci !

    Edit:
    En analysant en détail ton code dans l'IDE, ça marche™ mais ça demande une certaine gymnastique quand même. Parce que je sais très bien que les classes sont des objets "comme les autres", dont les instances sont produites par une metaclass, mais mon cerveau verrouille sa logique quand il s'agit de manipuler les instances de classes.
    Mais j'ai eu le même problème en rédigeant ma "solution" plus haut. J'ai fait au plus simple.
    J'ai l'impression d'être revenu au début de mon apprentissage Python, quand c'était :
    • Ayé, j'ai compris.
      (5 minutes plus tard)
    • - Non, en fait, je n'y comprends rien
      (5 minutes plus tard)
    • Okééééé
      (5 minutes plus tard)
    • Ah. Bah non, ce n'est pas ça.
    • ...Bis Repetita...


    Edit 2 (bon, désolé pour les éditions à rallonge) :
    En y allant en mode pas à pas dans le debugger, je pense avoir compris.
    C'était surtout __call__ que je ne comprenais pas, parce que je ne savais pas précisément quand il était appelé. C'est lors de la création d'une instance d'une classe fille de Creature. Je pensais que c'était bien avant, et ça devenait trop métaphysique pour moi dans mon esprit. Mais non en fait, c'est "plus simple" que cela.
    Bon, ceci dit, je ne peux pas encore enchaîner 10 écritures de metaclass le matin avec une main dans le dos et le genou au dessus de la tête, mais je commence à comprendre bien mieux le principe.
    Maintenant, je me demande si j'en ai RÉÉLEMENT besoin...

  8. #8
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 754
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 754
    Par défaut
    Salut,
    Bon, ceci dit, je ne peux pas encore enchaîner 10 écritures de metaclass le matin avec une main dans le dos et le genou au dessus de la tête, mais je commence à comprendre bien mieux le principe.
    Maintenant, je me demande si j'en ai RÉÉLEMENT besoin...
    Ah ah... c'est sûr que çà décoiffe!
    J'ai retravaillé un peu mon code, histoire de simplifier l'exigence "Je ne pouvais pas utiliser les variables de classes. Je veux des variables propre à chaque instance crée avec la classe, je ne veux pas d'une variable de l'instance de classe"...

    Que j'ai comprise je veux les variables dans le dict de l'"instance": c'est la raison de la balade dans le __mro__. Mais tout bien considéré satisfaire cette exigence "à la lettre" est "discutable"...

    D'ailleurs dans le code précédent, ce n'est pas 'beaute' mais '_beaute' qui est stocké: l'accès à 'beaute' n'est qu'un chemin vers le descripteur. Et si on n'utilise pas ce 'renommage', ca 'boucle'.

    Ceci dit comme on ne fait pas grand chose d'autre que stocker la valeur, la chose n'est pas nécessaire et on évite "la boucle" en positionnant la definition dans l'instance que lorsqu'elle est accédée.


    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
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class field(object):
        name = None
        def __init__(self, factory, *args, **kwds):
            self._data = factory, args, kwds
        def __get__(self, instance, cls=None):
            assert self.name
            factory, args, kwds = self._data
            if instance:
                setattr(instance, self.name, factory(*args, **kwds))
                return getattr(instance, self.name)
            return factory(*args, **kwds)
     
    def is_field(x):
        return isinstance(x, field)
     
    class _base_(type):
     
        def __new__(mcs, name, bases, dct):
            for k, v in dct.iteritems():
                if is_field(v): v.name = k
            return type.__new__(mcs, name, bases, dct)
     
        def __call__(cls, *args, **kwds):
            obj = type.__call__(cls)
            for k, v in kwds.iteritems():
                 setattr(obj, k, v)
            return obj
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  9. #9
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2012
    Messages
    48
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2012
    Messages : 48
    Par défaut
    Hop, je reviens deux secondes sur ce thread.

    Finalement, j'ai choisi comme construction ce que wiztrick recommendait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    # C'est juste un exemple de "prototype"
    class ElfeTresMechant(Creature):
        mechant = Attribute(50)
        gentil = Attribute(-20)
    Et ça se rapproche de ce que fait Django, ce qui m'arrange parce que je connais bien ce framework.

    J'ai zappé ce que je voulais initialement (avoir des instances qui ont des attributs sans qu'on le dise explicitement) parce que c'est une mauvaise construction.
    Et je me suis plongé dans les metaclass, dont j'ai enfin compris le fonctionnement.

    Donc, j'ai une dernière question.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    class Creature(BaseCreature):
        hp = Attribute(min=0, max=50)
        mp = Attribute(min=0, max=10)
     
    class MonstreAvecPleinDePV(Creature):
        hp = Attribute(min=0, max=999)
    Si je fais une instance de la dernière classe, je lui crée ses propres attributs (avec property), selon le modèle de la classe. Ce qui donne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    >>>jean = MonstreAvecPleinDePV()
    >>>print(jean.__dict__)
    {'_mp': (min:0, current:10, max:10), '_hp': (min:0, current:50, max:50)}
    Bon, il y a du travail interne, c'est donc pour cela que la sortie est un peu différent du résultat brut attendu. Mais j'ai laissé, parce que c'est quand même compréhensible.

    Le problème, c'est l'héritage. Ici, l'instance hérite de ses classes de la couche la plus haute de l'oignon vers la plus basse. Ce qui écrase l'attribut hp de la classe MonstreAvecPleinDePV avec l'attribut hp de la classe Creature.
    La solution serait de parcourir le __mro__ de la classe MonstreAvecPleinDePV en sens inverse (du plus bas vers le plus haut).

    Ma question porte donc sur la construction du __mro__.
    Est-ce que le __mro__ est toujours construit selon une manière "logique" ? C'est à dire, une liste de la couche la plus haute vers la couche la plus basse ?
    Si oui, cela veut dire que je peux la parcourir en sens inverse sans problème, le résultat sera toujours celui que j'attends.
    Si non, bah, faut que je réfléchisse à une autre manière de faire.

    Bien évidemment, il n'y a pas d'héritage multiple. Parce que là, je ne sais pas le comportement du __mro__ dans ce cas, et j'évite l'héritage multiple au maximum.

  10. #10
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 754
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 754
    Par défaut
    Salut,
    Le problème, c'est l'héritage. Ici, l'instance hérite de ses classes de la couche la plus haute de l'oignon vers la plus basse. Ce qui écrase l'attribut hp de la classe MonstreAvecPleinDePV avec l'attribut hp de la classe Creature.
    Ben, les meta class permettent de donner à l'héritage le sens que l'on veut. Ceci dit, passer par le mro n'est utile que pour forcer la création d'un attribut dans le dict de l'instance.
    En laissant faire les choses par défaut, i.e:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class A(object):
        a = 1
    class B(A):
        a = 2
     
    b = B()
    print b.a
    Vous auriez quelque chose de "naturel" sans plus de soucis.
    Ma question porte donc sur la construction du __mro__.
    Est-ce que le __mro__ est toujours construit selon une manière "logique" ? C'est à dire, une liste de la couche la plus haute vers la couche la plus basse ?
    Python supporte un héritage multiple.
    Les ancêtres d'une classe ne sont pas "uniques" mais "pluriels" et définissent un "arbre" pour lequel un des ancêtres pourra être atteint par plusieurs chemin et dont "plusieurs fois".
    Ce qui pose problème lorsqu'on doit trouver quelle méthode appeler.
    La solution est de transformer cet arbre en liste en respectant la précédence des héritages (si C et B héritent de A, quelque soit l'ordre de C, B ils devront apparaître avant A). Il y a pas mal d'articles qui traitent le sujet sur internet, ils sont plus ou moins comestibles suivant votre maîtrise des algo. de parcours d'arbres de dépendances.

    Je me répète mais, dans votre cas, vous n'avez pas besoin de balayer le __mro__.
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  11. #11
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2012
    Messages
    48
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2012
    Messages : 48
    Par défaut
    Justement, dans mon cas, j'ai besoin d'avoir des attributs bien distincts par instance.

    Bon, voilà mon code "complet" :
    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
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
     
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    Created on Thu Feb 23 16:56:25 2012
    """        
     
    class Attribute(object):
        def __init__(self, min=None, max=None):
            # TODO: Nettoyer résidu
            b = 0
            if min is not None: b += 1
            if max is not None: b += 2
            if b == 0: raise TypeError("%s takes at least one argument" % self.__class__)
            if b == 1:
                self._min = min
                self._max = min
            if b == 2:
                self._min = max
                self._max = max
            if b == 3:
                self._min = min
                self._max = max
            self._current = self._max
            assert self._min<=self._current<=self._max
     
        def __add__(self, value):
            v = self._current + value
            if v > self._max:
                return self._max
            return v
     
        def __iadd__(self, value):
            self._current = self.__add__(value)
            return self
     
        def __sub__(self, value):
            v = self._current - value
            if v < self._min:
                return self._min
            return v
     
        def __isub__(self, value):
            self._current = self.__sub__(value)
            return self
     
        def __repr__(self):
            min = "min:%i" % self._min
            max = "max:%i" % self._max
            current = "current:%i" % self._current
            return " ".join((min, current, max))
     
        def get_copy(self):
            return Attribute(self._min, self._max)
     
        def set_min(self, min):
            assert min <= self._current <= self._max
            self._min = min
     
        def set_max(self, max):
            assert self._min <= self._current <= max
            self._max = max
     
        def set_current(self, current):
            assert self._min <= current <= self._max
            self._current = current
     
     
    class MetaCreature(type):
        def __new__(cls, names, bases, attrs):
            new_attrs = dict()
            for key, value in attrs.iteritems():
                if isinstance(value, Attribute):
                    newkey = "_" + key
                    new_attrs[newkey] = value
                    getter = cls.attribute_getter(newkey)
                    setter = cls.attribute_setter(newkey)
                    new_attrs[key] = property(getter, setter)
                else:
                    new_attrs[key] = value
            return type.__new__(cls, names, bases, new_attrs)
     
        def __call__(cls, *args, **kwargs):
            obj = object.__new__(cls)
            for c in reversed(cls.__mro__):
                for key, value in c.__dict__.iteritems():
                    if isinstance(value, Attribute):
                        setattr(obj, key, value.get_copy())
            return obj
     
        @staticmethod
        def attribute_getter(attribute):
            def getter(obj):
                return getattr(obj, attribute)
            return getter
     
        @staticmethod
        def attribute_setter(attribute):
            def setter(obj, value):
                if isinstance(value, Attribute):
                    setattr(obj, attribute, value)
                elif type(value) is int:
                    attr_obj = getattr(obj, attribute)
                    attr_obj.set_current(value)
                else:
                    raise TypeError()
            return setter
     
    class BaseCreature(object):
        __metaclass__ = MetaCreature
        pass
     
     
    class Creature(BaseCreature):
        hp = Attribute(min=0, max=50)
        mp = Attribute(10)
     
    class Orc(Creature):
        hp = Attribute(min=0, max=999)
     
    orc = Orc()
    Avec le __mro__ en inversé, j'obtiens le comportement que je souhaite.

    Maintenant, je me pose des questions. J'ai l'impression que je suis un zéro pointé en construction. J'essaye différents trucs et j'ai l'impression à chaque fois que ça donne un résultat pas super propre.
    J'aimerais bien avoir différents modules qui soit indépendants, et pas un gros gloubiboulga de code qui soit formé d'un seul bloc.

  12. #12
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 064
    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 064
    Par défaut
    Depuis le début de ce post je ne comprend rien...

    Question :

    99% des utilisateurs python disent ne jamais avoir besoin de métaclasse, pourquoi en as-tu besoin ici?

  13. #13
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 754
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 754
    Par défaut
    Salut,

    Maintenant, je me pose des questions. J'ai l'impression que je suis un zéro pointé en construction. J'essaye différents trucs et j'ai l'impression à chaque fois que ça donne un résultat pas super propre.
    J'aimerais bien avoir différents modules qui soit indépendants, et pas un gros gloubiboulga de code qui soit formé d'un seul bloc
    Le problème lorsqu'on programme c'est qu'on peut faire quelque chose qui fonctionne sans forcément s'être approprié, maîtriser ce qui ferait un "meilleur" code.
    Ceci dit, les classes permettent de "cacher" la réalisation "dont on est peu fier" dans une boîte avec la possibilité d'y revenir plus tard sans trop de casse pour autant qu'on ne retouche pas l'API utilisateur.

    Ceci dit je n'aime pas le mélange des rôles de votre dernière mouture.
    Attribute réalise une fonction côté "définition des attributs" d'une classe.
    Mais çà embarque en plus un "entier amélioré".
    Et vous conduit à réaliser setter et getter dans la metaclasse.

    Séparez les rôles entier amélioré, attribute et metaclass!!

    Reprenez le dernier code de field que j'ai commis.
    factory doit pouvoir être remplacé par votre entier amélioré BoundedInt
    C'est l'existence de la méthode __get__ qui rend la chose "property".

    99% des utilisateurs python disent ne jamais avoir besoin de métaclasse, pourquoi en as-tu besoin ici?
    Dès qu'on utilise des descripteur pour fixer des variables de "classe", il faut gérer la liaison entre le nom de l'attribut et le descripteur. i.e. lorsqu'on écrit:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    class A:
         x = field(int, 3)
    Le descripteur "field" recoit des __get__, __set__ avec en paramètre l'instance ou la classe mais s'il doit "écrire", il faut bien récupérer le nom de la variable correspondante.
    A moins de faire faire cette association à l'utilisateur, je ne vois pas trop comment se passer des metaclass sur ce coup la.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  14. #14
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 064
    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 064
    Par défaut
    Le descripteur "field" recoit des __get__, __set__ avec en paramètre l'instance ou la classe mais s'il doit "écrire", il faut bien récupérer le nom de la variable correspondante.
    En gros il modifie sa classe.

    Les métaclasses lui permettent de modifier ou d'ajouter des attributs dans sa classe, c'est ça?

  15. #15
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 754
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 754
    Par défaut
    Citation Envoyé par fred1599 Voir le message
    En gros il modifie sa classe.

    Les métaclasses lui permettent de modifier ou d'ajouter des attributs dans sa classe, c'est ça?
    En gros c'est çà.
    Mais il n'est pas nécessaire de passer par des metaclass dès qu'on utilise les descriptors (exemple property)... et réciproquement.

    Lorsque j'écris:
    Le descripteur "field" recoit des __get__, __set__ avec en paramètre l'instance ou la classe mais s'il doit "écrire", il faut bien récupérer le nom de la variable correspondante.
    A moins de faire faire cette association à l'utilisateur, je ne vois pas trop comment se passer des metaclass sur ce coup la.
    Je force un peu le trait.

    En fait, le descripteur sera appelé dès qu'il y aura un accès à l'attribut "xyz".
    Charge à lui de faire sa soupe pour retourner la valeur attendue ou la stocker quelque part. Nous pourrions imaginer stocker les valeurs dans un dict crée dans l'instance en utilisant id(self) comme clé (le self du descriptor).
    Dans ce cas, pas besoin de passer par la metaclass pour créer une association qui soit fonction du "nom" de l'attribut puisqu'on l'ignore...

    Exemple:
    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
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    def get_fields(instance)
        if not hasattr(instance, '_fields_'):
            setattr(instance, '_fields_', {})
        return getattr(instance, '_fields_')
     
    class field(object):
        def __init__(self, factory, *args, **kwds):
            self._data = factory, args, kwds
        def __get__(self, instance, cls=None):
            factory, args, kwds = self._data
            if instance:
                fields = get_fields(instance)
                return fields.get(id(self), factory(*args, **kwds))
            return factory(*args, **kwds)
        def __set__(self, instance, value):
            factory = self._data[0]
            assert isinstance(value, factory)
            fields = get_fields(instance)
            fields[id(self)] = value
    class BaseCreature(object):
    #    __metaclass__ = _base_
        pass
     
    class Creature(BaseCreature):
        hp = field(int, 50)
        mp = field(int, 10)
     
    class Orc(Creature):
        hp = field(int, 99)
     
    orc = Orc()
    print orc.hp
    Nota que dans ce cas l'attribut _fields_ est crée dans l'instance 'à la volée' si nécessaire. Python étant "dynamique", il y a toujours plusieurs façons de réaliser... Pas facile de décider ce qui est mieux ou pas sans le "contexte".
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  16. #16
    Expert confirmé
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    4 064
    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 064
    Par défaut
    Nota que dans ce cas l'attribut _fields_ est crée dans l'instance 'à la volée' si nécessaire. Python étant "dynamique", il y a toujours plusieurs façons de réaliser... Pas facile de décider ce qui est mieux ou pas sans le "contexte".
    C'est vrai, mais question lisibilité, je suis moins dépaysé avec ta dernière solution.

  17. #17
    Membre éclairé
    Profil pro
    Inscrit en
    Février 2012
    Messages
    48
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2012
    Messages : 48
    Par défaut
    Non, mais en fait, ma metaclass ne me sert quasi-uniquement à copier les attributs d'une classe dans le dictionnaire de ses instances. Et à créer des accesseurs pour ces attributs dans l'instance.

    En observant le code que tu fournis, ça donne le même résultat avec les descriptors (bon, avec de l'enrobage factory). Seulement, je ne connaissais pas les descriptors (... tu forces l'humilité, hein. D'abord les metaclass et le __mro__, puis les descriptors), parce que je ne me souviens pas de l'avoir vu dans mon bouquin python. Ou alors, je n'avais strictement rien compris, et j'ai passé trop vite.
    Bon, à ma décharge (on se rassure comme on peut), je viens de faire une recherche sur le web francophone, et on en parle assez peu. Le web anglophone est un peu plus prolixe, et je suis en pleine lecture d'un article les concernant.

    Quelle prochaine feature python vais-je pouvoir réécrire ?


    Sinon, pour séparer entier amélioré et attributs, j'y avais pensé. Mais j'étais surtout pris dans l'écriture de mon code. Là, je vais le faire.
    Ceci dit, mon code fonctionne "bien". C'est faire un détour de 30km pour aller à la boulangerie situé de l'autre coté du patté de maison, mais ça fonctionne.

    Et je n'ai pas de complexe à utiliser du code qui ne serait pas de moi/que je ne comprends pas. Je m'en fiche pas mal de "planquer" le bordel dans le placard d'une classe.
    Mais faut quand même un minimum, que ça soit maintenable et autonome.

    En tout cas wiztricks, je te remercie. Déjà, du temps accordés. Ensuite, de m'avoir faire découvrir des choses que je connaissais pas forcément en Python. .Et bien évidemment, des réponses apportées.
    Ça peut paraître idiot, mais toutes ces choses là (metaclass, descriptors), je ne les connaissais pas.
    Je crois que je suis bon d'aller lire en profondeur la documentation officielle python sur les features "build-in". J'ai de sacré lacune, alors que je pensais commencer à avoir un bon petit niveau pour un amateur.

    Bon, ceci dit, à ce rythme, je risque de terminer mon petit jeu dans 20 ans. Et de plus, dans le petit schéma que j'ai réalisé pour décrire le fonctionnement du jeu, j'ai prévu d'utiliser des événements (par exemple, ici, si les HP tombent à zero, alors, envoyé un event pour prévenir le "controleur", que je n'ai pas encore défini).
    Je le dédicacerais à mes petits enfants, ce n'est pas grave...

    Edit:
    L'écriture de ce post s'est faite sous l'effet de la fatigue. J'ai un peu mélangé des notions, et la metaclass n'est pas forcément inutile dans mon cas (mais pas forcément utile non plus. Tout est relatif).

    Edit2:
    Ce matin, j'ai tenté d'utiliser rapidement les descripteurs (j'ai vérifié, en fait, ils en parlent dans mon bouquin, j'avais sauté cette partie en allant directement aux properties, qui sont la même chose mais avec une méthode d'utilisation différente ).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    class Attribute2(object):
        def __init__(self, min=None, max=None):
            self.min = min
            self.max = max
            self.currents = {id(self):max}
     
        def __get__(self, instance, classe=None):
            if instance and self.currents.has_key(id(instance)):
                return self.currents[id(instance)]
            return self.currents[id(self)]
     
        def __set__(self, instance, value):
            self.currents[id(instance)] = value
    Bon, c'est fait à l'arrache, aucune vérification, aucune méthode particulière, aucune factory.
    Mais ça se rapproche du code plus haut. Sauf qu'au lieu de stocker les valeurs directement dans l'instance, on les stock dans un dictionnaire du descripteur.
    Maintenant, je vais la retravailler sérieusement pour qu'elle produise des BoundedInt, en reprenant ton code avec factory.

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

Discussions similaires

  1. envoi automatisé de mails à partir d'une base postgresql
    Par youn608 dans le forum PostgreSQL
    Réponses: 11
    Dernier message: 15/02/2005, 09h06
  2. backup automatisé
    Par Pierre63 dans le forum Administration
    Réponses: 6
    Dernier message: 24/01/2005, 09h07
  3. Export automatisé de données
    Par LeLaid dans le forum Access
    Réponses: 6
    Dernier message: 26/11/2004, 09h02
  4. Réponses: 2
    Dernier message: 04/10/2004, 14h30
  5. Traitement automatisé journalier
    Par regbegpower dans le forum SQL Procédural
    Réponses: 6
    Dernier message: 21/01/2004, 09h51

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