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 :
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.
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
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 :
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
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_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
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
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 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
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
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", ""))
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 def public(func): return safe_decorator(func, "public") def private(func): return safe_decorator(func, "private") def protected(func): return safe_decorator(func, "protected")
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 !
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 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
Partager