在 Python 3 类的 __init__ 函数中,如何使用 **kwargs?

18

我正在使用Python 3编写一个类,希望能够从用户那里接收各种关键字参数,并将这些值存储以供稍后在类方法中使用。下面是示例代码:

class MathematicalModel:
    def __init__(self, var1, var2, var3, **kwargs):
        self.var1 = var1
        self.var2 = var2
        self.var3 = var3
        self.var4 = kwarg1
        self.var5 = kwarg2
        self.var6 = kwarg6

    def calculation1(self):
        x = self.var1 + self.var2 + self.var3
        return x

    def calculation2(self):
        y = self.var1 * self.var2 * var3
        return y

class MathematicalModelExtended(MathematicalModel):
    def __init__(self, var1, var2, var3, **kwargs):
        super.__init__(self, var1, var2, var3, **kwargs)

    def calculation1(self):
        '''Overrides calculation1 method from parent DoThis'''
        x = (self.var1 + self.var2 + self.var3) / self.kwarg1
        return x

    def calculation2(self):
        '''Overrides calculation2 method from parent DoThis'''
        y = (self.var1 * self.var2 * self.var3) / (self.kwarg1 + self.kwarg2)
        return y

a = MathematicalModel(1, 2, 3)
b = MathematicalModelExtended(1, 2, 3, var4 = 4, var5 = 5, var6 = 6)

然而,我不确定这如何运作,有几个原因: a)如果用户没有为b或c甚至是a提供参数怎么办?那么代码将会抛出一个错误,所以我不确定在这种情况下如何初始化这些属性。 b)当我不知道用户之前传递的关键字参数是什么时,我该如何访问与关键字相关联的值?

我计划在数学公式中使用这些变量。一些变量(不包括在kwargs中)将被用于每个公式,而其他变量(即kwargs中的变量)则只用于其他公式。我计划将MathematicalModel封装在另一个类中,像这样:MathematicalModelExtended(MathematicalModel)

谢谢!


1
你能解释一下最后一部分的意思吗?“当我不知道用户之前传递了哪个关键字参数时,我如何访问与关键字相关联的值?”此外,abc代表什么?同样地,key1key2key3又代表什么?你是在寻找*args还是**kwargs - Cohan
请添加一个代码示例,清楚地展示您正在尝试做什么。它不一定要正常工作,但是很难猜测像DoThisNowDoThis这样的类应该做什么以及它们如何被调用或使用。拥有通用函数和类具有很大的价值,但在这种情况下,缺乏清晰度使得很难为您的问题提供一个好的答案。 - Cohan
我添加了更多的代码,以更好地展示我想要做什么(尽管像你所说的那样,它绝不正确)。 - psychedelic-snail
嗨@MisterMiyagi,我意识到了,我只是列出了潜在的kwargs,以展示我想要如何使用这些参数。谢谢你的评论。 - psychedelic-snail
显示剩余3条评论
1个回答

48

通用kwargs想法

当您使用self.var = value加载变量时,它会将其添加到内部字典中,可以使用self.__dict__访问它。

class Foo1:

    def __init__(self, **kwargs):
        self.a = kwargs['a']
        self.b = kwargs['b']

foo1 = Foo1(a=1, b=2)
print(foo1.a)  # 1
print(foo1.b)  # 2
print(foo1.__dict__)  # {'a': 1, 'b': 2}

如果您想允许任意参数,您可以利用 kwargs 也是一个字典的事实,并使用 update() 函数。

class Foo2:

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

foo2 = Foo2(some_random_variable=1, whatever_the_user_supplies=2)
print(foo2.some_random_variable)  # 1
print(foo2.whatever_the_user_supplies)  # 2
print(foo2.__dict__)  # {'some_random_variable': 1, 'whatever_the_user_supplies': 2}
这将防止您在尝试存储不存在的值时出现错误。
class Foo3:

    def __init__(self, **kwargs):
        self.a = kwargs['a']
        self.b = kwargs['b']

foo3 = Foo3(a=1)  # KeyError: 'b'

如果您想确保类中的变量ab被设置,而不管用户提供了什么,您可以创建类属性或使用kwargs.get()

class Foo4:

    def __init__(self, **kwargs):
        self.a = kwargs.get('a', None)
        self.b = kwargs.get('b', None)

foo4 = Foo4(a=1)
print(foo4.a)  # 1
print(foo4.b)  # None
print(foo4.__dict__)  # {'a': 1, 'b': None}

然而,使用这种方法,变量属于类而不是实例。这就是为什么你会看到foo5.b返回一个字符串,但它并不在foo5.__dict__中。

class Foo5:

    a = 'Initial Value for A'
    b = 'Initial Value for B'

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

foo5 = Foo5(a=1)
print(foo5.a)  # 1
print(foo5.b)  # Initial Value for B
print(foo5.__dict__)  # {'a': 1}

