Czy możesz podać argumenty słów kluczowych, które otrzymuje funkcja?

104

Mam dyktando, które muszę przekazać klucz / wartości jako argumenty słów kluczowych .. Na przykład ..

d_args = {'kw1': 'value1', 'kw2': 'value2'}
example(**d_args)

Działa to dobrze, ale jeśli w dictwie d_args znajdują się wartości, które nie są akceptowane przez examplefunkcję, to oczywiście umiera. Powiedzmy, jeśli przykładowa funkcja jest zdefiniowana jakodef example(kw2):

Jest to problem, ponieważ nie kontroluję ani generowania funkcji d_args, ani examplefunkcji. Oba pochodzą z modułów zewnętrznych i exampleakceptują tylko niektóre argumenty słów kluczowych z dyktowania.

Idealnie bym to zrobił

parsed_kwargs = feedparser.parse(the_url)
valid_kwargs = get_valid_kwargs(parsed_kwargs, valid_for = PyRSS2Gen.RSS2)
PyRSS2Gen.RSS2(**valid_kwargs)

Prawdopodobnie po prostu przefiltruję dict, z listy prawidłowych argumentów-słów kluczowych, ale zastanawiałem się: czy istnieje sposób na programowe wyświetlenie argumentów słów kluczowych, które przyjmuje określona funkcja?

dbr
źródło

Odpowiedzi:

150

Nieco przyjemniejsze niż bezpośrednie sprawdzanie obiektu kodu i obliczanie zmiennych jest użycie modułu inspect.

>>> import inspect
>>> def func(a,b,c=42, *args, **kwargs): pass
>>> inspect.getargspec(func)
(['a', 'b', 'c'], 'args', 'kwargs', (42,))

Jeśli chcesz wiedzieć, czy można go wywołać z określonym zestawem argumentów, potrzebujesz argumentów bez wcześniej określonej wartości domyślnej. Można je uzyskać:

def getRequiredArgs(func):
    args, varargs, varkw, defaults = inspect.getargspec(func)
    if defaults:
        args = args[:-len(defaults)]
    return args   # *args and **kwargs are not required, so ignore them.

Następnie funkcją, która mówi, czego brakuje w twoim konkretnym dyktacie, jest:

def missingArgs(func, argdict):
    return set(getRequiredArgs(func)).difference(argdict)

Podobnie, aby sprawdzić nieprawidłowe argumenty, użyj:

def invalidArgs(func, argdict):
    args, varargs, varkw, defaults = inspect.getargspec(func)
    if varkw: return set()  # All accepted
    return set(argdict) - set(args)

Tak więc pełny test, jeśli można go wywołać, to:

def isCallableWithArgs(func, argdict):
    return not missingArgs(func, argdict) and not invalidArgs(func, argdict)

(Jest to dobre tylko w przypadku analizowania argumentów w Pythonie. Oczywiście nie można wykryć żadnych testów w czasie wykonywania pod kątem nieprawidłowych wartości w kwargach).

Brian
źródło
Miły! Nie znałem tej funkcji!
DzinX
1
Biorąc pod uwagę, że metoda wykorzystująca obiekty kodu jest mniej więcej identyczna, czy istnieje korzyść z konieczności zaimportowania jeszcze jednego modułu ...?
jmetz
@jmets - zdecydowanie - praktycznie zawsze lepiej jest używać modułu biblioteki niż tworzyć własne. Ponadto atrybuty obiektu kodu są bardziej wewnętrzne i czekają na zmiany (np. Zauważ, że przeniesiono to do kodu w pyhon3). Korzystanie z modułu jako interfejsu zabezpiecza Cię nieco bardziej w przyszłości, na wypadek gdyby niektóre z tych elementów wewnętrznych uległy zmianie. Zrobi również rzeczy, o których być może nie pomyślałeś, na przykład wyrzuca błąd odpowiedniego typu do funkcji, których nie możesz sprawdzić (np. Funkcje C).
Brian
13
inspect.getargspec(f)jest przestarzałe od Pythona 3.0; nowoczesna metoda to inspect.signature(f).
gerrit
FYI, jeśli chcesz obsługiwać Cython i Python, ta metoda nie działa na funkcji Cython. Z co_varnamesdrugiej strony opcja działa w obu przypadkach.
część wszystkiego
32

