我正在对一个对象进行子类化,以便覆盖我想要添加一些功能的方法。我不想完全替换它或添加一个不同命名的方法,但只是通过向方法添加一个可选参数来保持与超类方法兼容。
是否可以使用*args
和**kwargs
来传递超类的所有参数,并仍然添加带有默认值的可选参数?
我直观地想出了以下内容,但它不起作用:
class A(object):
def foo(self, arg1, arg2, argopt1="bar"):
print arg1, arg2, argopt1
class B(A):
def foo(self, *args, argopt2="foo", **kwargs):
print argopt2
A.foo(self, *args, **kwargs)
b = B()
b.foo("a", "b", argopt2="foo")
当我明确添加超类方法的所有参数时,我可以使它工作:
class B(A):
def foo(self, arg1, arg2, argopt1="foo", argopt2="bar"):
print argopt2
A.foo(self, arg1, arg2, argopt1=argopt1)
执行此操作的正确方法是,我是否必须知道并明确说明所有重写的方法参数?
答案 0 :(得分:11)
class A(object):
def foo(self, arg1, arg2, argopt1="bar"):
print arg1, arg2, argopt1
class B(A):
def foo(self, *args, **kwargs):
argopt2 = kwargs.get('argopt2', default_for_argopt2)
# remove the extra arg so the base class doesn't complain.
del kwargs['argopt2']
print argopt2
A.foo(self, *args, **kwargs)
b = B()
b.foo("a", "b", argopt2="foo")
答案 1 :(得分:6)
做到这一点的正确方法是什么? 必须知道并明确说明所有 被重写的方法参数?
如果你想要涵盖所有情况(而不是仅仅依靠调用者总是以你的方式做事,例如,总是只用名字传递的额外参数,从不按位置调用你)你必须编写代码(或动态发现)关于你所覆盖的方法的签名的许多知识 - 这并不令人惊讶:继承是一种强大的耦合形式,而重写方法是耦合呈现的一种方式。
你可以通过inspect.getargspec动态发现超类的方法参数,以确保你正确地调用它......但是如果 2 <<}这个内省技术可能会变得棘手/ strong>类试图做同样的事情(一旦你知道你的超类的方法接受*a
和/或**kw
你就可以做更多的事情而不是向上传递所有相关的论点并希望用手指越过,上游方法链最终会在调用一个不太宽容的版本之前进行适当的大扫除。)
当您设计一个动态应用于具有各种签名的callables的包装器时,这样的价格可能是值得的(特别是因为在装饰器设置中,您可以安排支付高昂的内省成本一次你正在装饰的功能,而不是每次调用生成的包装器时)。在像你这样的情况下,你似乎不太可能成为一个有价值的技术,在那里你最好知道你的子类是什么(子类化是强耦合:盲目地做它绝对不可取!)所以你不妨拼出来明确的论点。
是的,如果超类的代码发生了巨大的变化(例如,通过改变方法签名),你也必须修改子类 - 这是继承价格的一部分。整体价格非常高,新的Go编程语言完全没有它 - 强制你将Gang of 4的优秀建议应用于prefer composition over inheritance。在Python中,完全禁止继承只是不切实际,但是清醒地和适度地使用它(并且在你做的时候接受你将在耦合方面支付的价格)仍然是可取的。
答案 2 :(得分:1)
当子类化和重写方法时,必须始终决定使用super()
是否是个好主意,而this page对此有好处。
我不说应该避免使用super()
,例如文章作者可能是:我说super()
有一些非常重要的先决条件,必须是如果您不希望super()
回来咬你,那就紧随其后。