Дайте функции по умолчанию аргументы из словаря в Python

Представьте, что у меня есть диктат:

d = {'a': 3, 'b':4}

Я хочу создать функцию f, которая делает то же самое, что и эта функция:

def f(x, a=d['a'], b=d['b']):
  print(x, a, b)

(не обязательно печатать, но делать что-то с переменной и вызывать напрямую от их имени).

Но я бы хотел создать эту функцию прямо из текста, то есть, я хотел бы иметь что-то похожее на

def f(x, **d=d):
  print(x, a, b)

и это ведет себя как ранее определенная функция. Идея состоит в том, что у меня есть большой словарь, который содержит значения по умолчанию для аргументов моей функции, и я не хотел бы делать

def f(a= d['a'], b = d['b'] ...)

Я не знаю, возможно ли это вообще в Python. Любое понимание приветствуется!

Изменение: идея заключается в том, чтобы иметь возможность позвонить f(5, a=3). Edit2: Вопрос не в том, чтобы передать аргументы, сохраненные в dict, в функцию, а в том, чтобы определить функцию, имена аргументов и значения по умолчанию которой хранятся в dict.

Ответы

Ответ 1

Python спроектирован так, что локальные переменные любой функции могут быть определены однозначно, если взглянуть на исходный код функции. Итак, предложенный вами синтаксис

def f(x, **d=d):
  print(x, a, b)

не является стартером, потому что нет ничего, что указывает, являются ли a и b локальными по отношению к f или нет; это зависит от значения словаря во время выполнения, значение которого может меняться при каждом запуске.

Если вы можете смириться с явным перечислением имен всех ваших параметров, вы можете автоматически устанавливать их значения по умолчанию во время выполнения; это уже хорошо освещено в других ответах. В любом случае, перечисление имен параметров, вероятно, является хорошей документацией.

Если вы действительно хотите синтезировать весь список параметров во время выполнения из содержимого d, вам придется построить строковое представление определения функции и передать его в exec. Так работает, например, collections.namedtuple.

Переменные в областях модуля и класса ищутся динамически, так что это технически допустимо:

def f(x, **kwargs):
    class C:
        vars().update(kwargs)  # don't do this, please
        print(x, a, b)

Но, пожалуйста, не делайте этого, кроме как в записи IOPCC.

Ответ 2

Вы не можете достичь этого при определении функции, потому что Python статически определяет область действия функции. Хотя можно написать декоратор для добавления в аргументы по умолчанию ключевых слов.

from functools import wraps

def kwargs_decorator(dict_kwargs):
    def wrapper(f):
        @wraps(f)
        def inner_wrapper(*args, **kwargs):
            new_kwargs = {**dict_kwargs, **kwargs}
            return f(*args, **new_kwargs)
        return inner_wrapper
    return wrapper

Usage

Usage
@kwargs_decorator({'bar': 1})
def foo(**kwargs):
    print(kwargs['bar'])

foo() # prints 1

Или, если вы знаете имена переменных, но не их значения по умолчанию...

@kwargs_decorator({'bar': 1})
def foo(bar):
    print(bar)

foo() # prints 1

Caveat

Вышеприведенное можно использовать, например, для динамического создания нескольких функций с разными аргументами по умолчанию. Хотя, если параметры, которые вы хотите передать, одинаковы для каждой функции, было бы проще и более идиоматично просто передать dict параметров.

Ответ 3

попробуйте это:

# Store the default values in a dictionary
>>> defaults = {
...     'a': 1,
...     'b': 2,
... }
>>> def f(x, **kwa):
        # Each time the function is called, merge the default values and the provided arguments
        # For python >= 3.5:
        args = {**defaults, **kwa}
        # For python < 3.5:
        # Each time the function is called, copy the default values
        args = defaults.copy()
        # Merge the provided arguments into the copied default values
        args.update(kwa)
...     print(args)
... 
>>> f(1, f=2)
{'a': 1, 'b': 2, 'f': 2}
>>> f(1, f=2, b=8)
{'a': 1, 'b': 8, 'f': 2}
>>> f(5, a=3)
{'a': 3, 'b': 2}

Спасибо Ольвину Роту за указание, как правильно объединить словари в python> = 3.5

Ответ 4

Этот вопрос очень интересен, и, похоже, разные люди по-своему догадываются, чего на самом деле хочет этот вопрос.

У меня тоже есть свои. Вот мой код, который можно выразить:

# python3 only
from collections import defaultdict

# only set once when function definition is executed
def kwdefault_decorator(default_dict):
    def wrapper(f):
        f.__kwdefaults__ = {}
        f_code = f.__code__
        po_arg_count = f_code.co_argcount
        keys = f_code.co_varnames[po_arg_count : po_arg_count + f_code.co_kwonlyargcount]
        for k in keys:
            f.__kwdefaults__[k] = default_dict[k]
        return f

    return wrapper

default_dict = defaultdict(lambda: "default_value")
default_dict["a"] = "a"
default_dict["m"] = "m"

@kwdefault_decorator(default_dict)
def foo(x, *, a, b):
    foo_local = "foo"
    print(x, a, b, foo_local)

@kwdefault_decorator(default_dict)
def bar(x, *, m, n):
    bar_local = "bar"
    print(x, m, n, bar_local)

foo(1)
bar(1)
# only kw_arg permitted
foo(1, a=100, b=100)
bar(1, m=100, n=100)

выход:

1 a default_value
1 m default_value
1 100 100
1 100 100

Ответ 5

Как насчет ** трюка с Кваргсом?

def function(arg0, **kwargs):
    print("arg is", arg0, "a is", kwargs["a"], "b is", kwargs["b"])

d = {"a":1, "b":2}
function(0., **d)

результат:

arg is 0.0 a is 1 b is 2

Ответ 6

Вы можете распаковать значения dict:

from collections import OrderedDict

def f(x, a, b):
    print(x, a, b)

d = OrderedDict({'a': 3, 'b':4})
f(10, *d.values())

UPD.

Да, можно реализовать эту безумную идею модификации локальной области видимости, создав декоратор, который будет возвращать класс с переопределением __call__() и сохранять ваши значения по умолчанию в области видимости класса, НО ЭТО МАССИВНОЕ ПЕРЕКРЫТИЕ.

Ваша проблема в том, что вы пытаетесь скрыть проблемы вашей архитектуры за этими хитростями. Если вы сохраняете свои значения по умолчанию в dict, то доступ к ним осуществляется по ключу. Если вы хотите использовать ключевые слова - определите класс.

Постскриптум Я до сих пор не понимаю, почему этот вопрос собирает столько голосов.

Ответ 7

Конечно... надеюсь, это поможет

def funcc(x, **kwargs):
    locals().update(kwargs)
    print(x, a, b, c, d)

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

funcc(x, **kwargs)