2010-07-30 9 views
5

Ich möchte in der Lage sein, die __init__ Methode einer Klasse zu fragen, was es Parameter sind. Der einfache Ansatz ist der folgende:Suche nach den Parametern einer Funktion in Python

Allerdings wird das nicht funktionieren, wenn die Klasse Dekoratoren hat. Es gibt die Parameterliste für die Funktion, die vom Decorator zurückgegeben wird. Ich möchte auf die ursprüngliche __init__ Methode zurückgreifen und diese ursprünglichen Parameter erhalten. Im Fall eines Dekorateur, ist die Dekorateur Funktion geht in der Schließung der Funktion durch den Dekorateur zurück gefunden werden:

cls.__init__.__func__.__closure__[0] 

Allerdings ist es komplizierter, wenn es andere Dinge in der Schließung ist, der Dekorateure kann von Zeit zu Zeit tun:

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 von der ursprünglichen Funktion muss ich entscheiden, ob ich auf die Parameter von Dekorateur oder die Parameter werden soll. Die vom Decorator zurückgegebene Funktion könnte *args und **kwargs für ihre Parameter haben. Was ist, wenn es mehrere Dekorateure gibt und ich entscheiden muss, welche mir wichtig ist?

Also, was ist der beste Weg, die Parameter einer Funktion zu finden, selbst wenn die Funktion dekoriert sein sollte? Was ist der beste Weg, um eine Kette von Dekorateuren zurück in die dekorierte Funktion zu bringen?

Update:

Hier ist effektiv, wie ich dieses Recht jetzt tue (Namen geändert wurden, 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 dieser Stelle Basis Berichten Werte für uninteressant Die vier contants und die dict-Version der Instanzen sind leer. Wenn Sie jedoch eine Unterklasse Sie eine der vier außer Kraft setzen können, oder Sie können andere Parameter der __init__ umfassen:

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

Warum zum Teufel würden Sie das wollen? – delnan

+4

Die Tatsache, dass dies so schwierig ist, sollte Ihnen zeigen, dass es nicht das Richtige ist. – katrielalex

+0

Ich möchte in der Lage sein, Objekte einer Klasse als Dicts zu verwenden und explizite Steuerelemente darauf haben, welche Schlüssel durch '__getitem__' und' __iter__' exponiert werden. Die Parameter von "__init__" machen große Standard-Keys, also ziehe ich diese programmatisch. Ich versuche nur, Ecken zu adressieren, etwa wenn Deskriptoren involviert werden. –

Antwort

3

dieses Dekorateur Bedenken Sie:

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) 

Darin nimmt die rickroll Dekorateur die Bar Methode, verwirft sie, ersetzt sie durch eine neue Funktion, die ihre anders benannten (und möglicherweise nummerierten!) Parameter ignoriert und stattdessen eine Linie von einem klassischen Lied zurückgibt.

Es gibt keine weiteren Verweise auf die ursprüngliche Funktion, und der Garbage Collector kann jederzeit kommen und es entfernen.

In solch einem Fall kann ich nicht sehen, wie Sie die Parameternamen p1 und p2 finden konnten. Nach meinem Verständnis hat selbst der Python-Interpreter selbst keine Ahnung, wie sie früher genannt wurden.

+0

LOL Ich habe dich nur dazu gebracht, diesen Rickroll-Dekorateur zu haben. Jetzt lesen Sie Ihre Antwort ... –

+0

Das ist ein großartiger Punkt. Ich hatte definitiv keinen Dekorateur erwogen, der eine völlig andere, nicht verwandte Funktion zurückgibt. Sie haben recht mit einem Mangel an Referenzen. Es macht Sinn, aber gleichzeitig scheint es nicht intuitiv, dass die in der Klasse definierte Funktion verschwinden würde. Du hast mir etwas zum Nachdenken gegeben. Vielen Dank! –

+0

Ich habe dieses Thema in http://stackoverflow.com/questions/3481872/decorated-for-python-decorators genauer verfolgt –

Verwandte Themen