我经常发现自己覆盖了父类的方法,并且永远无法决定是否应该明确列出给定的参数,或者只是使用毯子*args, **kwargs
构造。一个版本比另一个好吗?有最好的做法吗?我缺少什么(dis-)优势?
class Parent(object):
def save(self, commit=True):
# ...
class Explicit(Parent):
def save(self, commit=True):
super(Explicit, self).save(commit=commit)
# more logic
class Blanket(Parent):
def save(self, *args, **kwargs):
super(Blanket, self).save(*args, **kwargs)
# more logic
明确变体的好处
一揽子变体的好处
答案 0 :(得分:31)
Liskov替代原则
通常,您不希望方法签名在派生类型中有所不同。如果要交换派生类型的使用,这可能会导致问题。这通常被称为Liskov Substitution Principle。
明确签名的好处
与此同时,我认为所有方法都没有*args
,**kwargs
的签名是正确的。明确签名:
可变长度参数和耦合
不要将可变长度参数误认为是良好的耦合实践。父类和派生类之间应该有一定的内聚力,否则它们就不会彼此相关。相关代码导致耦合反映内聚水平是正常的。
使用可变长度参数的位置
使用可变长度参数不应该是您的第一选择。当你有充分的理由时应该使用它:
你做错了吗?
如果您发现自己经常创建带有许多参数的方法或带有不同签名的派生方法,那么您在组织代码方面可能会遇到更大的问题。
答案 1 :(得分:13)
我的选择是:
class Child(Parent):
def save(self, commit=True, **kwargs):
super(Child, self).save(commit, **kwargs)
# more logic
它避免了从*args
和**kwargs
访问提交参数,并且如果Parent:save
的签名发生更改(例如添加新的默认参数),它会保证安全。
更新:在这种情况下,如果将新的位置参数添加到父级,则使用* args会导致麻烦。我只保留**kwargs
并仅使用默认值管理新参数。它可以避免传播错误。
答案 2 :(得分:4)
如果您确定Child会保留签名,那么明确的方法肯定是可取的,但是当Child改变签名时我个人更喜欢使用这两种方法:
class Parent(object):
def do_stuff(self, a, b):
# some logic
class Child(Parent):
def do_stuff(self, c, *args, **kwargs):
super(Child, self).do_stuff(*args, **kwargs)
# some logic with c
这样,签名中的更改在Child中非常易读,而原始签名在Parent中非常易读。
在我看来,当你有多重继承时,这也是更好的方法,因为当你没有args和kwargs时,调用super
几次是非常恶心的。
对于它的价值,这也是很多Python库和框架中的首选方式(Django,Tornado,Requests,Markdown,仅举几例)。虽然不应该根据这些事情做出选择,但我只是暗示这种做法非常普遍。
答案 3 :(得分:3)
不是一个真正的答案,而是更多的旁注:如果你真的,真的想确保父类的默认值传播到子类,你可以做类似的事情:
class Parent(object):
default_save_commit=True
def save(self, commit=default_save_commit):
# ...
class Derived(Parent):
def save(self, commit=Parent.default_save_commit):
super(Derived, self).save(commit=commit)
但是我必须承认这看起来很丑陋,如果我觉得我真的需要它,我只会使用它。
答案 4 :(得分:2)
我更喜欢显式参数,因为自动完成允许您在进行函数调用时查看函数的方法签名。
答案 5 :(得分:0)
除了其他答案:
具有可变参数可以使父级与子级“分离”,但是会在创建的对象与父级之间建立耦合,我认为这更糟,因为现在您创建了“长距离”对(更难发现,维护起来比较困难,因为您可能会在应用程序中创建多个对象)
如果您要进行去耦,请查看composition over inheritance