避免在子类中指定所有参数

Posted

技术标签:

【中文标题】避免在子类中指定所有参数【英文标题】:Avoid specifying all arguments in a subclass 【发布时间】:2011-01-14 00:40:15 【问题描述】:

我有一堂课:

class A(object):
    def __init__(self,a,b,c,d,e,f,g,...........,x,y,z)
        #do some init stuff

我有一个子类需要一个额外的 arg(最后一个 W

class B(A):
    def __init__(self.a,b,c,d,e,f,g,...........,x,y,z,W)
        A.__init__(self,a,b,c,d,e,f,g,...........,x,y,z)
        self.__W=W

编写所有这些样板代码似乎很愚蠢,例如将所有 args 从 B 的 Ctor 传递到对 A 的 ctor 的内部调用,从那时起每次更改为 A 的 ctor必须应用到B的代码中的另外两个地方。

我猜 python 有一些习惯用法来处理我不知道的这种情况。你能指出我正确的方向吗?

我最好的预感是为 A 提供一种 Copy-Ctor,然后将 B 的代码更改为

class B(A):
     def __init__(self,instanceOfA,W):
         A.__copy_ctor__(self,instanceOfA)
         self.__W=W

这将满足我的需要,因为我总是在给定父类的实例时创建子类,尽管我不确定是否可能......

【问题讨论】:

如果你有那么多参数,我不会使用位置参数,而是将它们全部/大部分作为 kwargs 并使用 py3 '*' 语法 def __initi__(arg1,arg2,* kwarg1=..., ..., kwargx=..., w=...) 强制 kwargs(=不依赖命令)。使用 def __init__(self, arg1, arg2,*, **kwargs) 更短,但我不喜欢它,因为它会在 pycharm 等 IDE 中搞乱代码完成。除了一点点屏幕浪费之外,写出所有的 kwargs 有什么害处?无论如何,您都必须在文档字符串中记录它们... 【参考方案1】:

考虑到参数可以按名称或位置传递,我会编写代码:

class B(A):
    def __init__(self, *a, **k):
      if 'W' in k:
        w = k.pop('W')
      else:
        w = a.pop()
      A.__init__(self, *a, **k)
      self._W = w

【讨论】:

我喜欢这个想法,但我认为这个例子行不通。由于*a 导致a 到receive a tuplea 没有pop() 方法。 a 需要先转换为列表。另外,如果所有参数都按名称传递,a 是空的(所以你不能pop() 它)。 AttributeError: 'tuple' object has no attribute 'pop' a = list(a)a.pop() 之前【参考方案2】:

编辑:基于 Matt 的建议,并解决 gnibbler 的担忧,即位置参数方法;您可以检查以确保指定了额外的子类特定参数——类似于Alex's answer:

class B(A):
  def __init__(self, *args, **kwargs):
    try:
      self._w = kwargs.pop('w')
    except KeyError:
      pass
    super(B,self).__init__(*args, **kwargs)

>>> b = B(1,2,w=3)
>>> b.a
1
>>> b.b
2
>>> b._w
3

原答案: 与Matt's answer 相同的想法,改为使用super()

使用super()调用超类的__init__()方法,然后继续初始化子类:

class A(object):
  def __init__(self, a, b):
    self.a = a
    self.b = b

class B(A):
  def __init__(self, w, *args):
    super(B,self).__init__(*args)
    self.w = w

【讨论】:

此解决方案要求 w 必须作为第一个 arg 或按名称传递。这个问题将它作为最后一个参数 @gnu nibbler:感谢您的评论。我已经编辑了我的答案来解决这个问题;类似于 Alex 几个小时前发布的内容 :-)【参考方案3】:

在传递给__init__ 的部分或全部参数具有默认值的情况下,避免在子类中重复__init__ 方法签名会很有用。

在这些情况下,__init__ 可以将任何额外的参数传递给另一个方法,子类可以覆盖这些方法:

class A(object):
    def __init__(self, a=1, b=2, c=3, d=4, *args, **kwargs):
        self.a = a
        self.b = b
        # …
        self._init_extra(*args, **kwargs)

    def _init_extra(self):
        """
        Subclasses can override this method to support extra
        __init__ arguments.
        """

        pass


class B(A):
    def _init_extra(self, w):
        self.w = w

【讨论】:

【参考方案4】:

你想要这样的东西吗?

class A(object):
    def __init__(self, a, b, c, d, e, f, g):
        # do stuff
        print a, d, g

class B(A):
    def __init__(self, *args):
        args = list(args)
        self.__W = args.pop()
        A.__init__(self, *args)

【讨论】:

好吧,这适用于非常简化的版本。但是现在 B 可以使用任意数量的 args(限制较少)......难道没有更惯用的东西吗? B only 可以接受任意数量的参数;它调用 A,如果 A 得到错误的数字,它将引发 TypeError。如果您愿意,可以显式检查参数的数量。我可能会在生产代码中。 如果你发现错误的参数数量,无论如何你都会自己引发 TypeError。 是的,但是第一个错误是一个明显的错误(例如,pylint 会在您运行代码之前识别),但是您的解决方案调用的错误只能在运行时发现时间 PS 我认为该解决方案的另一个缺点是,现在 B 的 init 文档字符串必须类似于“这个 init 需要所有 A 的参数,加上 W 作为最后一个参数”,这比在 init 中明确写入所有参数更糟糕......好吧,也许你不能同时享受这两个世界的好处: )

以上是关于避免在子类中指定所有参数的主要内容,如果未能解决你的问题,请参考以下文章

为啥在定义为宏值的路径中指定的反斜杠会被删除?有没有办法避免这种情况?

在 Xcode 中,如何在我的项目设置中指定正确的库以避免链接错误?

在构建 Java GraphQL API 时,如何避免从数据库中过度获取(即仅获取查询中指定的字段)?

如何避免需要在 CXF 或 JAX-WS 生成的 Web 服务客户端中指定 WSDL 位置?

如何避免在CXF或JAX-WS生成的Web服务客户端中指定WSDL位置?

在 Ansible 中指定进程优先级