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 :

Classe : empêcher la création dynamique de nouveaux attributs


Sujet :

Python

  1. #1
    Membre éprouvé

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Par défaut Classe : empêcher la création dynamique de nouveaux attributs
    Voici un code qui me gêne un peu.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class UneClasse:
        """exemple d'une classe"""
        champ1 = "abc"
        def f (self):
                return champ1
     
    a = UneClasse()
    a.champ1
    'abc'
    a.chmap1 = "bcd"
     
    print dir(a)
    Ce code renvoie :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ['__doc__', '__module__', 'champ1', 'chmap1', 'f']
    On voie qu'un nouvel attribut a été créé. Je connaissais ce principe. Peut-on l'empêcher ? Pourquoi ? S'il on souhaite utiliser Python avec des débutants, il serait beaucoup mieux que l'on puisse empêcher cela.

    Toute info. est la bienvenue, sauf si ce comportement n'est plus présent dans Python 3 (que je n'ai pas sous la main).

  2. #2
    Membre émérite
    Avatar de Antoine_935
    Profil pro
    Développeur web/mobile
    Inscrit en
    Juillet 2006
    Messages
    883
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur web/mobile

    Informations forums :
    Inscription : Juillet 2006
    Messages : 883
    Par défaut
    Citation Envoyé par rambc Voir le message
    S'il on souhaite utiliser Python avec des débutants, il serait beaucoup mieux que l'on puisse empêcher cela.
    J'ai fait mes premiers pas en orienté objet avec Python, et je n'ai jamais été gené par cette propriété.
    Au contraire, ça permet de définir des attributs "à l'arrache" dans les méthodes, ce qui simplifie vachement pour un débutant.

    Mais il existe une solution: __slots__
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    In [1]: class UneClass(object):
       ...:     __slots__ = ("attr1", "attr2")
       ...:
       ...:
     
    In [2]: u = UneClass()
     
    In [3]: u.hello = "haha"
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
     
    C:\Windows\system32\<ipython console> in <module>()
     
    AttributeError: 'UneClass' object has no attribute 'hello'
    Note: à partir de Python 3, il n'est plus nécessaire de dériver d'object.

  3. #3
    Membre confirmé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    29
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 29
    Par défaut
    Bonjour, cela est faisable en surchargeant ou en créant une méthode __setattr__ dans la classe en question dont tu permetter ou non les données pouvant être crées ou non.
    Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    >>> class A:
    ...     def __setattr__(self, dat, val):
    ...             pass
    ...
    >>> r = A()
    >>> dir(r)
    ['__doc__', '__module__', '__setattr__']
    >>> r.newvar = 339
    >>> dir(r)
    ['__doc__', '__module__', '__setattr__']

  4. #4
    Membre éprouvé

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Par défaut
    Merci Antoine_935 pour cette astuce.

    Cela peut être très utile si tu distribues un module et que tu veux aider les futurs utilisateurs en cas de faute de frappe.

    Sinon comme je le dis ci-dessus, je me plaçais d'un point de vue de débutant. J'aurais dû préciser que je pensais à des lycéens qui aurait très peu programmé.

    Oups la mort m'a eu par surprise...

    the-death, ta méthode est très bien car elle ne nécessite pas de mettre à la main tous les attributs (chose que j'aurais refusé de faire). C'est nickel.

  5. #5
    Membre émérite
    Avatar de Antoine_935
    Profil pro
    Développeur web/mobile
    Inscrit en
    Juillet 2006
    Messages
    883
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur web/mobile

    Informations forums :
    Inscription : Juillet 2006
    Messages : 883
    Par défaut
    Citation Envoyé par rambc Voir le message
    the-death, ta méthode est très bien car elle ne nécessite pas de mettre à la main tous les attributs (chose que j'aurais refusé de faire). C'est nickel.
    Oula... le jour où tu voudras faire du C, C++, PHP, Java, C# tu vas pas aimer toi... Eh oui, faut les déclarer les attributs.

    Sinon, euh...
    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
    In [7]: class A:
       ...:     def __init__(self):
       ...:         self.myValue = 5
       ...:
       ...:     def __setattr__(self, attr, value):
       ...:         pass
       ...:
       ...:
     
    In [8]: a = A()
     
    In [9]: a.myValue
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
     
    C:\Windows\system32\<ipython console> in <module>()
     
    AttributeError: A instance has no attribute 'myValue'
    Haha, pratique...

  6. #6
    Membre éprouvé

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Par défaut
    Citation Envoyé par Antoine_935 Voir le message
    Oula... le jour où tu voudras faire du C, C++, PHP, Java, C# tu vas pas aimer toi... Eh oui, faut les déclarer les attributs.
    Ne m'en parles pas, j'ai du mal car je trouve cela très verbeux. Ceci étant dit, le jour où la lenteur de Python sera critique dans mon application, je passerais au C++. Sinon je parlais de cela :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    __slots__ = ("attr1", "attr2")
    A comparer avec cela :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    self.attr1 = value1
    self.attr1 = value2
    ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    int attr1 = value1
    string attr1 = value2
    La seconde méthode donne en plus le choix de gestion des attributs créés à la volée.

  7. #7
    Membre Expert
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Par défaut @ rambc
    1) Où en es-tu de ce problème ?

    As-tu trouvé une solution ?
    La file est taguée ’Résolu’ mais je ne vois pas s’il y en a une dans cette file.





    2) Comme tu as l’air de préférer la “solution“ de the-death, j’ai regardé un peu. Il est facile de corriger son inconvénient pointé par Antoine935 en complexifiant la méthode __setattr__() . Elle est très bien cette méthode.
    De plus, l’attribut __slots__ n’existe que pour les classes dérivées d’object (classes de nouvelle facture).

    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
    class A:
        champ1 = "abc"
        def __init__(self,n):
            print "debut d'initialisation d'une instance"
            self.myValue = 5
            self.name = n
            self.__dict__['ini'] = 'faite'
            print "fin de l'initialisation de l'instance de  nom  \""+n+'"\n'
        def __setattr__(self, attr, value):
            if 'ini' in self.__dict__:
                print "Il n'est pas possible de definir des champs d'instance supplementaires:"\
                      +"\nvotre demande de creation du champ d'instance \""+attr+'" est rejetee.'
            else:
                self.__dict__[attr] = value
                print "- je viens de creer l'attribut "+attr+" avec la valeur "+repr(value)
     
    a = A('SERENGUETI')
    print 'a.champ1 :',a.champ1
    print '\ndir(a) : ',dir(a)
    print "\n* Tentative de creation du champ d'instance chmap1"
    a.chmap1 = "bcd"
    print 'dir(a) : ',dir(a)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    debut d'initialisation d'une instance
    - je viens de creer l'attribut myValue avec la valeur 5
    - je viens de creer l'attribut name avec la valeur 'SERENGUETI'
    fin de l'initialisation de l'instance de  nom  "SERENGUETI"
     
    a.champ1 : abc
     
    dir(a) :  ['__doc__', '__init__', '__module__', '__setattr__', 'champ1', 'ini', 'myValue', 'name']
     
    * Tentative de creation du champ d'instance chmap1
    Il n'est pas possible de definir des champs d'instance supplementaires:
    votre demande de creation du champ d'instance "chmap1" est rejetee.
    dir(a) :  ['__doc__', '__init__', '__module__', '__setattr__', 'champ1', 'ini', 'myValue', 'name']

    3) Par ailleurs, j’aimerais comprendre ce que tu sous-entend en disant qu’il faut comparer
    __slots__ = ("attr1", "attr2")
    à
    self.attr1 = value1
    self.attr1 = value2

    et

    int attr1 = value1
    string attr1 = value2
    Veux-tu dire que l’utilisation de __slots__ ne définit pas les valeurs des champs et que ça oblige à faire les choses en deux endroits dans le code ?






    int attr1 = value1
    string attr1 = value2
    Ces instructions ne peuvent pas être mises dans __init__() , uh ? Alors qu’est-ce qu’elles montrent ?






    La seconde méthode donne en plus le choix de gestion des attributs créés à la volée.
    N’est ce pas ce que tu veux empêcher: la création d’attributs à la volée ?




    ----------------------------------------------

    J’ai d’autres remarques mais pour ne pas alourdir, je les renvoie à plus tard.

  8. #8
    Membre éprouvé

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Par défaut
    Citation Envoyé par eyquem Voir le message
    As-tu trouvé une solution ?
    Celle de the-death me va.

    Citation Envoyé par eyquem Voir le message
    Comme tu as l’air de préférer la “solution“ de the-death, j’ai regardé un peu. Il est facile de corriger son inconvénient pointé par Antoine935 en complexifiant la méthode __setattr__()
    Effectivement, je n'ai rien posté car ce qu'a dit the-death me parait très clair.

    J'ai pas trop le temps aujourd'hui. A quoi servent tes modifications ?

    Citation Envoyé par eyquem Voir le message
    Veux-tu dire que l’utilisation de __slots__ ne définit pas les valeurs des champs et que ça oblige à faire les choses en deux endroits dans le code ?
    Oui.

    Citation Envoyé par eyquem Voir le message
    N’est ce pas ce que tu veux empêcher: la création d’attributs à la volée ?
    Je parlais côté concepteur et non côté utilisateur.

  9. #9
    Membre éprouvé

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    int attr1 = value1
    string attr1 = value2
    C'était juste un exemple de type C++.

  10. #10
    Membre Expert
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Par défaut
    Celle de the-death me va.



    Tu as mal lu le post de Antoine935.
    Je trouvais bien qu’il y avait quelque chose de curieusement satisfait dans le ton de ton dernier post du 4 octobre......



    Comparer

    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
    class A:
        champ1 = "abc"
        def __init__(self,n):
            print "debut d'initialisation d'une instance"
            self.myValue = 5
            self.name = n
            self.CinquanteTreize = 'trombone'
            self.chateau = 'de Chambord'
            self.distance = 187654637782
            self.ghurutsedik = ('saucisson','midinette','arthropode','soleil')
            self.nougatine = 'Montelimar'
            print "fin de l'initialisation de l'instance de  nom  \""+n+'"\n'
        def __setattr__(self, attr, value):
            print "votre demande de creation du champ d'instance \""+attr+'" est rejetee.'
            pass
     
    a = A('SERENGUETI')
    print 'a.champ1 :',a.champ1
    print '\ndir(a) : ',dir(a)
    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
    class A:
        champ1 = "abc"
        def __init__(self,n):
            print "debut d'initialisation d'une instance"
            self.myValue = 5
            self.name = n
            self.CinquanteTreize = 'trombone'
            self.chateau = 'de Chambord'
            self.distance = 187654637782
            self.ghurutsedik = ('saucisson','midinette','arthropode','soleil')
            self.nougatine = 'Montelimar'
            self.__dict__['ini'] = 'faite'
            print "- je viens de creer l'attribut ini avec la valeur'faite'"
            print "fin de l'initialisation de l'instance de  nom  \""+n+'"\n'
        def __setattr__(self, attr, value):
            if 'ini' in self.__dict__:
                print "Il n'est pas possible de definir des champs d'instance supplementaires:"\
                      +"\nvotre demande de creation du champ d'instance \""+attr+'" est rejetee.'
            else:
                self.__dict__[attr] = value
                print "- je viens de creer l'attribut "+attr+" avec la valeur "+repr(value)
     
     
    a = A('SERENGUETI')
    print 'a.champ1 :',a.champ1
    print '\ndir(a) : ',dir(a)
    qui donnent respectivement:

    debut d'initialisation d'une instance
    votre demande de creation du champ d'instance "myValue" est rejetee.
    votre demande de creation du champ d'instance "name" est rejetee.
    votre demande de creation du champ d'instance "CinquanteTreize" est rejetee.
    votre demande de creation du champ d'instance "chateau" est rejetee.
    votre demande de creation du champ d'instance "distance" est rejetee.
    votre demande de creation du champ d'instance "ghurutsedik" est rejetee.
    votre demande de creation du champ d'instance "nougatine" est rejetee.
    fin de l'initialisation de l'instance de nom "SERENGUETI"

    a.champ1 : abc

    dir(a) : ['__doc__', '__init__', '__module__', '__setattr__', 'champ1']
    debut d'initialisation d'une instance
    - je viens de creer l'attribut myValue avec la valeur 5
    - je viens de creer l'attribut name avec la valeur 'SERENGUETI'
    - je viens de creer l'attribut CinquanteTreize avec la valeur 'trombone'
    - je viens de creer l'attribut chateau avec la valeur 'de Chambord'
    - je viens de creer l'attribut distance avec la valeur 187654637782L
    - je viens de creer l'attribut ghurutsedik avec la valeur ('saucisson', 'midinette', 'arthropode', 'soleil')
    - je viens de creer l'attribut nougatine avec la valeur 'Montelimar'
    - je viens de creer l'attribut ini avec la valeur'faite'
    fin de l'initialisation de l'instance de nom "SERENGUETI"

    a.champ1 : abc

    dir(a) : ['CinquanteTreize', '__doc__', '__init__', '__module__', '__setattr__', 'champ1', 'chateau', 'distance', 'ghurutsedik', 'ini', 'myValue', 'name', 'nougatine']
    C’est assez décortiqué cette fois ?


    __setattr__() étant une méthode qui est appelée dès qu’une tentative de création ou de modification de la valeur d’un champ d’instance est faite, y compris lors de l’initialisation,
    il s’en suit qu’aucun champ d'instance n’est créé lors de l’initialisation d’une instance si __setattr__() est du genre pass de façon inconditionnelle.

    Tu aurais eu un bug plus ou moins tard.



    Il reste un problème, j’y reviendrai ultérieurement.

  11. #11
    Membre émérite
    Avatar de Antoine_935
    Profil pro
    Développeur web/mobile
    Inscrit en
    Juillet 2006
    Messages
    883
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur web/mobile

    Informations forums :
    Inscription : Juillet 2006
    Messages : 883
    Par défaut
    Eyquem, dans ton modèle, il est strictement interdit de modifier le moindre élément à partir du moment ou ini a été défini. Dommage, si on veut changer un entier on peut aller se faire voir.
    Enfin, c'est tjs bien pour des valuesObject, qui sont immuables. Mais dans un tel cas, on ferait mieux d'utiliser des variables privées avec des properties pour y accéder.

    De plus, on peut toujours supprimer des attributs...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    In [5]: a.myValue = 7
    Il n'est pas possible de definir des champs d'instance supplementaires:
    votre demande de creation du champ d'instance "myValue" est rejetee.
     
    In [6]: del a.myValue
     
    In [7]: a.myValue
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
     
    C:\Windows\system32\<ipython console> in <module>()
     
    AttributeError: A instance has no attribute 'myValue'
    Plus incohérent que ça tu meurs...
    Et puis pourquoi s'ennuyer à faire compliqué (et, honnêtement, crade) alors que Python nous offre un mécanisme tout fait pour ça ? (soit __slots__ justement)

    Edit: ah, et j'oubliais:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    In [12]: del a.ini
     
    In [13]: a.myValue = "hello"
    - je viens de creer l'attribut myValue avec la valeur 'hello'
     
    In [14]: a.errttght = 47474
    - je viens de creer l'attribut errttght avec la valeur 47474
    Aucune rélle protection donc.

  12. #12
    Membre confirmé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    29
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 29
    Par défaut
    Ayant testé la méthode de Antoine_935, celle ci se voit le mérite d'être plus adaptée a la situation actuelle.

  13. #13
    Membre éprouvé

    Profil pro
    Account Manager
    Inscrit en
    Décembre 2006
    Messages
    2 301
    Détails du profil
    Informations personnelles :
    Localisation : France, Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Account Manager

    Informations forums :
    Inscription : Décembre 2006
    Messages : 2 301
    Par défaut
    Les exemples ci-dessus sont très probants et montrent bien que j'avais juste survoler le problème (faute de temps pour programmer en ce moment...). Merci à tous pour ses rectifications.

  14. #14
    Membre Expert
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Par défaut @ Antoine935
    Mais... mais....
    je ne cherche pas à apporter un code contributif bien ficelé, ni à répondre à un objectif précis. Tout simplement parce que je n’ai pas d’objectif personnel et que rambc n’a pas décrit précisément le sien. Il ne faut pas me houspiller.

    Je m’intéresse simplement à une discussion pour me former en recherchant des solutions et je m’imisce pour signaler à rambc que la “solution“ de the-death est imparfaite. Son post m’a poussé à m’intéresser à la fonction __setattr__() et le tien a judicieusement pointé qu’il fallait regarder les choses de plus près. J’ai donc d’abord cherché réponse à ce problème apparu sans me fixer d’envisager tous les aspects, dans un premier temps.

    Je suis donc bien d’accord que mon dernier code n’est pas parfait. J’ai d’ailleurs pensé après coup à ce qui se passe si l’utilisateur de la classe connaît l’existence du champ ini.
    Je suis aussi bien d’accord que ta solution a ses avantages.
    Mais je prends le problème posé par rambc et son désir de ne pas écrire des champs à la main dans un attribut __slots__ comme base de départ, comme un exercice intellectuel, et je me dis «voyons voir comment on peut répondre au problème ainsi énoncé».

    J’ai lu sur d’autres forums que les développeurs informaticiens sont parfois confrontés à la contrariété de devoir respecter des instructions et développer un code dans une certaine direction quand bien même il y aurait une autre manière de faire plus avantageuse, mais qui ne plaît pas au donneur d’ordre ou au chef qui n’y connaît rien. Dans ces cas là, il faut savoir s’adapter aux contraintes imposées.
    Je pense aussi aux performances littéraires auxquelles parviennent des écrivains comme Georges Perec et le mouvement de l’Oulipo qui s’amusent à se fixer des contraintes à respecter dans l’écriture d’un texte: ça produit des choses aussi intéressantes que quand il n’y a aucune limite fixée.

    Je pars du principe qu’on peut tout faire en informatique avec des outils suffisamment performants, et je crois que Python en est un. Alors je me frotte les mains et je me régale à chercher des solutions. C’est tout.

    Quant à la façon d’avancer, je prends ça comme un problème en cours de résolution, à faire progresser pas à pas en fonction des critiques qui peuvent naître, comme celles que tu as émises. Je ne le prends pas comme un problème auquel apporter une solution parfaitement ficelée du premier coup.




    ------------------------------------------------



    Alors ces critiques justement ?
    Ben c’est vrai que mon code n’est pas terrible sur plusieurs points auxquels je n’avait pas pris suffisamment le temps de penser (suis pas le seul). Mais si on veut, on peut solutionner.



    Eyquem, dans ton modèle, il est strictement interdit de modifier le moindre élément à partir du moment ou ini a été défini. Dommage, si on veut changer un entier on peut aller se faire voir.
    Il suffit de corriger __setattr__() dans ce sens.
    On peut même permettre la modification de certains champs et pas d’autres de façon différenciée si ça nous chante.



    De plus, on peut toujours supprimer des attributs...
    Quand je disais que Python est un bon langage: hop !, __delattr__()




    Ce qui donne

    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
    class A:
        champ1 = "abc"
        def __init__(self,n):
            print "debut d'initialisation d'une instance"
            self.myValue = 5
            self.name = n
            self.CinquanteTreize = 'trombone'
            self.chateau = 'de Chambord'
            self.distance = 187654637782
            self.ghurutsedik = ('saucisson','midinette','arthropode','soleil')
            self.nougatine = 'Montelimar'
            self.__dict__['ini'] = 'faite'
            print "- je viens de creer l'attribut ini avec la valeur'faite'"
            print "fin de l'initialisation de l'instance de  nom  \""+n+'"\n'
        def __setattr__(self, attr, value):
            if 'ini' in self.__dict__:
                if attr in self.__dict__:
                    self.__dict__[attr] = value
                    print "- je viens de modifier l'attribut "+attr+" avec la valeur "+repr(value)
                else:
                    print "Il n'est pas possible de definir des champs d'instance supplementaires:"\
                          +"\nvotre demande de creation du champ d'instance \""+attr+'" est rejetee.'
            else:
                self.__dict__[attr] = value
                print "- je viens de creer l'attribut "+attr+" avec la valeur "+repr(value)
        def __delattr__(self,atirdel):
            if atirdel in self.__dict__:
                print "Vous n'etes pas autorise a supprimer des champs de cette instance: votre demande est rejetee."
            else:
                print "Le champ "+atirdel+" que vous voulez supprimer n'existe pas dans cette instance."
     
     
    a = A('SERENGUETI')
    print 'a.champ1 :',a.champ1
    print 'dir(a) : ',dir(a)
     
     
    print "\n^*^ Tentative de creation du champ d'instance chmap1"
    a.chmap1 = "bcd"
    print 'dir(a) : ',dir(a)
     
    print "\n^*^ Modification de la variable d'instance ghurutsedik"
    a.ghurutsedik = ['0','1','2','3','4','5','6','7','8','9','a','e','i','o','u','y','%','&']
    print 'dir(a) : ',dir(a)
    print 'ghurutsedik : ',a.ghurutsedik
     
    print "\n^*^ Tentative de deletion du champ d'instance chateau"
    del a.chateau
    print 'dir(a) : ',dir(a)
     
    print "\n^*^ Tentative de deletion d'un champ d'instance inexistant"
    del a.saskatchewan
    print 'dir(a) : ',dir(a)
     
     
    print "\n^*^ Tentative de deletion du champ ini"
    del a.ini
    print 'dir(a) : ',dir(a)
    debut d'initialisation d'une instance
    - je viens de creer l'attribut myValue avec la valeur 5
    - je viens de creer l'attribut name avec la valeur 'SERENGUETI'
    - je viens de creer l'attribut CinquanteTreize avec la valeur 'trombone'
    - je viens de creer l'attribut chateau avec la valeur 'de Chambord'
    - je viens de creer l'attribut distance avec la valeur 187654637782L
    - je viens de creer l'attribut ghurutsedik avec la valeur ('saucisson', 'midinette', 'arthropode', 'soleil')
    - je viens de creer l'attribut nougatine avec la valeur 'Montelimar'
    - je viens de creer l'attribut ini avec la valeur'faite'
    fin de l'initialisation de l'instance de nom "SERENGUETI"

    a.champ1 : abc
    dir(a) : ['CinquanteTreize', '__delattr__', '__doc__', '__init__', '__module__', '__setattr__', 'champ1', 'chateau', 'distance', 'ghurutsedik', 'ini', 'myValue', 'name', 'nougatine']

    ^*^ Tentative de creation du champ d'instance chmap1
    Il n'est pas possible de definir des champs d'instance supplementaires:
    votre demande de creation du champ d'instance "chmap1" est rejetee.
    dir(a) : ['CinquanteTreize', '__delattr__', '__doc__', '__init__', '__module__', '__setattr__', 'champ1', 'chateau', 'distance', 'ghurutsedik', 'ini', 'myValue', 'name', 'nougatine']

    ^*^ Modification de la variable d'instance ghurutsedik
    - je viens de modifier l'attribut ghurutsedik avec la valeur ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'e', 'i', 'o', 'u', 'y', '%', '&']
    dir(a) : ['CinquanteTreize', '__delattr__', '__doc__', '__init__', '__module__', '__setattr__', 'champ1', 'chateau', 'distance', 'ghurutsedik', 'ini', 'myValue', 'name', 'nougatine']
    ghurutsedik : ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'e', 'i', 'o', 'u', 'y', '%', '&']

    ^*^ Tentative de deletion du champ d'instance chateau
    Vous n'etes pas autorise a supprimer des champs de cette instance: votre demande est rejetee.
    dir(a) : ['CinquanteTreize', '__delattr__', '__doc__', '__init__', '__module__', '__setattr__', 'champ1', 'chateau', 'distance', 'ghurutsedik', 'ini', 'myValue', 'name', 'nougatine']

    ^*^ Tentative de deletion d'un champ d'instance inexistant
    Le champ saskatchewan que vous voulez supprimer n'existe pas dans cette instance.
    dir(a) : ['CinquanteTreize', '__delattr__', '__doc__', '__init__', '__module__', '__setattr__', 'champ1', 'chateau', 'distance', 'ghurutsedik', 'ini', 'myValue', 'name', 'nougatine']

    ^*^ Tentative de deletion du champ ini
    Vous n'etes pas autorise a supprimer des champs de cette instance: votre demande est rejetee.
    dir(a) : ['CinquanteTreize', '__delattr__', '__doc__', '__init__', '__module__', '__setattr__', 'champ1', 'chateau', 'distance', 'ghurutsedik', 'ini', 'myValue', 'name', 'nougatine']

    Plus incohérent que ça tu meurs...
    Voilà , ça l’est moins. Encore fallait il le vouloir.






    Et puis pourquoi s'ennuyer à faire compliqué (et, honnêtement, crade) alors que Python nous offre un mécanisme tout fait pour ça ? (soit __slots__ justement)
    Compliqué ?
    ->
    __slots__
    A declaration inside a new-style class that saves memory by pre-declaring space for instance attributes and eliminating instance dictionaries. Though popular, the technique is somewhat tricky to get right and is best reserved for rare cases where there are large numbers of instances in a memory-critical application.
    http://www.python.org/doc/2.6.2/glos...tml#term-slots
    Ceci dit, si on regarde de près ce qu’entraîne la définition de __slots__ dans une classe:
    __slots__ reserves space for the declared variables and prevents the automatic creation of __dict__ and __weakref__ for each instance.
    Without a __dict__ variable, instances cannot be assigned new variables not listed in the __slots__ definition.
    http://www.python.org/doc/2.6.2/refe...l.html#__slots
    Ceci qui signifie qu’on ne peut pas assigner de nouvelles variables à une instance si elles ne sont pas listées dans __slots__ ; c’est d’ailleurs bien pour cette raison que tu préconises l’emploi de __slots__ .
    Mais cela veut bien dire que le programme va vérifier si une variable que l’on cherche à assigner à une instance est ou non dans __slots__ ,
    ce qui n’a rien de bien différent de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    if attr in self.__dict__:
        self.__dict__[attr] = value
        print "- je viens de modifier l'attribut "+attr+" avec la valeur "+repr(value)
    que j’ai rajouté dans mon code.
    Seulement dans mon code c’est apparent et c’est en Python, tandis qu’avec __slots__ , cette verification est invisible et écrite en C. La différence n’est pas transcendante.



    Je ne tiens pas spécialement à ma solution, mais on voit bien qu’elle est possible. Et si elle a des limitations, le recours à __slots__ a aussi les siennes qui sont différentes, et ma solution a des possibilités qu’on n’a pas avec __slots__ .
    Ppar exemple, elle permet des variations, du genre n’autoriser la modification de la valeur d’un champ que si la valeur d’un autre champ ou les valeurs de plusieurs autre champs vérifient individuellement ou par le biais d’une fonction dont elles sont les paramètres une certaine condition.

    Je veux dire qu’il est intéressant de découvrir des applications possibles dans la direction __setattr__() en ne se limitant pas à l’usage de __slots__ , et que c’est moins fructueux de renvoyer une option d’un revers de la main.





    crade ?
    C’est à dire ? Ça veut dire quoi ?





    Pour le cas où l’utilisateur , connaissant le nom ini du champ utilisé , peut opérer des modifications aprés sa suppression: c’est réglé, une fois qu’on a installé __delattr__() dans la classe.

    La question qui me vient alors à l’esprit:
    on peut supprimer des attributs champs d’une instance, mais peut on supprimer des attributs méthodes ? Car si oui, il suffit de supprimer __delattr__() puis ini dans la foulée.

    Si ça ne suffisait pas, on pourrait encore compliquer les choses en donnant à ini une valeur de nature cryptographique.






    Cette discussion m’a stimulé à m’intéresser de plus près à __slots__ . C’est à ça que me sert le forum (entre autres), stimuler ma découverte d’aspects divers de Python.

  15. #15
    Membre émérite
    Avatar de Antoine_935
    Profil pro
    Développeur web/mobile
    Inscrit en
    Juillet 2006
    Messages
    883
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur web/mobile

    Informations forums :
    Inscription : Juillet 2006
    Messages : 883
    Par défaut
    Citation Envoyé par eyquem Voir le message
    Seulement dans mon code c’est apparent et c’est en Python, tandis qu’avec __slots__ , cette verification est invisible et écrite en C. La différence n’est pas transcendante.
    Que ce soit codé en C ou en Python je m'en fiche pas mal.
    Le souci est que ton exemple met en jeu une contrainte de plus: on doit définir un attribut ini. Je trouve ça largement plus contraignant que de lister quelques variables dans un tuple...
    De plus, la méthode __slots__ ne peut pas être bypassée. Même en redéfinissant __slots__ après la création de la classe.



    Je ne tiens pas spécialement à ma solution, mais on voit bien qu’elle est possible. Et si elle a des limitations, le recours à __slots__ a aussi les siennes qui sont différentes, et ma solution a des possibilités qu’on n’a pas avec __slots__ .
    Ppar exemple, elle permet des variations, du genre n’autoriser la modification de la valeur d’un champ que si la valeur d’un autre champ ou les valeurs de plusieurs autre champs vérifient individuellement ou par le biais d’une fonction dont elles sont les paramètres une certaine condition.
    Ton exemple relève d'une très mauvaise conception.
    Déjà, en soi, jamais au grand jamais des attributs ne devraient être publiques.
    Pour ce genre de vérifications, on utilise des setters, ou properties puisqu'on en a en Python. C'est largement plus maintenable et propre. Je vous laisse deviner pourquoi.


    C’est à dire ? Ça veut dire quoi ?
    Pas propre, impossible à maintenir. Simple exemple: je veux faire une classe qui dérive de la tienne. Ca va pas être marrant...
    1. Je dois appeler ton constructeur en dernier dans le mien, sinon je ne peux plus définir d'attribut. Or c'est généralement une très mauvaise idée d'appeler les constructeurs parents ailleurs qu'au début.
    2. Et si je veux redéfinir le __setattr__ pour, par exemple, faire un proxy d'un autre objet, ou proxy réseau (style CORBA/COM), quid ?




    La question qui me vient alors à l’esprit:
    on peut supprimer des attributs champs d’une instance, mais peut on supprimer des attributs méthodes ? Car si oui, il suffit de supprimer __delattr__() puis ini dans la foulée.
    Normalement, si ton __delattr__ interdit la supression, il devrait être impossible de le supprimer lui-même. Enfin, si on passe par une variable. En supprimant le __delattr__ au niveau de la classe c'est vite rêglé. On pourrait imaginer une métaclasse avec un __delattr__ aussi. Mais quid si on supprime son __delattr__ à elle ? Alors on pourrait lui coller une métaclasse qui...

    Si ça ne suffisait pas, on pourrait encore compliquer les choses en donnant à ini une valeur de nature cryptographique.
    Un genre d'obfuscation de ton propre code ?


    Enfin, si vous faites avec le __setattr__, pensez à prendre une photo de votre tête quand vous retoucherez au code dans 5 mois

    import this

  16. #16
    Membre Expert
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Par défaut
    Le souci est que ton exemple met en jeu une contrainte de plus: on doit définir un attribut ini. Je trouve ça largement plus contraignant que de lister quelques variables dans un tuple...
    Je l’ai dit: je me place en situation de répondre à un problème posé par rambc aprés qu’il ait dit qu’il ne veut pas avoir à écrire des variables dans une liste. Je ne cherche pas à le convaincre que ce serait mieux qu’il change son idée.

    Dès lors, il est certain que pour éviter quelque chose d’un coté, on est obligé d’en faire plus d’un autre.

    Si sa démarche est de toujours trouver des solutions en moins de 12 caractères, il va devoir limiter ses objectifs de développement informatique. Ce n’est pas mon souci à moi.





    Je ne comprends pas toutes tes remarques.
    Je les prends comme pistes d’étude: je vais commencer par regarder les getters, setters et properties.
    Ainsi que clarifier mes idées sur publiques-privées.

  17. #17
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 495
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 495
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    J'ai suivi ce fil avec beaucoup d'intérêt, et je propose une solution complémentaire qui s'inspire des solutions précédentes:

    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 A(object):
        def __init__(self,n):
            self.name = n
            self.CinquanteTreize = 'trombone'
            self.distance = 187654637782
            self.ghurutsedik = ('saucisson','midinette','arthropode','soleil')
            self.nougatine = 'Montelimar'
            A.__setattr__ = A.__monsetattr__
     
        def __monsetattr__(self, attr, value):
            if attr in self.__dict__:
                self.__dict__[attr] = value
            else:
                raise Exception, "Erreur: creation de la variable " + attr + " interdite
    Le principe est de permettre l'exécution de __init__ comme d'hab, mais à la fin, de détourner la méthode de la classe __setattr__ vers une nouvelle méthode de classe __monsetattr__. Cela permet d'éviter de gérer et de questionner un drapeau.

    Ainsi:

    - pendant l'initialisation, tout est possible
    - après l'initialisation on peut encore modifier les attributs existants, mais pas en créer de nouveaux.

    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
     
    x = A("toto")
     
    # verif que les attributs initialisés par __init__ sont accessibles:
    print x.name
    print x.nougatine  # mmm... j'aime bien ce genre d'exemple ;-)
     
    # modif de l'attribut name:
    x.name = "tata"
    print x.name
     
    # tentative de création d'un nouvel attribut:
    x.machin = "titi"
    print x.machin
    Dans cet exemple, seule la dernière tentative échoue en générant l'exception: "Exception: Erreur: creation de la variable machin interdite"

    Tyrtamos

  18. #18
    Membre émérite
    Avatar de Antoine_935
    Profil pro
    Développeur web/mobile
    Inscrit en
    Juillet 2006
    Messages
    883
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur web/mobile

    Informations forums :
    Inscription : Juillet 2006
    Messages : 883
    Par défaut
    Ta solution, bien qu'élégante, souffre d'un problème.
    Tu remplaces le __setattr__ de la classe A.
    Autrement dit, lorsqu'on essayera de créer un deuxième objet A, on aura une erreur:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    In [2]: a = A("Salut")
     
    In [2]: a = A("Salut")
    ---------------------------------------------------------------------------
    Exception                                 Traceback (most recent call last)
     
    C:\Windows\system32\<ipython console> in <module>()
     
    C:\Windows\system32\<ipython console> in __init__(self, n)
     
    C:\Windows\system32\<ipython console> in __monsetattr__(self, attr, value)
     
    Exception: Erreur: creation de la variable name interdite
    Bon, puisque tout le monde y va de son exemple, je vais quand même sortir le mien, plutôt que de sortir des phrases qui sonnent peut-être comme "non-sens" à certains.

    Note en passant: Sous IPython, les properties sont visiblement appelées 2 fois pour chaque get... Etrange, je vais leur signaler le bug. Tout va bien sous le Python tout seul.
    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
    >>> class Person(object):
    ...     __slots__ = ("__name", "__age")
    ...
    ...     def __init__(self):
    ...         self.__age = None
    ...         self.__name = None
    ...
    ...     @property
    ...     def name(self):
    ...         return self.__name
    ...
    ...     @name.setter
    ...     def name(self, new):
    ...         # Effectuer des vérifications sur la nouvelle valeur
    ...         self.__name = new
    ...
    ...     @property
    ...     def age(self):
    ...         # Vérifier, calculer, on peut tout faire ici
    ...         return self.__age
    ...
    ...     @age.setter
    ...     def age(self, new):
    ...         if new < self.__age:
    ...             raise ValueError("On ne peut rajeunir personne")
    ...
    ...         self.__age = new
    ...
    >>>
    >>> antoine = Person()
    >>> antoine.name = "Antoine"
    >>> antoine.age = 20
    >>>
    >>> antoine.age = 19
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 25, in age
    ValueError: On ne peut rajeunir personne
    >>> antoine.age = 21
    >>>
    Variante: on pourrait passer le nom à la construction de l'objet, et retirer le setter. Ca permet d'avoir une propriété qu'on ne peut pas redéfinir.

  19. #19
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 495
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 495
    Billets dans le blog
    6
    Par défaut
    Effectivement, ma solution a un grave défaut. Merci Antoine_935.

    Avant, j'avais essayé "self.__setattr__ = self.__monsetattr__" qui me semblait plus normal puisque ça ne touchait que l'instance. Mais ça ne marchait pas (ça ne détournait pas la méthode) et je n'ai pas compris pourquoi. J'avais pourtant utilisé ce genre de détournement avec succès pour d'autres problèmes.

    Y a-t-il une explication?

    Tyrtamos

  20. #20
    Membre émérite
    Avatar de Antoine_935
    Profil pro
    Développeur web/mobile
    Inscrit en
    Juillet 2006
    Messages
    883
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur web/mobile

    Informations forums :
    Inscription : Juillet 2006
    Messages : 883
    Par défaut
    Selon que la classe soit "new-style" ou "old-style" (c'est à dire qu'elle dérive d'object ou non), certains attributs spéciaux sont redéfinissables.
    Malgré tout, __setattr__ (et ses associés j'imagine) semblent bien être impossibles à redéfinir à la volé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
    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
    >>> o = OldStyle()
    Original
    >>> o.hello = "haha"
    Original
    >>> class OldStyle:
    ...     def __init__(self):
    ...         self.__setattr__ = self.restricted
    ...
    ...     def restricted(self, attr, value):
    ...         print("This is restricted")
    ...
    >>> o = OldStyle()
    >>> o.haha = "hello"
    >>> o.haha
    'hello'
     
    >>> class OldStyle2:
    ...     def __init__(self):
    ...        self.__dict__["__setattr__"] = self.restricted
    ...
    ...     def __setattr__(self, attr, value):
    ...         print("Original")
    ...
    ...     def restricted(self, attr, value):
    ...         print("Restricted")
    ...
    >>> o2 = OldStyle2()
    >>> o2.haha = "hello"
    Original
     
     
    >>> class OldStyle3:
    ...     def __init__(self):
    ...         self.__eq__ = self.modified
    ...
    ...     def __eq__(self, other):
    ...         print("Original")
    ...         return False
    ...
    ...     def modified(self, other):
    ...         print("Modified")
    ...         return True
    ...
    >>> o3 = OldStyle3()
    >>> o3 == "hello"
    Modified
    True
     
    >>> class NewStyle(object):
    ...     def __init__(self):
    ...         self.__eq__ = self.modified
    ...
    ...     def __eq__(self, other):
    ...         print("Original")
    ...         return False
    ...
    ...     def modified(self, other):
    ...         print("Modified")
    ...         return True
    ...
    >>> n = NewStyle()
    >>> n == "hello"
    Original
    False
    Les attributs non spéciaux, quant à eux, sont toujours redéfinissables:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    >>> class Standard(object):
    ...     def __init__(self):
    ...         self.target = self.replacement
    ...
    ...     def target(self):
    ...         print("Target")
    ...
    ...     def replacement(self):
    ...         print("Replacement")
    ...
    >>> s = Standard()
    >>> s.target()
    Replacement

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. création dynamique de classes
    Par r83 dans le forum Windows Forms
    Réponses: 2
    Dernier message: 16/12/2008, 17h04
  2. Réponses: 3
    Dernier message: 06/02/2008, 18h11
  3. Réponses: 1
    Dernier message: 19/07/2007, 12h39
  4. [POO] Création dynamique de variables de classe
    Par Philoulheinz dans le forum Langage
    Réponses: 2
    Dernier message: 24/03/2007, 15h38
  5. Création dynamique de méthode sur une classe ?
    Par elitost dans le forum Général Java
    Réponses: 9
    Dernier message: 18/10/2005, 14h47

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