【问题标题】:How can a function take **kwargs as its arguments and distribute them among different classes?函数如何将 **kwargs 作为其参数并将它们分配给不同的类?
【发布时间】:2017-05-19 11:35:57
【问题描述】:

假设我有两个班级......

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 作为参数并确定哪些参数可以转到哪个类的某种方式?

【问题讨论】:

标签: python initialization arguments keyword-argument


【解决方案1】:

为避免所有重复输入,您可以创建一个实用函数,该函数使用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'

【讨论】:

  • 我更喜欢这个,我很想把它变成一个返回有效参数但没有解决它的函数。
  • 好。我试图做一些相当通用的东西,以便在必要时可以轻松修改并在需要时重复使用。但是,请注意,它确实要求所有关键字参数都是唯一的。如果我的回答解决了你的问题,请考虑采纳。
【解决方案2】:
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
>>> 

检查 Parameter 是否是关键字参数可能是多余的。

【讨论】:

    【解决方案3】:

    如果您在类的__init__ 方法中添加*arg**kwargs,您可以实现您所期望的行为。就像下面的例子:

    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.cobjA.d,它将返回 charliedelta,就像在 make_objs 参数中传递的一样。

    【讨论】:

    • 抱歉,我应该指定我已经在使用 *args 作为 classA。 classA 的 init(self, *args, **kwargs),函数采用(*args, **kwargs)
    • 您能用该信息再次更新您的问题吗?谢谢。您也可以更具体地了解您期望实现的目标。我可能不清楚你的问题。
    猜你喜欢
    • 1970-01-01
    • 2021-07-03
    • 2023-01-16
    • 2020-01-26
    • 2013-07-11
    • 2013-01-30
    • 1970-01-01
    • 2022-12-31
    • 1970-01-01
    相关资源
    最近更新 更多