Чи можете ви перерахувати аргументи ключових слів, які отримує функція?


104

У мене є дікт, який мені потрібно передати ключ / значення як аргументи ключових слів. Наприклад ..

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

Це прекрасно працює, але якщо в диктаті d_args є значення, які не приймаються exampleфункцією, воно, очевидно, відмирає. Скажімо, якщо функція прикладу визначена якdef example(kw2):

Це проблема, оскільки я не контролюю ні генерацію d_args, ні exampleфункцію. Обидва вони походять із зовнішніх модулів і exampleприймають лише деякі ключові слова-аргументи з диктату ..

В ідеалі я просто зробив би

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

Я, мабуть, просто відфільтрую дикт із списку дійсних ключових слів-аргументів, але мені було цікаво: чи існує спосіб програмно перелічити аргументи ключових слів, які приймає конкретна функція?

Відповіді:


150

Трохи приємніше, ніж безпосередньо перевіряти об'єкт коду та опрацьовувати змінні - використовувати модуль перевірки.

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

Якщо ви хочете дізнатись, чи є її дзвінок з певним набором аргументів, вам потрібні аргументи без уже вказаного за замовчуванням. Їх можна отримати:

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.

Тоді функцією розповісти, що вам не вистачає у вашому конкретному диктаті, є:

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

Аналогічно, щоб перевірити наявність недійсних аргументів, використовуйте:

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

Отже, повний тест, якщо він може телефонувати:

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

(Це добре лише в тому, що стосується розбору аргументів python. Будь-які перевірки часу виконання недійсних значень у kwargs явно не можуть бути виявлені.)


Приємно! Я не знав цієї функції!
DzinX

1
Враховуючи, що метод, що використовує об'єкти коду, більш-менш ідентичний, чи є користь від того, що довелося імпортувати ще один модуль ...?
jmetz

@jmets - безумовно - практично завжди краще використовувати модуль бібліотеки, ніж прокручувати свій власний. Крім того, атрибути об'єкта коду є більш внутрішніми, і вони готові для зміни (наприклад, зауважте, що це переміщено до коду в pyhon3). Використовуючи модуль як майбутній інтерфейс, ви захищаєте вас трохи більше, якщо хтось із цих внутрішніх справ колись зміниться. Це також зробить речі, які ви, можливо, не думали робити, як, наприклад, помилка відповідного типу для функцій, які ви не можете перевірити (наприклад, функції C).
Брайан

13
inspect.getargspec(f)застаріло з Python 3.0; сучасний метод є inspect.signature(f).
Геррі

FYI, якщо ви хочете підтримувати Cython та Python, цей метод не працює на функції Cython'd. З co_varnamesіншого боку, варіант працює і в обох.
партовиробляння

32

Це надрукує назви всіх прохідних аргументів, ключових та не ключових слів:

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

Це тому, що спочатку co_varnamesзавжди є параметри (далі - локальні змінні, як yу прикладі вище).

Отже, тепер у вас може бути функція:

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)

Що ви потім могли використовувати так:

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

EDIT : Невелике доповнення: якщо вам дійсно потрібні лише аргументи ключових слів функції, ви можете скористатися func_defaultsатрибутом для їх вилучення:

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)

Тепер ви можете викликати свою функцію відомими аргами, але вилученими кваргами, наприклад:

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

Чи не Це передбачає , що funcвикористання не *argsчи **kwargsмагія в його підпису.


що робити, якщо я хочу роздрукувати лише "ключові слова" аргументи "ключі"?
Цзя

7

У Python 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={})

7

Для рішення Python 3 ви можете використовувати inspect.signatureта фільтрувати відповідно до типу параметрів, про які ви хочете знати.

Беручи зразкову функцію з позиційними або ключовими словами, лише ключовими словами, параметрами позиційного та ключового слова var:

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

Ви можете створити для нього об’єкт підпису:

from inspect import signature
sig =  signature(spam)

а потім відфільтруйте, щоб зрозуміти потрібні деталі:

>>> # 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']

і, аналогічно, для var позицій з використанням p.VAR_POSITIONALта ключового слова var з VAR_KEYWORD.

Крім того, ви можете додати пункт до параметра if, щоб перевірити, чи існує значення за замовчуванням, перевіривши, чи p.defaultдорівнює p.empty.


3

Розширення відповіді 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.