2010-07-30 17 views
5

Je veux pouvoir demander à la méthode __init__ d'une classe quels sont ses paramètres. L'approche directe est la suivante:Recherche des paramètres d'une fonction en Python

cls.__init__.__func__.__code__.co_varnames[:code.co_argcount] 

Cependant, cela ne fonctionnera pas si la classe a des décorateurs. Il donnera la liste des paramètres pour la fonction renvoyée par le décorateur. Je veux descendre à la méthode originale __init__ et obtenir ces paramètres d'origine. Dans le cas d'un décorateur, la fonction décorateur va être trouvé dans la fermeture de la fonction renvoyée par le décorateur:

cls.__init__.__func__.__closure__[0] 

Cependant, il est plus compliqué s'il y a d'autres choses dans la fermeture, qui décorateurs peut faire de temps en temps:

def Something(test): 
    def decorator(func): 
     def newfunc(self): 
      stuff = test 
      return func(self) 
     return newfunc 
    return decorator 

def test(): 
    class Test(object): 
     @Something(4) 
     def something(self): 
      print Test 
    return Test 

test().something.__func__.__closure__ 
(<cell at 0xb7ce7584: int object at 0x81b208c>, <cell at 0xb7ce7614: function object at 0xb7ce6994>) 

Et puis je dois décider si je veux les paramètres de décorateur ou les paramètres de la fonction d'origine. La fonction renvoyée par le décorateur peut avoir *args et **kwargs pour ses paramètres. Et s'il y a plusieurs décorateurs et que je dois décider quel est celui qui me tient à cœur?

Alors, quelle est la meilleure façon de trouver les paramètres d'une fonction même lorsque la fonction peut être décorée? Aussi, quel est le meilleur moyen de ramener une chaîne de décorateurs à la fonction décorée?

Mise à jour:

Voici efficacement comment je fais ce moment (les noms ont été changés pour protéger l'identité de l'accusé):

import abc 
import collections 

IGNORED_PARAMS = ("self",) 
DEFAULT_PARAM_MAPPING = {} 
DEFAULT_DEFAULT_PARAMS = {} 

class DICT_MAPPING_Placeholder(object): 
    def __get__(self, obj, type): 
     DICT_MAPPING = {} 
     for key in type.PARAMS: 
      DICT_MAPPING[key] = None 
     for cls in type.mro(): 
      if "__init__" in cls.__dict__: 
       cls.DICT_MAPPING = DICT_MAPPING 
       break 
     return DICT_MAPPING 

class PARAM_MAPPING_Placeholder(object): 
    def __get__(self, obj, type): 
     for cls in type.mro(): 
      if "__init__" in cls.__dict__: 
       cls.PARAM_MAPPING = DEFAULT_PARAM_MAPPING 
       break 
     return DEFAULT_PARAM_MAPPING 

class DEFAULT_PARAMS_Placeholder(object): 
    def __get__(self, obj, type): 
     for cls in type.mro(): 
      if "__init__" in cls.__dict__: 
       cls.DEFAULT_PARAMS = DEFAULT_DEFAULT_PARAMS 
       break 
     return DEFAULT_DEFAULT_PARAMS 

class PARAMS_Placeholder(object): 
    def __get__(self, obj, type): 
     func = type.__init__.__func__ 
     # unwrap decorators here 
     code = func.__code__ 
     keys = list(code.co_varnames[:code.co_argcount]) 
     for name in IGNORED_PARAMS: 
      try: keys.remove(name) 
      except ValueError: pass 
     for cls in type.mro(): 
      if "__init__" in cls.__dict__: 
       cls.PARAMS = tuple(keys) 
       break 
     return tuple(keys) 

class BaseMeta(abc.ABCMeta): 
    def __init__(self, name, bases, dict): 
     super(BaseMeta, self).__init__(name, bases, dict) 
     if "__init__" not in dict: 
      return 
     if "PARAMS" not in dict: 
      self.PARAMS = PARAMS_Placeholder() 
     if "DEFAULT_PARAMS" not in dict: 
      self.DEFAULT_PARAMS = DEFAULT_PARAMS_Placeholder() 
     if "PARAM_MAPPING" not in dict: 
      self.PARAM_MAPPING = PARAM_MAPPING_Placeholder() 
     if "DICT_MAPPING" not in dict: 
      self.DICT_MAPPING = DICT_MAPPING_Placeholder() 