如果您允许用户自由指定任何kwargs,则可以在函数中遍历__dict__

class Foo6:

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def do_something(self):
        for k, v in self.__dict__.items():
            print(f"{k} -> {v}")

foo6 = Foo6(some_random_variable=1, whatever_the_user_supplies=2)
foo6.do_something()
# some_random_variable -> 1
# whatever_the_user_supplies -> 2

然而,根据您在课堂上的其他活动,您可能会拥有比用户提供的更多的实例属性。因此,最好让用户提供一个字典作为参数。

class Foo7:

    def __init__(self, user_vars):
        self.user_vars  = user_vars

    def do_something(self):
        for k, v in self.user_vars.items():
            print(f"{k} -> {v}")

foo7 = Foo7({'some_random_variable': 1, 'whatever_the_user_supplies': 2})
foo7.do_something()
# some_random_variable -> 1
# whatever_the_user_supplies -> 2

处理你的代码

使用更新后的代码,我建议使用self.__dict__.update(kwargs)方法。然后当你依赖的变量没有遇到时,你可以抛出一个错误(option1方法),或者你可以为该变量设置一个默认值以防未定义(option2方法)。

class MathematicalModel:
    def __init__(self, var1, var2, var3, **kwargs):
        self.var1 = var1
        self.var2 = var2
        self.var3 = var3
        self.__dict__.update(kwargs)  # Store all the extra variables


class MathematicalModelExtended(MathematicalModel):
    def __init__(self, var1, var2, var3, **kwargs):
        super().__init__(var1, var2, var3, **kwargs)

    def option1(self):
        # Trap error if you need var4 to be specified
        if 'var4' not in self.__dict__:
            raise ValueError("Please provide value for var4")

        x = (self.var1 + self.var2 + self.var3) / self.var4
        return x

    def option2(self):
        # Use .get() to provide a default value when the user does not provide it.
        _var4 = self.__dict__.get('var4', 1)

        x = (self.var1 + self.var2 + self.var3) / self.var4
        return x


a = MathematicalModel(1, 2, 3)
b = MathematicalModelExtended(1, 2, 3, var4=4, var5=5, var6=6)
print(b.option1())  # 1.5
print(b.option2())  # 1.5

如果 MathematicalModel 永远只使用 var1var2var3,那么传递 kwargs 是没有意义的。

class MathematicalModel:
    def __init__(self, var1, var2, var3, **kwargs):
        self.var1 = var1
        self.var2 = var2
        self.var3 = var3

class MathematicalModelExtended(MathematicalModel):
    def __init__(self, var1, var2, var3, **kwargs):
        super().__init__(var1, var2, var3)
        self.__dict__.update(kwargs)

    def option1(self):
        # Trap error if you need var4 to be specified
        if 'var4' not in self.__dict__:
            raise ValueError("Please provide value for var4")

        x = (self.var1 + self.var2 + self.var3) / self.var4
        return x

    def option2(self):
        # Use .get() to provide a default value when the user does not provide it.
        _var4 = self.__dict__.get('var4', 1)

        x = (self.var1 + self.var2 + self.var3) / self.var4
        return x


a = MathematicalModel(1, 2, 3)
b = MathematicalModelExtended(1, 2, 3, var4=4, var5=5, var6=6)
print(b.option1())  # 1.5
print(b.option2())  # 1.5

谢谢您的回复,但我不确定我理解了。在您回复的第一部分中,__dict__是在将**kwargs作为参数传递时创建的内部字典吗?其次,我不明白,定义类值然后稍后覆盖它们只是为了使它们具有特定名称吗? - psychedelic-snail
我认为我对第二种方法感到困惑的原因是,为什么要实例化可能会或可能不会使用的属性?有没有更优雅的方法来做到这一点? - psychedelic-snail
1
我的困惑在于为什么您要允许用户定义任意kwargs,并且您计划如何使用它们。如果您只是将它们引导到类或函数中调用,我可以理解,但我感觉这不是您正在做的事情。因此,如果您需要确保某些变量可访问而不管用户输入什么,最好在类或实例中提前定义它们。我会使用“Foo7”方法并将所有用户输入都包含在一个变量中。然后,您可以准确地查看他们提供了什么并将其与关键功能隔离开来。 - Cohan
2
我可以理解如果你只是将它们引导到另一个在你的类中调用的类或函数,但我感觉你并不是这样做的。实际上,这正是我计划要做的。用户将提供作为数学公式中某些变量的特定数字(但并非所有情况下所有公式都需要这些变量)。因此,这就是为什么我想将它们定义为实例变量的原因。 - psychedelic-snail
这取决于MathematicalModel是什么,但如果它不会使用额外的参数,那么传递额外的参数就没有意义。 - Cohan
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接