2010-07-30 11 views
5

Quiero poder preguntar el método __init__ de una clase cuáles son sus parámetros. El enfoque directo es el siguiente:Encontrar los parámetros de una función en Python

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

Sin embargo, eso no funcionará si la clase tiene ningún decoradores. Proporcionará la lista de parámetros para la función devuelta por el decorador. Quiero llegar al método original __init__ y obtener esos parámetros originales. En el caso de un decorador, la función decoradora va a ser encontrado en el cierre de la función devuelta por el decorador:

cls.__init__.__func__.__closure__[0] 

Sin embargo, es más complicado si hay otras cosas en el cierre, que decoradores puede hacer de vez en cuando:

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>) 

Y luego tengo que decidir si quiero los parámetros de decorador o los parámetros de la función original. La función devuelta por el decorador podría tener *args y **kwargs para sus parámetros. ¿Qué pasa si hay decoradores múltiples y tengo que decidir cuál es el que me importa?

Entonces, ¿cuál es la mejor manera de encontrar los parámetros de una función incluso cuando la función puede estar decorada? Además, ¿cuál es la mejor manera de ir por una cadena de decoradores a la función decorada?

Actualización:

Aquí es efectivamente cómo lo estoy haciendo esto ahora (los nombres han sido cambiados para proteger la identidad de los acusados):

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()) 
# {} 

en este informes punto base poco interesantes valores para los cuatro contants y la versión dict de instancias están vacíos. Sin embargo, si subclase se puede reemplazar cualquiera de los cuatro, o puede incluir otros parámetros a 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

¿Por qué diablos quieres eso? – delnan

+4

El hecho de que esto sea tan difícil debería indicarle que no es lo correcto. – katrielalex

+0

Quiero ser capaz de usar objetos de una clase como dicts y tener controles explícitos sobre qué claves están expuestas a través de '__getitem__' y' __iter__'. Los parámetros de '__init__' hacen que las teclas predeterminadas sean geniales, así que las estoy extrayendo programáticamente. Solo trato de abordar casos de esquina, como cuando los descriptores se involucran. –

Respuesta

3

consideran este decorador:

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) 

En ella, el decorador rickroll toma la barra método, lo descarta, lo reemplaza con una nueva función que ignora los parámetros con nombres diferentes (¡y posiblemente numerados!) y en su lugar devuelve una línea de una canción clásica.

No hay más referencias a la función original, y el recolector de basura puede venir y eliminarlo cuando lo desee.

En tal caso, no puedo ver cómo se pueden encontrar los nombres de los parámetros p1 y p2. Según entiendo, incluso el intérprete de Python en sí no tiene idea de cómo se les solía llamar.

+0

LOL Te puse en pie por tener ese decorador de rickroll. Ahora a leer tu respuesta ... –

+0

Ese es un gran punto. Definitivamente no había considerado un decorador que devuelva una función completamente diferente, no relacionada. Tienes razón sobre la falta de referencias. Tiene sentido, pero al mismo tiempo parece poco intuitivo que la función definida en la clase desaparecería. Me has dado algo en qué pensar. ¡Gracias! –

+0

He estado persiguiendo este tema de una manera más específica en http://stackoverflow.com/questions/3481872/decorated-for-python-decorators –

Cuestiones relacionadas