class Base(collections.Mapping): 
    __metaclass__ = BaseMeta 
    """ 
    Dict-like class that uses its __init__ params for default keys. 

    Override PARAMS, DEFAULT_PARAMS, PARAM_MAPPING, and DICT_MAPPING 
    in the subclass definition to give non-default behavior. 

    """ 
    def __init__(self): 
     pass 
    def __nonzero__(self): 
     """Handle bool casting instead of __len__.""" 
     return True 
    def __getitem__(self, key): 
     action = self.DICT_MAPPING[key] 
     if action is None: 
      return getattr(self, key) 
     try: 
      return action(self) 
     except AttributeError: 
      return getattr(self, action) 
    def __iter__(self): 
     return iter(self.DICT_MAPPING) 
    def __len__(self): 
     return len(self.DICT_MAPPING) 

print Base.PARAMS 
#() 
print dict(Base()) 
# {} 

A ce stade, les rapports de base valeurs inintéressantes pour les quatre contants et la version dict des instances sont vides. Toutefois, si vous sous-classe vous pouvez remplacer l'un des quatre ou vous pouvez inclure d'autres paramètres à la __init__:

class Sub1(Base): 
    def __init__(self, one, two): 
     super(Sub1, self).__init__() 
     self.one = one 
     self.two = two 

Sub1.PARAMS 
# ("one", "two") 
dict(Sub1(1,2)) 
# {"one": 1, "two": 2} 

class Sub2(Base): 
    PARAMS = ("first", "second") 
    def __init__(self, one, two): 
     super(Sub2, self).__init__() 
     self.first = one 
     self.second = two 

Sub2.PARAMS 
# ("first", "second") 
dict(Sub2(1,2)) 
# {"first": 1, "second": 2} 
+1

Pourquoi diable voudriez-vous cela? – delnan

+4

Le fait que cela soit si difficile devrait vous indiquer que ce n'est pas la bonne chose à faire. – katrielalex

+0

Je veux être capable d'utiliser des objets d'une classe en tant que dicts et avoir des contrôles explicites sur les clés exposées par '__getitem__' et' __iter__'. Les paramètres de '__init__' font d'excellentes clés par défaut, donc je les tire par programmation. J'essaie juste d'aborder les cas de coin, comme lorsque les descripteurs s'impliquent. –

Répondre

3

Tenir compte ce décorateur:

def rickroll(old_function): 
    return lambda junk, junk1, junk2: "Never Going To Give You Up" 

class Foo(object): 
    @rickroll 
    def bar(self, p1, p2): 
     return p1 * p2 

print Foo().bar(1, 2) 

Dans ce document, le décorateur de rickroll prend la barre méthode, le supprime, le remplace par une nouvelle fonction qui ignore ses paramètres nommés différemment (et éventuellement numérotés!) et renvoie à la place une ligne d'une chanson classique.

Il n'y a pas d'autres références à la fonction d'origine, et le garbage collector peut venir et l'enlever quand il le veut.

Dans un tel cas, je ne vois pas comment vous pourriez trouver les noms de paramètres p1 et p2. D'après ce que je comprends, même l'interprète Python lui-même n'a aucune idée de ce qu'on appelait autrefois.

+0

LOL Je vous ai trouvé juste pour avoir ce décorateur rickroll. Maintenant, à la lecture de votre réponse ... –

+0

C'est un excellent point. Je n'avais certainement pas considéré un décorateur qui renvoie une fonction entièrement différente et sans rapport. Vous avez raison à propos d'un manque de références. Cela a du sens, mais en même temps semble inintéressant que la fonction définie dans la classe disparaisse. Vous m'avez donné quelque chose à penser. Merci! –

+0

J'ai poursuivi ce sujet d'une manière plus spécifique à http://stackoverflow.com/questions/3481872/decorated-for-python-decorators –