2016-03-19 37 views
27

szukam najlepszego sposobu, aby połączyć funkcję ze słownikiem który zawiera więcej elementów niż wejść w funkcję zaJak wywołać funkcję ze słownikiem, który zawiera więcej elementów niż funkcja ma parametry?

podstawowy ** kwarg rozpakowanie nie w tym przypadku:

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

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

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

Po niektórych badań wymyśliłem następujące podejście:

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 

Moje pytanie brzmi: czy jest to dobry sposób radzenia sobie z tym problemem, czy jest jakiś lepszy praktyka czy istnieje mechan ism w języku, którego może mi brakować?

Odpowiedz

23

Jak o dokonywania decorator które tylkofiltry dozwolone argumenty słów kluczowych:

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)) 

Co jest ładne o tym dekoratora jest to, że jest nazwą rodzajową i wielokrotnego użytku. Nie trzeba zmieniać sposobu, w jaki dzwonisz i używasz swoich funkcji docelowych.

11

Twój problem leży w sposób zdefiniowany swoją funkcję, powinien być zdefiniowany tak -

def foo(**kwargs): 

A potem wewnątrz funkcji można iteracyjne nad liczbą argumentów przekazanych do funkcji jak tak -

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

można znaleźć więcej informacji na temat korzystania ** kwargs w tym poście - http://pythontips.com/2013/08/04/args-and-kwargs-in-python-explained/

+1

Dzięki. Powinienem wspomnieć być może, że buduję ramy dla programu, w którym inne osoby (czasami względni nowicjusze w pytonie) tworzą funkcje. Dlatego wolałbym, żeby nie musieli radzić sobie z ** kwargs, ale raczej za pomocą podstawowych funkcji wejściowych: foo (a, b); Chciałbym ukryć tę ** kwargs, filtrując złożoność funkcji użyteczności. –

3

zrobiłbym coś g tak:

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

Zastosowanie:

>>> 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

Można również użyć decorator function odfiltrować te argumentów słów kluczowych które nie są dozwolone w was działać. Od korzystania z signature funkcja nowego w 3.3, aby przywrócić funkcję 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 

Od Pythona 3.0 można użyć getargspec który Przestarzałe od wersji 3,0

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 

aby zastosować ozdobić się istniejąca funkcja musisz przekazać swoją funkcję jako argument do swojego dekoratora:

Demo:

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

Aby zastosować dekorator do nowo funkcji wystarczy użyć @

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

Można również zdefiniować swoją funkcję przy użyciu dowolnych słów kluczowych argumentów **kwargs.

>>> 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

Wszystkie te odpowiedzi są błędne.

Nie jest możliwe, aby robić to, co prosisz, ponieważ funkcja może zostać uznane za tak:

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

Teraz, dlaczego na ziemi ktoś miałby napisać, że?

Ponieważ nie znają wszystkich argumentów z wyprzedzeniem. Oto bardziej realistyczny przypadek:

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 

I here jest jakiś kod w świecie rzeczywistym, które rzeczywiście to robi.

Możesz zapytać, dlaczego potrzebujemy ostatniej linii. Po co przekazywać argumenty do superklasy, która nie bierze żadnej? Cooperative multiple inheritance. Jeśli moja klasa dostanie argument, którego nie rozpozna, nie powinna połknąć tego argumentu, ani nie powinna go pomijać. Powinien przekazać argument w łańcuchu, aby inna klasa, o której nie wiem, mogła sobie z tym poradzić. A jeśli nikt go nie obsłuży, wtedy object.__init__() dostarczy odpowiedni komunikat o błędzie. Niestety, pozostałe odpowiedzi nie będą tak wdzięczne. Zobaczą one **kwargs i albo nie przekazują żadnych argumentów, albo przekazują wszystkie, które są niepoprawne.

Dolna linia: Nie ma ogólnego sposobu na sprawdzenie, czy wywołanie funkcji jest legalne, bez wywoływania tej funkcji. inspect jest przybliżonym przybliżeniem i całkowicie rozpada się w obliczu funkcji variadycznych. Variadic nie oznacza "podaj, co chcesz"; oznacza to, że "reguły są zbyt złożone, by można je było wyrazić w podpisie". W rezultacie, chociaż w wielu przypadkach może być możliwe robienie tego, co chcesz zrobić, zawsze będą sytuacje, w których nie ma poprawnej odpowiedzi.

+1

Dzięki Kevin! W końcu przyjąłem odpowiedź alecxe, ponieważ była ona najbardziej bezpośrednio pomocna w mojej sytuacji, jednak uznałem, że jest to bardzo interesująca lektura, i zgadzam się, że ważne jest, aby zdać sobie sprawę z tego, że istnieją ważne scenariusze, aby rozważyć, gdzie podejście "filtra kwargów" nie będzie praca. –

2

to wciąż zmieniając pierwotną funkcję, ale można utworzyć bitbucket kwargs na końcu listy argumentów:

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

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