避免在子类中指定所有参数
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 tuple,a
没有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 位置?