Как функция может принимать **kwargs в качестве аргументов и распределять их по разным классам?

Предположим, у меня есть два класса...

class A:

    def __init__(self, *args, arg1="default1", arg2="default2"):
        # Initialise class A


class B:

    def __init__(self, arg3="default3", arg4="default4"):
        # Initialise class B

У каждого класса есть свои аргументы ключевого слова, а у одного есть позиционные аргументы.

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

def make_objs(*args, arg1="default1", arg2="default2", arg3="default3", arg4="default4"):
    objA = ClassA(*args, arg1=arg1, arg2=arg2)
    objB = ClassB(arg3=arg3, arg4=arg4)

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

В идеале я бы сделал что-то вроде этого:

def make_objs(*args, **kwargs):
    objA = ClassA(*args, **kwargs)
    objB = ClassB(**kwargs)

Где каждый класс будет принимать все аргументы ключевого слова и извлекать только те, которые относятся к нему. Это, конечно, не то, что приведенный выше код на самом деле сделал бы, он выдаст исключение, потому что ClassA не ожидает аргумента с именем arg3.

Есть какой-либо способ сделать это? Какой-то способ заставить функцию принимать **kwargs в качестве аргумента и определять, какие аргументы могут относиться к какому классу?


person Sam Ireland    schedule 05.01.2017    source источник
comment
Как насчет аргументов constructor в class A добавить *arg и **kwarg после arg1 и arg2? То же самое касается class B.   -  person kiran.koduru    schedule 05.01.2017
comment
Вопрос: Когда вы собираетесь распаковывать *args в classA? Не могли бы вы дать полное определение вашего constructor для classA?   -  person kiran.koduru    schedule 05.01.2017
comment
На самом деле функция такова: /2.0/быстрые сюжеты/   -  person Sam Ireland    schedule 05.01.2017
comment
Класс A — это github.com/samirelanduk/quickplots/blob/2.0. /быстрые сюжеты/   -  person Sam Ireland    schedule 05.01.2017
comment
Класс B: github.com/samirelanduk/quickplots/blob/2.0 /быстрые сюжеты/   -  person Sam Ireland    schedule 05.01.2017
comment
Вы хотите, чтобы сигнатуры вызовов класса оставались прежними? ... со значениями по умолчанию?   -  person wwii    schedule 05.01.2017
comment
Взгляните на Разделение **kwargs для разных функций и Можете ли вы перечислить аргументы ключевого слова, которые получает функция Python? - Возможно, вы сможете найти решение, основанное на одном из ответов на эти вопросы.   -  person wwii    schedule 05.01.2017


Ответы (3)


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

Вот работающий пример применения его к вашему коду. Вспомогательная функция называется get_kwarg_names:

from inspect import signature, Parameter

class ClassA:
    def __init__(self, *args, arg1="default1", arg2="default2"):
        print('ClassA.__init__(): '
              '*args={}, arg1={!r}, arg2={!r}'.format(args, arg1, arg2))

class ClassB:
    def __init__(self, arg3="default3", arg4="default4"):
        print('ClassB.__init__(): arg3={!r}, arg4={!r}'.format(arg3, arg4))

def get_kwarg_names(function):
    """ Return a list of keyword argument names function accepts. """
    sig = signature(function)
    keywords = []
    for param in sig.parameters.values():
        if(param.kind == Parameter.KEYWORD_ONLY or
            (param.kind == Parameter.POSITIONAL_OR_KEYWORD and
                param.default != Parameter.empty)):
            keywords.append(param.name)
    return keywords

# Sample usage of utility function above.
def make_objs(*args, arg1="default1", arg2="default2",
                     arg3="default3", arg4="default4"):

    local_namespace = locals()
    classA_kwargs = {keyword: local_namespace[keyword]
                        for keyword in get_kwarg_names(ClassA.__init__)}
    objA = ClassA(*args, **classA_kwargs)

    classB_kwargs = {keyword: local_namespace[keyword]
                        for keyword in get_kwarg_names(ClassB.__init__)}
    objB = ClassB(**classB_kwargs)

make_objs(1, 2, arg1="val1", arg2="val2", arg4="val4")

Выход:

ClassA.__init__(): *args=(1, 2), arg1='val1', arg2='val2'
ClassB.__init__(): arg3='default3', arg4='val4'
person martineau    schedule 05.01.2017
comment
Мне это нравится больше, у меня был соблазн сделать это функцией, которая возвращала бы допустимые параметры, но я не дошел до этого. - person wwii; 06.01.2017
comment
Хорошо. Я попытался сделать что-то довольно универсальное, чтобы его можно было легко изменить при необходимости и повторно использовать при желании. Однако обратите внимание, что требуется, чтобы аргументы ключевого слова для всех были уникальными. Если мой ответ решит вашу проблему, рассмотрите возможность его принятия. - person martineau; 06.01.2017

Если вы добавите *arg и **kwargs в метод __init__ своих классов, вы сможете добиться ожидаемого поведения. Как в примере ниже:

class A(object):

    def __init__(self, a, b, *arg, **kwargs):
        self.a = a
        self.b = b

class B(object):

    def __init__(self, c, d, *arg, **kwargs):
        self.c = c
        self.d = d

def make_objs(**kwargs):
    objA = A(**kwargs)
    objB = B(**kwargs)

make_objs(a='apple', b='ball', c='charlie', d='delta')

Но предостережение здесь заключается в том, что если вы напечатаете objA.c и objA.d, будут возвращены charlie и delta, переданные в параметрах make_objs.

person kiran.koduru    schedule 05.01.2017
comment
Извините, мне нужно было указать, что я уже использую *args для classA. init класса A равен (self, *args, **kwargs), а функция принимает (*args, **kwargs). - person Sam Ireland; 05.01.2017
comment
Можете ли вы снова обновить свой вопрос с этой информацией? Спасибо. Также вы можете более конкретно указать, чего вы ожидали достичь. Я могу быть не ясен в вашем вопросе. - person kiran.koduru; 05.01.2017

class A:
    def __init__(self, *args, a1 = 'd1', a2 = 'd2'):
        self.a1 = a1
        self.a2 = a2
class B:
    def __init__(self, a3 = 'd3', a4 = 'd4'):
        self.a3 = a3
        self.a4 = a4

Используйте inpect.signature. чтобы получить позывные, отфильтруйте kwargs перед созданием объектов.

from inspect import signature
from inspect import Parameter

sig_a = signature(A)
sig_b = signature(B)

def f(*args, **kwargs):
    d1 = {}
    d2 = {}
    for k, v in kwargs.items():
        try:
            if sig_a.parameters[k].kind in (Parameter.KEYWORD_ONLY,
                                            Parameter.POSITIONAL_OR_KEYWORD,
                                            Parameter.VAR_KEYWORD):
                 d1[k] = v
        except KeyError:
            pass
        try:
            if sig_b.parameters[k].kind in (Parameter.KEYWORD_ONLY,
                                            Parameter.POSITIONAL_OR_KEYWORD,
                                            Parameter.VAR_KEYWORD):
                d2[k] = v
        except KeyError:
            pass
    return (A(args, **d1), B(**d2))

d = {'a1':1, 'a2':2, 'a3':3, 'a4':4}   
x, y = f(2, **d)

>>> x.a1
1
>>> x.a2
2
>>> y.a3
3
>>> y.a4
4
>>> 

Проверки, чтобы увидеть, является ли параметр параметром ключевого слова, могут быть излишними.

person wwii    schedule 05.01.2017