Encontrar los parámetros de una función en Python

Quiero poder preguntar al método __init__ una clase cuáles son sus parámetros. El enfoque directo es el siguiente:

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

Sin embargo, eso no funcionará si la clase tiene decoradores. Le dará la lista de parámetros para la función devuelta por el decorador. Quiero llegar al método __init__ original y obtener esos parámetros originales. En el caso de un decorador, la función de decorador se encontrará 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 los decoradores pueden 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__ (, ) 

Y luego tengo que decidir si quiero los parámetros del 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 varios decoradores 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 volver a bajar una cadena de decoradores a la función decorada?

Actualizar:

He aquí cómo lo hago en este momento (los nombres se han cambiado para proteger la identidad del acusado):

 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 punto, la Base informa valores sin interés para los cuatro contantes y la versión dict de las instancias está vacía. Sin embargo, si hace una subclase, puede anular cualquiera de los cuatro, o puede incluir otros parámetros en el __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} 

Considera 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 él, el decorador rickroll toma el método de la barra, lo descarta, lo reemplaza con una nueva función que ignora sus 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 eliminarla cuando lo desee.

En tal caso, no puedo ver cómo podría encontrar los nombres de los parámetros p1 y p2. En mi opinión, incluso el intérprete de Python en sí mismo no tiene idea de cómo se llamaban.