在覆盖方法中添加关键字参数并使用**kwarg

17 投票
4 回答
10262 浏览
提问于 2025-04-15 15:54

我正在创建一个对象的子类,目的是为了重写一个方法,给它添加一些新功能。我不想完全替换这个方法,也不想用不同的名字来定义一个新方法,而是希望通过添加一个可选参数,让这个方法依然和父类的方法兼容。 我想知道是否可以使用 *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)

那么,正确的做法是什么呢?我是否必须知道并明确列出所有被重写方法的参数?

4 个回答

2

在子类化和重写方法时,我们总是需要考虑使用 super() 是否合适。这里有一篇不错的文章可以参考:这篇文章

我并不是说一定要避免使用 super(),就像文章的作者可能会说的那样。我想表达的是,使用 super() 有一些非常重要的前提条件,如果不遵循这些条件,可能会导致一些麻烦。

7

这样做的正确方法是什么?我是否需要知道并明确说明所有被重写方法的参数?

如果你想覆盖所有情况(而不是仅仅依赖调用者总是按照你的方式来做,比如总是用名字传递额外的参数,而不是按位置传递),那么你确实需要编写(或动态发现)很多关于你正在重写的方法的参数信息。这并不奇怪:继承是一种强耦合的形式,而重写方法就是这种耦合的一种表现。

你可以通过inspect.getargspec动态发现父类的方法参数,以确保你正确调用它……但是如果两个类试图做完全相同的事情,这种自省技术可能会变得棘手(当你知道父类的方法接受*a和/或**kw时,你能做的就是把所有相关的参数传递上去,并希望上游的方法链在调用一个不那么宽容的版本之前能做好清理工作)。

当你设计一个包装器,目的是动态应用于具有多种签名的可调用对象时,这样的代价可能是值得的(尤其是在装饰器的情况下,你可以安排在装饰的每个函数上只支付一次昂贵的自省成本,而不是每次调用结果包装器时都支付)。在你的情况下,这种技术似乎不太值得,因为你最好知道自己在子类化什么(子类化是强耦合:盲目地这样做绝对不明智!),所以你不妨明确列出参数。

是的,如果父类的代码发生了重大变化(例如,改变了方法签名),你也必须修改子类——这就是继承的代价之一。整体代价相当高,以至于新的Go编程语言完全没有继承——迫使你遵循Gang of 4的优秀建议,更倾向于组合而不是继承。在Python中完全不使用继承是不切实际的,但在使用时保持清醒和适度(并接受你在耦合方面付出的代价)仍然是明智的选择。

14
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")

当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。

撰写回答