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 :

membres de classe private/protected (yet another try)


Sujet :

Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre à l'essai
    Homme Profil pro
    Directeur technique
    Inscrit en
    Août 2012
    Messages
    6
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Directeur technique
    Secteur : Communication - Médias

    Informations forums :
    Inscription : Août 2012
    Messages : 6
    Par défaut membres de classe private/protected (yet another try)
    Salutations,

    long time no see (oui oui je vais éviter l'anglais ;p), à vrai dire depuis les concours c2i diggers, ça date, salut Nix et … wazaaaaa !

    Pour des raisons que je suis prêt à défendre même si ça n'est pas le sujet, j'essaye de mettre en place un mécanisme "fort" de gestion des membres de classes public/protected/private. Fort mais simple, l'idée étant que ça doit rester léger dans l'utilisation …

    Après de nombreuses recherches et essais ratés, j'y suis presque, mais ça coince

    les contraintes :
    - doit passer sur toutes les versions de python à partir de la 2.5, sans tests bourrins sur du sys.version_info dans les modules (2.5, 2.6 et 2.7 en activité, 3.x à l'horizon)
    - le plus léger possible dans la syntaxe pour ne pas polluer la définition des classes
    - toujours pour ne pas polluer, pas de brouzouf au niveau des locales de la classe, tout doit se passer dans les accesseurs
    - fonctionnement classique : public sans restriction, private réservé à la classe, protected accessible aux sous-classes
    - support complet (get/set/delattr)
    - respect des conventions python en cas d'inspection (simple / double underscore)

    Mon approche actuelle se base sur les décorateurs qui sont en partie viables dès la 2.5 tant qu'on reste dans une utilisation simple (pas de @property / @prop.setter/deleter, pas de surcharge sale des __setattr__ __getattr__ non plus à l'inverse). Le but étant de me retrouver avec des définition de membres de ce type :

    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
     
    from MClasses import public, protected, private
     
    class MyClass(object):
    	def __init__(self):
    		self._pub    = 42
    		self._prot   = 42
    		self.__priv  = 42
     
            @public
    	def pub(): pass
     
            @protected
    	def prot(): pass
     
            @private
    	def priv(): pass
     
    	def assign(self):
    		self.pub  = 666
    		self.prot  = 666
    		self.priv  = 666
     
    class MySubClass(MyClass):
    	pass
    Dans ce contexte, un myclass.assign() doit marcher de bout en bout, un mysubclass.assign() doit lever une exception sur le self.priv, et une affectation de l'extérieur doit péter sur prot et priv.

    Je coince pour l'instant à la récupération de la classe d'origine d'un membre dans les decorateurs, étant donné que la classe n'existe justement pas lors du chargement de la définition … j'ai tenté de créer une classe vide juste avant la "vraie" définition (class MyClass(object): pass) pour pouvoir la passer en argument (genre @public(MyClass)), mais l'id diffère (self.__class__ != MyClass), et la recherche d'héritage par issubclass/isinstance échoue, logique.

    Quelques bouts de mon implémentation super naïve :

    get_call_class.py, pour récupérer la classe de l'objet qui accède à un membre :
    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
     
    import inspect
     
    from   get_frame_class import get_frame_class
     
    def get_call_class(depth=1):
        """class get_call_class(int depth=1)                                                                              
                                                                                                                          
        Returns the current calling class at given depth (1 by default so that we get                                     
        the first container available)."""
     
        # add 2 depth to avoid get_call_class and get_frame_class levels                                                  
        depth += 2
     
        stack = inspect.stack()
        if stack and len(stack) > depth and stack[depth]:
            return get_frame_class(stack[depth][0])
     
        return None
    get_frame_class.py, pour extraire la classe à partir d'une frame :
    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
     
    import inspect
     
    def get_frame_class(obj):
        """class get_frame_class(frame obj)                                                                               
                                                                                                                          
        Returns the class object for the given frame (see inspect module)."""
     
        args, _, _, value_dict = inspect.getargvalues(obj)
        if len(args) and args[0] == 'self':
            instance = value_dict.get('self', None)
            if instance:
                return getattr(instance, '__class__', None)
     
        return None
    get_func_class.py pour avoir la classe de l'objet qui a défini un membre / méthode (c'est là que ça bloque ;p)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    import inspect
     
    def get_func_class(func):
      if hasattr(func, "im_class"):
        for cls in inspect.getmro(func.im_class):
          if func.__name__ in cls.__dict__:
            return cls
     
      return None
    safe_decorator.py, un décorateur central appelé par les décorateurs public/private/protected lors de la construction, merci stack overflow !
    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
     
    from get_func_class import get_func_class
    from safe_getattr   import safe_getattr
    from safe_setattr   import safe_setattr
    from safe_delattr   import safe_delattr
     
    def safe_decorator(func, scope):
        ops = func() or {}
        name = ops.get("prefix", "__" if scope == "private" else "_") + func.__name__
     
        cls = get_func_class(func)
     
        fget = ops.get("fget", lambda self: safe_getattr(self, name, scope, cls))
        fset = ops.get("fset", lambda self, value: safe_setattr(self, name, value, scope, cls))
        fdel = ops.get("fdel", lambda self: safe_delattr(self, name, scope, cls))
     
        return property(fget, fset, fdel, ops.get("doc", ""))
    public.py, private.py, protected.py, les surcharges de safe_decorator, passant juste un argument "scope" pour préciser dans quel cas on se trouve :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    def public(func):                                                                                                   
        return safe_decorator(func, "public")
     
    def private(func):                                                                                                   
        return safe_decorator(func, "private")
     
    def protected(func):                                                                                                   
        return safe_decorator(func, "protected")
    safe_setattr.py, le setter générique (laissons de côté les safe_getattr et safe_delattr pour l'instant, il suffit de les remplacer par de simples getattr/delattr)
    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
     
    from get_call_class import get_call_class
     
    def safe_setattr(self, name, value, scope, cls):
        caller = get_call_class()
        print "SETATTR " + name + " " + str(cls) + " " + str(caller)
     
        doit = True
        if scope == "private":
            doit = caller == cls
        elif scope == "protected":
            doit = cls and issubclass(caller, cls)
     
        if doit:
            setattr(self, name, value)
        else:
    	raise AttributeError(str(cls) + "." + name + " is " + scope + " !")
    La question principale reste donc : comment récupérer la classe de définition d'un membre dans un décorateur, ou en tout cas un élément de comparaison avec la classe appelante ? Toute astuce sera la bienvenue !

    La question subsidiaire : l'approche vous semble viable ? réalisable ? pythonesque ? ^^

    Quoi qu'il en soit merci aux courageu(x|ses) qui auront eu le courage de lire cet horrible pavé jusqu'au bout !

    Tonio

  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
    Bonjour,

    functools.wraps (ou @abc.abstractmethod mais >2.6) ?

    Mais bon... Tout cela me semble bien loin du 'programmeur responsable' Python.

    @+

  3. #3
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    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 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Le mécanisme intégré à Python pour rendre une méthode "privée" est très simple: il suffit de préfixer son nom avec 2 blancs soulignés ('__'):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Test(object):
     
        def fonctionpublique(self):
            pass
     
        def __fonctionprivee(self):
            pass
     
    a = Test()
    a.fonctionpublique()
    a.__fonctionprivee() # => exception: AttributeError: 'Test' object has no attribute '__fonctionprivee'
    Toute tentative d'appel externe à la méthode "__fonctionprivee" déclenche une exception: "AttributeError: 'Test' object has no attribute '__fonctionprivee' "

    C'est vrai aussi pour les variables d'instance.

    Sans vouloir te freiner dans ton élan: tu es sûr que ça ne suffit pas?

  4. #4
    Membre Expert
    Profil pro
    Développeur en systèmes embarqués retraité
    Inscrit en
    Mars 2006
    Messages
    952
    Détails du profil
    Informations personnelles :
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Mars 2006
    Messages : 952
    Par défaut
    Salut,
    Citation Envoyé par tyrtamos Voir le message
    Le mécanisme intégré à Python pour rendre une méthode "privée" est très simple: il suffit de préfixer son nom avec 2 blancs soulignés ('__'): .../... C'est vrai aussi pour les variables d'instance.
    C'est ça que j'aime bien sur ce forum... Tous les jours, j'apprends!

    A+

    Pfeuh

  5. #5
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 742
    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 742
    Par défaut
    Salut,
    Joli chantier!
    Je coince pour l'instant à la récupération de la classe d'origine d'un membre dans les decorateurs, étant donné que la classe n'existe justement pas lors du chargement de la définition … j'ai tenté de créer une classe vide juste avant la "vraie" définition (class MyClass(object): pass) pour pouvoir la passer en argument (genre @public(MyClass)), mais l'id diffère (self.__class__ != MyClass), et la recherche d'héritage par issubclass/isinstance échoue, logique.
    Si vous faites abstraction de la possibilité de modifier dynamiquement une classe, i.e. en restant "statique", passer par les "metaclass" pourrait être une possibilité (à explorer): les méthodes __new__ et __init__ étant appelées "avant" l'initialisation des différents attributs de la classe.

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

  6. #6
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par pfeuh Voir le message
    Salut,
    C'est ça que j'aime bien sur ce forum... Tous les jours, j'apprends!
    A+
    Pfeuh
    Ce mécanisme est connu sous le nom de name mangling. Voir la partie 9.6 sur les classes : http://docs.python.org/tutorial/classes.html ^^

  7. #7
    Membre Expert
    Profil pro
    Développeur en systèmes embarqués retraité
    Inscrit en
    Mars 2006
    Messages
    952
    Détails du profil
    Informations personnelles :
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Mars 2006
    Messages : 952
    Par défaut
    Citation Envoyé par Bktero Voir le message
    Ce mécanisme est connu sous le nom de name mangling. Voir la partie 9.6 sur les classes : http://docs.python.org/tutorial/classes.html ^^
    Génial, y'a même une ancre sur le titre du chapitre, du coup on peut donner le lien direct:
    9.6. Private Variables and Class-local References

    A+

    Pfeuh

Discussions similaires

  1. [reflexion] membre private & protected
    Par ZaaN dans le forum C#
    Réponses: 2
    Dernier message: 28/03/2008, 15h21
  2. Réponses: 3
    Dernier message: 12/01/2006, 21h26
  3. [POO] vider un array membre de classe
    Par jlf dans le forum Langage
    Réponses: 2
    Dernier message: 20/10/2005, 10h54
  4. Problèmes de fonctions membres de classe templates, gcc3.3.6
    Par yves.dessertine dans le forum Autres éditeurs
    Réponses: 12
    Dernier message: 17/10/2005, 21h36
  5. Private - Protected
    Par Argonz dans le forum C++
    Réponses: 11
    Dernier message: 06/08/2004, 16h21

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