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}
¿Por qué diablos quieres eso? – delnan
El hecho de que esto sea tan difícil debería indicarle que no es lo correcto. – katrielalex
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. –