Spowoduje to wyświetlenie nazw wszystkich dopuszczalnych argumentów, słów kluczowych i innych niż słowa kluczowe:

def func(one, two="value"):
    y = one, two
    return y
print func.func_code.co_varnames[:func.func_code.co_argcount]

Dzieje się tak, ponieważ najpierw co_varnamessą zawsze parametry (następne są zmienne lokalne, jak yw powyższym przykładzie).

Więc teraz możesz mieć funkcję:

def getValidArgs(func, argsDict):
    '''Return dictionary without invalid function arguments.'''
    validArgs = func.func_code.co_varnames[:func.func_code.co_argcount]
    return dict((key, value) for key, value in argsDict.iteritems() 
                if key in validArgs)

Którego następnie możesz użyć w ten sposób:

>>> func(**getValidArgs(func, args))

EDYCJA : Mały dodatek: jeśli naprawdę potrzebujesz tylko argumentów słów kluczowych funkcji, możesz użyć func_defaultsatrybutu, aby je wyodrębnić:

def getValidKwargs(func, argsDict):
    validArgs = func.func_code.co_varnames[:func.func_code.co_argcount]
    kwargsLen = len(func.func_defaults) # number of keyword arguments
    validKwargs = validArgs[-kwargsLen:] # because kwargs are last
    return dict((key, value) for key, value in argsDict.iteritems() 
                if key in validKwargs)

Możesz teraz wywołać swoją funkcję ze znanymi argumentami, ale wyodrębnionymi kwargami, np:

func(param1, param2, **getValidKwargs(func, kwargsDict))

Zakłada się, że funcnie używa *argslub **kwargsmagii w jej podpisania.

DzinX
źródło
co zrobić, jeśli chcę wydrukować tylko „klucze” argumentów „słów kluczowych”?
Jia
7

W Pythonie 3.0:

>>> import inspect
>>> import fileinput
>>> print(inspect.getfullargspec(fileinput.input))
FullArgSpec(args=['files', 'inplace', 'backup', 'bufsize', 'mode', 'openhook'],
varargs=None, varkw=None, defaults=(None, 0, '', 0, 'r', None), kwonlyargs=[], 
kwdefaults=None, annotations={})
jfs
źródło
7

W przypadku rozwiązania Python 3 możesz używać inspect.signaturei filtrować według rodzaju parametrów , o których chcesz wiedzieć.

Pobranie przykładowej funkcji z parametrami pozycyjnymi lub słowami kluczowymi, tylko słowami kluczowymi, var pozycyjnymi i var słowo kluczowe:

def spam(a, b=1, *args, c=2, **kwargs):
    print(a, b, args, c, kwargs)

Możesz stworzyć dla niego obiekt podpisu:

from inspect import signature
sig =  signature(spam)

a następnie przefiltruj listę, aby znaleźć potrzebne szczegóły:

>>> # positional or keyword
>>> [p.name for p in sig.parameters.values() if p.kind == p.POSITIONAL_OR_KEYWORD]
['a', 'b']
>>> # keyword only
>>> [p.name for p in sig.parameters.values() if p.kind == p.KEYWORD_ONLY]
['c']

i podobnie, dla pozycjonowania var przy użyciu p.VAR_POSITIONALsłowa kluczowego i var z VAR_KEYWORD.

Ponadto możesz dodać klauzulę do if, aby sprawdzić, czy istnieje wartość domyślna, sprawdzając, czy p.defaultjest równa p.empty.

Dimitris Fasarakis Hilliard
źródło
3

Rozszerzenie odpowiedzi DzinX:

argnames = example.func_code.co_varnames[:func.func_code.co_argcount]
args = dict((key, val) for key,val in d_args.iteritems() if key in argnames)
example(**args)
Claudiu
źródło