【问题标题】:Passing kwargs in a class inheritance chain在类继承链中传递 kwargs
【发布时间】:2019-03-11 21:08:07
【问题描述】:

我有以下设置:

class A:
  def __init__(self, **kwargs):
    # Some variables initialized
    for k, v in kwargs.items():
        setattr(self, k, v)

class B(A):
  def __init__(self, **kwargs):
    A.__init__(self, **kwargs)
    self._b = {}
    for k, v in kwargs.items():
        setattr(self, k, v)

    @property
    def b(self):
        return self._b

    @b.setter
    def b(self, value):
        self._b.update(value)

class C(B):
    def __init__(self, **kwargs):
    B.__init__(self, **kwargs)
    # Some variables initialized
    for k, v in kwargs.items():
        setattr(self, k, v)

当我现在创建 C 的新实例时,我收到以下错误:

AttributeError: 'C' object has no attribute '_b'

现在这很有意义,因为在调用 A.__init__(self, **kwargs) 时尚未初始化 B._b。我可以通过像这样重新排序 B 的初始化来解决这个问题,

class B(A):
  def __init__(self, **kwargs):
    self._b = {}
    A.__init__(self, **kwargs)
    for k, v in kwargs.items():
        setattr(self, k, v)

当我需要在初始化期间将kwargs 从子类传递给父类时,我想了解是否有推荐/最佳实践方法?在我看来,以下事情会起作用,

  1. 像上面那样重新排序初始化
  2. 在每个子类中分配kwargs,然后弹出它们并将剩余的kwargs 传递给父类初始化
  3. 更好的东西

希望为 3 提供一些方法。

【问题讨论】:

  • “当我现在创建一个新的 C 实例时,出现以下错误” 我没有收到错误。 minimal reproducible example?
  • @Aran-Fey 你是对的我很抱歉我没有包含我提供的错误来源的属性设置器。上面的 B 类也执行以下操作,@property def b(self): return self._b @b.setter def b(self, value): self._b.update(value)
  • 您在所有__init__ 方法中循环kwargs 的循环对我来说似乎是多余的。你为什么要在每节课上都这样做?要么只做一次,要么根本不做(让每个类接受它期望的任何命名参数,并且只使用kwargs作为父类的参数。请edit这个问题把关于b 属性进入主要问题。没有这些信息没有任何意义,因此不应将其留在评论中。
  • 我在@Blckknght 上面发表评论后确实更新了它。将在下面进一步回复您的回答

标签: python inheritance keyword-argument method-resolution-order


【解决方案1】:

您遇到的问题是这些循环:

for k, v in kwargs.items():
    setattr(self, k, v)

每个类都有一个,这意味着 每个 类中的一个都将 all 关键字参数设置为 self 上的属性。

当该循环在 A 中运行时,它会失败,因为 B 有一个需要初始化才能工作的属性。

正如您在问题中提到的,快速解决方法是确保 B 在运行 A.__init__ 之前设置其字典:

class B(A):
    def __init__(self, **kwargs):
        _b = {}                        # set this up first
        A.__init__(self, **kwargs)     # before calling the superclass
        for k, v in kwargs.items():
            setattr(self, k, v)

但可能有更好的方法可以让您避免冗余循环。我建议在每个类中明确命名您期望的关键字参数。这样b 只会被B 类看到,AC 都不会看到(kwargs 的一部分除外)。

class A:
    def __init__(self, *, a): # a is keyword-only arg, no kwargs accepted here
        self.a = a

class B:
    def __init__(self, *, b, **kwargs):
        super().__init__(**kwargs) # doesn't mess with b!
        self._b = {}
        self.b = b

    @property
    def b(self):
       ...

class C:
    def __init__(self, *, c, **kwargs):
        super().__init__(**kwargs)
        self.c = c

现在你可以调用C(a="foo", b={1: 2}, c="bar"),每个类只会关注它关心的属性。

【讨论】:

  • 感谢@Blckknght 的回答。循环的原因仅仅是因为类有很多在初始化时需要分配的属性。
  • 在我的 5 分钟用完后,在此处进一步编辑我的评论。因此,如果我想保持使用循环分配多个属性的好处,那么也许通过首先执行 _b = {} 来初始化 B 可能是我最好的选择。
  • 我应该补充一点,添加基于 kwargs 分配属性的循环使得在初始化中指定默认值并且如果传入仍然能够分配非默认值。另一个选项当然是在 __init__ 中指定所有参数及其默认值,但这不那么紧凑
  • 如果您真的想让所有参数都包含在kwargs 中,那么只需确保只有一个循环将它们分配给属性(可能在基类中)。这种方法的缺点是没有简单的方法来设置强制参数。如果a 没有有用的默认值,比如说,没有if 'a' in kwargs 的检查就不能强制执行,这比仅仅将其作为显式参数更丑陋。同样,也无法阻止传入未知参数(可能是由于拼写错误)。如果您致电C(A='foo'),您可能暂时不会意识到您将a 大写错误。
猜你喜欢
  • 2020-04-06
  • 2015-05-04
  • 2022-09-22
  • 1970-01-01
  • 2020-02-07
  • 2018-04-03
  • 2019-09-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多