2009-05-06 17 views
12

Ich schreibe einen Dekorator, der vor dem Aufruf der Funktion, die es dekorieren ist, andere Funktionen aufrufen muss. Die dekorierte Funktion kann Positionsargumente haben, aber die Funktionen, die der Dekorator aufrufen wird, können nur Schlüsselwortargumente akzeptieren. Hat jemand eine praktische Möglichkeit, Positionsargumente in Schlüsselwortargumente umzuwandeln?Python Konvertieren Args zu Kwargs

Ich weiß, dass ich eine Liste der Variablennamen der dekorierten Funktion bekommen:

>>> def a(one, two=2): 
... pass 

>>> a.func_code.co_varnames 
('one', 'two') 

Aber ich kann nicht herausfinden, wie zu sagen, was in ihrer Position übergeben wurde und was als Schlüsselwort.

Mein Dekorateur sieht wie folgt aus:

class mydec(object): 
    def __init__(self, f, *args, **kwargs): 
     self.f = f 

    def __call__(self, *args, **kwargs): 
     hozer(**kwargs) 
     self.f(*args, **kwargs) 

Gibt es eine Möglichkeit andere als nur den Vergleich kwargs und co_varnames, etwas hinzuzufügen dort zu kwargs nicht, und das Beste zu hoffen?

+1

Warum brauchen Sie wissen, was args waren positionell? –

+1

Weil ich sie in Kwarts konvertieren muss, um die Hozer-Funktion aufzurufen. Diese Funktion akzeptiert nur Kwargs, muss jedoch alle ursprünglich aufgerufenen Argumente kennen. Je nachdem, ob Leute die dekorierte Funktion mit positionellen oder benannten Argumenten aufrufen, erhält die Hozer-Funktion möglicherweise nicht alle Daten, die sie benötigt. – jkoelker

Antwort

4

Hinweis - co_varnames enthält lokale Variablen sowie Schlüsselwörter. Dies ist wahrscheinlich egal, da zip die kürzere Sequenz abschneidet, aber zu fehlerhaften Fehlermeldungen führen kann, wenn Sie die falsche Anzahl von Argumenten übergeben.

Sie können dies mit func_code.co_varnames[:func_code.co_argcount] vermeiden, aber besser ist es, das inspect Modul zu verwenden. dh:

import inspect 
argnames, varargs, kwargs, defaults = inspect.getargspec(func) 

Sie können auch den Fall behandeln wollen, wo die Funktion **kwargs oder *args definiert (selbst wenn es nur eine Ausnahme zu erheben, wenn sie mit dem Dekorateur verwendet). Wenn diese gesetzt sind, geben das zweite und dritte Ergebnis von getargspec ihren Variablennamen zurück, andernfalls werden sie None sein.

16

Alle arg positionierten Argumente werden an * args übergeben. Und jedes Argument, das als Schlüsselwort übergeben wird, wird an ** kwargs weitergegeben. Wenn Sie Positions args Werte und Namen haben, dann können Sie tun:

kwargs.update(dict(zip(myfunc.func_code.co_varnames, args))) 

sie alle in Keyword-args zu konvertieren.

+1

In der Tat, 'kwars.update (zip (myfunc.func_code.co_varnames, args))' ist genug. 'dict.update' behandelt auch 2D-Iterables. – obskyr

5

Nun, das kann übertrieben sein. Ich habe es für das Paket dectools (auf PyPi) geschrieben, damit Sie dort Updates erhalten können. Es gibt das Wörterbuch unter Berücksichtigung der Argumente positional, keyword und default zurück. Es gibt eine Testsuite im Paket (test_dict_as_called.py):

def _dict_as_called(function, args, kwargs): 
""" return a dict of all the args and kwargs as the keywords they would 
be received in a real function call. It does not call function. 
""" 

names, args_name, kwargs_name, defaults = inspect.getargspec(function) 

# assign basic args 
params = {} 
if args_name: 
    basic_arg_count = len(names) 
    params.update(zip(names[:], args)) # zip stops at shorter sequence 
    params[args_name] = args[basic_arg_count:] 
else: 
    params.update(zip(names, args))  

# assign kwargs given 
if kwargs_name: 
    params[kwargs_name] = {} 
    for kw, value in kwargs.iteritems(): 
     if kw in names: 
      params[kw] = value 
     else: 
      params[kwargs_name][kw] = value 
else: 
    params.update(kwargs) 

# assign defaults 
if defaults: 
    for pos, value in enumerate(defaults): 
     if names[-len(defaults) + pos] not in params: 
      params[names[-len(defaults) + pos]] = value 

# check we did it correctly. Each param and only params are set 
assert set(params.iterkeys()) == (set(names)|set([args_name])|set([kwargs_name]) 
           )-set([None]) 

return params 
+0

Ich habe gesehen, dass dies in Dutzende von Open-Source-Projekten kopiert und eingefügt wurde. Dies sollte zu einer Funktion bewegt werden, die Leute leichter anrufen können! –

8

Wenn Sie mit Python> = 2.7 inspect.getcallargs tut dies für Sie aus dem Kasten heraus. Sie würden einfach die dekorierte Funktion als erstes Argument übergeben und dann den Rest der Argumente genau so, wie Sie es nennen wollen. Beispiel:

>>> def f(p1, p2, k1=None, k2=None, **kwargs): 
...  pass 
>>> from inspect import getcallargs 

Ich plane tun f('p1', 'p2', 'p3', k2='k2', extra='kx1') (das k1 beachten Sie bestanden positionell als p3 wird), so ...

>>> call_args = getcallargs(f, 'p1', 'p2', 'p3', k2='k2', extra='kx1') 
>>> call_args 
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'kwargs': {'extra': 'kx1'}} 

Wenn Sie die Funktion eingerichtet wissen nicht **kwargs verwenden , dann wird dieser Schlüssel nicht im Diktat erscheinen, und Sie sind fertig (und ich nehme an, dass es keine *args gibt, da dies die Anforderung brechen würde, dass alles einen Namen hat).Wenn Sie **kwargs zu tun haben, wie ich in diesem Beispiel haben, und wollen, dass sie mit dem Rest der genannten Argumente enthalten, dauert es eine weitere Zeile:

>>> call_args.update(call_args.pop('kwargs')) 
>>> call_args 
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'extra': 'kx1'} 
+0

Dies ist der richtige Weg (wenn Sie Python 2.7 oder höher haben, was praktisch jeder tut). –

Verwandte Themen