2016-03-19 9 views
27

ich nach dem besten Weg suchen, eine Funktion mit einem Wörterbuch kombinieren , die mehr Elemente als die Eingänge der Funktion enthältWie kann man eine Funktion mit einem Wörterbuch aufrufen, das mehr Elemente enthält als die Funktion Parameter hat?

Grund ** kwarg auspacken versagt in diesem Fall:

def foo(a,b): 
    return a + b 

d = {'a':1, 
    'b':2, 
    'c':3} 

foo(**d) 
--> TypeError: foo() got an unexpected keyword argument 'c' 

Nach einigen Recherchen ich kam mit dem folgenden Ansatz auf:

import inspect 

# utilities 
def get_input_names(function): 
    '''get arguments names from function''' 
    return inspect.getargspec(function)[0] 

def filter_dict(dict_,keys): 
    return {k:dict_[k] for k in keys} 

def combine(function,dict_): 
    '''combine a function with a dictionary that may contain more items than the function's inputs ''' 
    filtered_dict = filter_dict(dict_,get_input_names(function)) 
    return function(**filtered_dict) 

# examples 
def foo(a,b): 
    return a + b 

d = {'a':1, 
    'b':2, 
    'c':3} 

print combine(foo,d) 
--> 3 

Meine Frage ist: ist dies ein guter Weg, um mit diesem Problem umzugehen, oder gibt es eine bessere Praxis oder gibt es eine mechan Ismus in der Sprache, die ich vielleicht vermisse?

Antwort

23

Wie wäre es ein decorator das wäre Filter erlaubt Stichwort Argumente nur machen:

import inspect 


def get_input_names(function): 
    '''get arguments names from function''' 
    return inspect.getargspec(function)[0] 


def filter_dict(dict_,keys): 
    return {k:dict_[k] for k in keys} 


def filter_kwargs(func): 
    def func_wrapper(**kwargs): 
     return func(**filter_dict(kwargs, get_input_names(func))) 
    return func_wrapper 


@filter_kwargs 
def foo(a,b): 
    return a + b 


d = {'a':1, 
    'b':2, 
    'c':3} 

print(foo(**d)) 

Was ist mit diesem Dekorateur schön ist, dass es allgemeine und wiederverwendbar ist. Und Sie müssten die Art, wie Sie anrufen und Ihre Zielfunktionen verwenden, nicht ändern.

11

Ihr Problem mit der Art und Weise liegen Sie Ihre Funktion definiert ist, sollte es so definiert werden -

def foo(**kwargs): 

Und dann innerhalb der Funktion können Sie über die Anzahl der Argumente iterieren an die Funktion wie so geschickt -

if kwargs is not None: 
     for key, value in kwargs.iteritems(): 
       do something 

Sie können weitere Informationen über die Verwendung finden ** kwargs in diesem Beitrag - http://pythontips.com/2013/08/04/args-and-kwargs-in-python-explained/

+1

Dank. Ich hätte vielleicht erwähnen sollen, dass ich ein Framework für ein Programm entwickle, in dem andere Leute (manchmal relative Novizen in Python) die Funktionen erstellen. Daher würde ich bevorzugen, dass sie sich nicht mit den Kwargen befassen müssen, sondern eher mit grundlegenden Funktionseingaben: foo (a, b); Ich möchte diese ** kwargs filtering complexity in Utility-Funktionen verbergen. –

3

ich würde somethin tun g wie folgt aus:

def combine(function, dictionary): 
    return function(**{key:value for key, value in dictionary.items() 
        if key in inspect.getargspec(function)[0]} 
    ) 

Verwendung:

>>> def this(a, b, c=5): 
...  print(a, b, c) 
... 
>>> combine(this, {'a': 4, 'b': 6, 'c': 6, 'd': 8}) 
4 6 6 
>>> combine(this, {'a': 6, 'b': 5, 'd': 8}) 
6 5 5 
8

Sie auch eine decorator function, um herauszufiltern, jene Schlüsselwort Argumente die nicht erlaubt sind in Sie Funktion verwenden können. Von Ihnen die signature Funktion neu in 3.3 benutzen, um Ihre Funktion Signature

from inspect import signature 
from functools import wraps 


def decorator(func): 
    @wraps(func) 
    def wrapper(*args, **kwargs): 
     sig = signature(func) 
     result = func(*[kwargs[param] for param in sig.parameters]) 
     return result 
    return wrapper 

