Finden der Parameter einer Funktion in Python
Ich möchte in der Lage sein, eine Klasse zu fragen,__init__
-Methode, was es Parameter sind. Der einfache Ansatz ist der folgende:
cls.__init__.__func__.__code__.co_varnames[:code.co_argcount]
Das geht aber nicht, wenn die Klasse Dekorateure hat. Es wird die Parameterliste für die vom Dekorateur zurückgegebene Funktion angezeigt. Ich möchte zum Original zurückkehren__init__
-Methode und erhalten Sie diese ursprünglichen Parameter. Im Fall eines Dekorateurs befindet sich die Dekoratorfunktion im Abschluß der vom Dekorator zurückgegebenen Funktion:
cls.__init__.__func__.__closure__[0]
Es ist jedoch komplizierter, wenn der Verschluss andere Dinge enthält, die die Dekorateure von Zeit zu Zeit tun können:
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>)
Und dann muss ich mich entscheiden, ob ich die Parameter vom Dekorateur oder die Parameter von der ursprünglichen Funktion haben will. Die vom Dekorateur zurückgegebene Funktion könnte @ hab*args
und**kwargs
für seine Parameter. Was ist, wenn es mehrere Dekorateure gibt und ich mich entscheiden muss, welchen ich mag?
Was ist der beste Weg, um die Parameter einer Funktion zu finden, auch wenn die Funktion dekoriert ist? Und was ist der beste Weg, um eine Reihe von Dekorateuren auf die dekorierte Funktion zurückzuführen?
Aktualisieren
Hier ist praktisch, wie ich das jetzt tue (die Namen wurden geändert, um die Identität des Angeklagten zu schützen):
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())
# {}
An diesem Punkt meldet Base uninteressante Werte für die vier Kon- tanten, und die diktierte Version von Instanzen ist leer. Wenn Sie jedoch eine Unterklasse bilden, können Sie eine der vier überschreiben oder dem @ andere Parameter hinzufüge__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}