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}
Pourquoi diable voudriez-vous cela? – delnan
Le fait que cela soit si difficile devrait vous indiquer que ce n'est pas la bonne chose à faire. – katrielalex
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. –