Von Python 3.0 zurückkehren können Sie getargspec verwenden, die 3.0

seit Version veraltet ist
import inspect 


def decorator(func): 
    @wraps(func) 
    def wrapper(*args, **kwargs): 
     argspec = inspect.getargspec(func).args 
     result = func(*[kwargs[param] for param in argspec]) 
      return result 
    return wrapper 

Um Ihre dekorieren anzuwenden, um eine existierende Funktion müssen Sie Ihre Funktion als Argument an Ihre Dekorateur übergeben:

Demo:

>>> def foo(a, b): 
...  return a + b 
... 
>>> foo = decorator(foo) 
>>> d = {'a': 1, 'b': 2, 'c': 3} 
>>> foo(**d) 
3 

Um Ihren Dekorateur auf eine neu Funktion einfach @

>>> @decorator 
... def foo(a, b): 
...  return a + b 
... 
>>> foo(**d) 
3 

Sie können auch definieren Sie Ihre Funktion mit beliebigen Keywords Argumente **kwargs verwenden anzuwenden.

>>> def foo(**kwargs): 
...  if 'a' in kwargs and 'b' in kwargs: 
...   return kwargs['a'] + kwargs['b'] 
... 
>>> d = {'a': 1, 'b': 2, 'c': 3} 
>>> foo(**d) 
3 
14

Alle diese Antworten sind falsch.

Es ist nicht möglich zu tun, was Sie fordern, weil die Funktion könnte wie folgt erklärt werden:

def foo(**kwargs): 
    a = kwargs.pop('a') 
    b = kwargs.pop('b') 
    if kwargs: 
     raise TypeError('Unexpected arguments: %r' % kwargs) 

Nun, warum auf der Erde würde, dass jemand schreiben?

Weil sie nicht alle Argumente vor der Zeit kennen. Hier ist ein realistischer Fall:

def __init__(self, **kwargs): 
    for name in self.known_arguments(): 
     value = kwargs.pop(name, default) 
     self.do_something(name, value) 
    super().__init__(**kwargs) # The superclass does not take any arguments 

Und here ist einige der realen Welt Code, der dies tatsächlich der Fall ist.

Sie könnten fragen, warum wir die letzte Zeile benötigen. Warum Argumente an eine Superklasse übergeben, die keine braucht? Cooperative multiple inheritance. Wenn meine Klasse ein Argument erhält, das sie nicht erkennt, sollte sie dieses Argument nicht verschlingen, noch sollte es einen Fehler enthalten. Es sollte das Argument die Kette hinauf führen, so dass eine andere Klasse, die ich vielleicht nicht kenne, damit umgehen kann. Und wenn niemand es handhabt, wird object.__init__() eine entsprechende Fehlermeldung bereitstellen. Leider werden die anderen Antworten das nicht elegant behandeln. Sie werden sehen **kwargs und entweder keine Argumente übergeben oder alle von ihnen übergeben, die beide falsch sind.

Die untere Zeile: Es gibt keine allgemeine Möglichkeit zu ermitteln, ob ein Funktionsaufruf zulässig ist, ohne diesen Funktionsaufruf tatsächlich auszuführen. inspect ist eine grobe Näherung und fällt angesichts variadischer Funktionen völlig auseinander. Variadisch bedeutet nicht "passier was du willst"; es bedeutet "die Regeln sind zu komplex, um sie in einer Signatur auszudrücken". Obwohl es in vielen Fällen möglich ist, das zu tun, was Sie versuchen, wird es immer Situationen geben, in denen es keine richtige Antwort gibt.

+1

Danke Kevin! Ich akzeptierte die Antwort von Alecxe am Ende, weil sie für meine Situation am direktesten hilfreich war, aber ich fand auch eine sehr interessante Lektüre, und ich stimme zu, dass es wichtig ist, zu berücksichtigen, wo der Filter-Kwargs-Ansatz nicht funktioniert Arbeit. –

2

Dies wird modifiziert noch die ursprüngliche Funktion, aber Sie können einen kwargs bitbucket am Ende der Argumentliste erstellen:

def foo(a, b, **kwargs): 
    return a + b 

foo(**{ 
    'a': 5, 
    'b': 8, 
    'c': '' 
}) # 13