Получение аргументов ключевого слова, фактически переданных методу Python
Я мечтаю о методе Python с явными ключевыми словами:
def func(a=None, b=None, c=None):
for arg, val in magic_arg_dict.items(): # Where do I get the magic?
print '%s: %s' % (arg, val)
Я хочу получить словарь только тех аргументов, которые вызывающий фактически передал в метод, как и **kwargs
, но я не хочу, чтобы вызывающий мог передать любые старые случайные аргументы, в отличие от **kwargs
.
>>> func(b=2)
b: 2
>>> func(a=3, c=5)
a: 3
c: 5
Итак: есть ли такое заклинание? В моем случае я могу сравнить каждый аргумент с его значением по умолчанию, чтобы найти те, которые отличаются друг от друга, но это своего рода неэлегантный и утомительный, когда у вас есть девять аргументов. Для бонусных очков укажите заклинание, которое может сказать мне, даже когда вызывающий абонент переходит в аргумент ключевого слова, которому присвоено его значение по умолчанию:
>>> func(a=None)
a: None
Трикси!
Изменить: Подпись функции (лексическая) должна оставаться неповрежденной. Это часть общедоступного API, и основная ценность явных ключевых слов args заключается в их документальном значении. Просто чтобы все было интересно.:)
Ответы
Ответ 1
Меня вдохновляла доброжелательность декораторов с теорией потери теории, и после того, как он немного поработал с этим, придумал следующее:
def actual_kwargs():
"""
Decorator that provides the wrapped function with an attribute 'actual_kwargs'
containing just those keyword arguments actually passed in to the function.
"""
def decorator(function):
def inner(*args, **kwargs):
inner.actual_kwargs = kwargs
return function(*args, **kwargs)
return inner
return decorator
if __name__ == "__main__":
@actual_kwargs()
def func(msg, a=None, b=False, c='', d=0):
print msg
for arg, val in sorted(func.actual_kwargs.iteritems()):
print ' %s: %s' % (arg, val)
func("I'm only passing a", a='a')
func("Here b and c", b=True, c='c')
func("All defaults", a=None, b=False, c='', d=0)
func("Nothin'")
try:
func("Invalid kwarg", e="bogon")
except TypeError, err:
print 'Invalid kwarg\n %s' % err
Что печатает это:
I'm only passing a
a: a
Here b and c
b: True
c: c
All defaults
a: None
b: False
c:
d: 0
Nothin'
Invalid kwarg
func() got an unexpected keyword argument 'e'
Я доволен этим. Более гибкий подход заключается в том, чтобы передать имя атрибута, который вы хотите использовать для декоратора, вместо жесткого кодирования его на "actual_kwargs", но это самый простой подход, который иллюстрирует решение.
Ммм, Питон вкусный.
Ответ 2
Вот самый простой и простой способ:
def func(a=None, b=None, c=None):
args = locals().copy()
print args
func(2, "egg")
Это даст результат: {'a': 2, 'c': None, 'b': 'egg'}
.
Причиной args
должна быть копия словаря locals
, что словари являются изменяемыми, поэтому, если вы создали какие-либо локальные переменные в этой функции, args
будет содержать все локальные переменные и их значения, а не только аргументы.
Дополнительная документация по встроенной функции locals
здесь.
Ответ 3
Одна возможность:
def f(**kw):
acceptable_names = set('a', 'b', 'c')
if not (set(kw) <= acceptable_names):
raise WhateverYouWantException(whatever)
...proceed...
IOW, очень легко проверить, что переданные имена находятся в допустимом наборе и в противном случае повышают то, что вы хотите, чтобы Python повышал (TypeError, я думаю;-). Довольно легко превратиться в декоратор, кстати.
Другая возможность:
_sentinel = object():
def f(a=_sentinel, b=_sentinel, c=_sentinel):
...proceed with checks `is _sentinel`...
создав уникальный объект _sentinel
, вы устраните риск того, что вызывающий может случайно пройти None
(или другие неповторимые значения по умолчанию, которые может пройти вызывающий). Это все object()
полезно для, btw: чрезвычайно легкий, уникальный дозор, который нельзя случайно спутать с каким-либо другим объектом (когда вы проверяете с помощью оператора is
).
Любое решение предпочтительнее для немного разных проблем.
Ответ 4
Как насчет использования декоратора для проверки входящих кваргов?
def validate_kwargs(*keys):
def entangle(f):
def inner(*args, **kwargs):
for key in kwargs:
if not key in keys:
raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
return f(*args, **kwargs)
return inner
return entangle
###
@validate_kwargs('a', 'b', 'c')
def func(**kwargs):
for arg,val in kwargs.items():
print arg, "->", val
func(b=2)
print '----'
func(a=3, c=5)
print '----'
func(d='not gonna work')
Дает этот вывод:
b -> 2
----
a -> 3
c -> 5
----
Traceback (most recent call last):
File "kwargs.py", line 20, in <module>
func(d='not gonna work')
File "kwargs.py", line 6, in inner
raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
ValueError: Received bad kwarg: 'd', expected: ('a', 'b', 'c')
Ответ 5
Это проще всего выполнить с помощью одного экземпляра сторожевого объекта:
# Top of module, does not need to be exposed in __all__
missing = {}
# Function prototype
def myFunc(a = missing, b = missing, c = missing):
if a is not missing:
# User specified argument a
if b is missing:
# User did not specify argument b
Самое приятное в этом подходе состоит в том, что, поскольку мы используем оператор "is", вызывающий может передать пустой dict в качестве значения аргумента, и мы все равно поймем, что они не хотели его передавать, Мы также избегаем неприятных декораторов и сохраняем наш код немного чище.
Ответ 6
Вероятно, есть лучшие способы сделать это, но вот мое взятие:
def CompareArgs(argdict, **kwargs):
if not set(argdict.keys()) <= set(kwargs.keys()):
# not <= may seem weird, but comparing sets sometimes gives weird results.
# set1 <= set2 means that all items in set 1 are present in set 2
raise ValueError("invalid args")
def foo(**kwargs):
# we declare foo "standard" args to be a, b, c
CompareArgs(kwargs, a=None, b=None, c=None)
print "Inside foo"
if __name__ == "__main__":
foo(a=1)
foo(a=1, b=3)
foo(a=1, b=3, c=5)
foo(c=10)
foo(bar=6)
и его вывод:
Inside foo
Inside foo
Inside foo
Inside foo
Traceback (most recent call last):
File "a.py", line 18, in
foo(bar=6)
File "a.py", line 9, in foo
CompareArgs(kwargs, a=None, b=None, c=None)
File "a.py", line 5, in CompareArgs
raise ValueError("invalid args")
ValueError: invalid args
Возможно, это можно было бы преобразовать в декоратор, но мои декораторы нуждаются в работе. Оставленный как упражнение для читателя: P
Ответ 7
Возможно, возникает ошибка, если они передают любые * args?
def func(*args, **kwargs):
if args:
raise TypeError("no positional args allowed")
arg1 = kwargs.pop("arg1", "default")
if kwargs:
raise TypeError("unknown args " + str(kwargs.keys()))
Было бы просто включить его в список списков varnames или общую функцию синтаксического анализа. Было бы не слишком сложно сделать это в декораторе (python 3.1) тоже:
def OnlyKwargs(func):
allowed = func.__code__.co_varnames
def wrap(*args, **kwargs):
assert not args
# or whatever logic you need wrt required args
assert sorted(allowed) == sorted(kwargs)
return func(**kwargs)
Примечание. Я не уверен, насколько хорошо это будет работать уже с уже завернутыми функциями или функциями, у которых уже есть *args
или **kwargs
.
Ответ 8
Магия - это не ответ:
def funky(a=None, b=None, c=None):
for name, value in [('a', a), ('b', b), ('c', c)]:
print name, value