继承最佳实践:*args,**kwargs或显式指定参数

Mai*_*fel 44 python inheritance method-signature

我经常发现自己覆盖了父类的方法,并且永远无法决定是否应该明确地列出给定的参数,或者只是使用一个毯子*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
Run Code Online (Sandbox Code Playgroud)

显性变体的感知益处

  • 更明确(Python的禅)
  • 更容易掌握
  • 功能参数易于访问

毯子变体的感知好处

  • 更干
  • 父类很容易互换
  • 在不触及其他代码的情况下传播父方法中的默认值的更改

die*_*dha 34

利斯科夫替代原则

通常,您不希望方法签名在派生类型中有所不同.如果要交换派生类型的使用,这可能会导致问题.这通常被称为Liskov替代原则.

显式签名的好处

与此同时,我认为你的所有方法都没有签名的正确*args,**kwargs.明确签名:

  • 通过良好的参数名称帮助记录方法
  • 通过指定哪些args是必需的以及哪些具有默认值来帮助记录方法
  • 提供隐式验证(缺少必需的args抛出明显的异常)

可变长度参数和耦合

不要将变长参数误认为是良好的耦合实践.父类和派生类之间应该有一定的内聚力,否则它们就不会彼此相关.相关代码导致耦合反映内聚水平是正常的.

使用可变长度参数的位置

使用可变长度参数不应该是您的第一选择.当你有充分的理由时应该使用它:

  • 定义函数包装器(即装饰器).
  • 定义参数化多态函数.
  • 当您可以采用的参数确实是完全可变的(例如,通用的DB连接函数).数据库连接函数通常采用多种不同形式的连接字符串,包括单个arg形式和多arg形式.不同的数据库也有不同的选项集.
  • ...

你做错了什么吗?

如果您发现自己经常创建带有许多参数的方法或带有不同签名的派生方法,那么您在编写代码时可能会遇到更大的问题.


luc*_*luc 15

我的选择是:

class Child(Parent):

    def save(self, commit=True, **kwargs):
        super(Child, self).save(commit, **kwargs)
        # more logic
Run Code Online (Sandbox Code Playgroud)

它避免了从*args和访问提交参数,并且**kwargs如果Parent:save更改的签名(例如添加新的默认参数),它可以保证安全.

更新:在这种情况下,如果将新的位置参数添加到父级,则使用*args会导致麻烦.我只会保留**kwargs并仅使用默认值管理新参数.它可以避免传播错误.

  • 我相信这是最好的答案,因为它解决了问题所暴露的需求。即:保持参数明确而不是将来向父级添加参数的可能性。尽管@dietbuddha 提供的答案在“学术上”是正确的,但它并没有提供可以在实践中使用的替代方案,当您不确定未来代码的命运会是什么时。这个答案解决了这个问题。 (3认同)

dmg*_*dmg 7

如果您确定 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
Run Code Online (Sandbox Code Playgroud)

这样,签名中的更改在 Child 中非常易读,而原始签名在 Parent 中非常易读。

在我看来,当你有多重继承时,这也是更好的方法,因为super当你没有 args 和 kwargs 时,调用几次是很恶心的。

就其价值而言,这也是不少 Python 库和框架(Django、Tornado、Requests、Markdown 等)中的首选方式。尽管人们不应该根据这些事情做出选择,但我只是暗示这种方法非常普遍。


Gen*_*cos 5

我更喜欢显式参数,因为自动完成功能允许您在调用函数时查看函数的方法签名。


Cle*_*aar 5

不是真正的答案,而是更多的旁注:如果您真的非常想确保父类的默认值传播到子类,您可以执行以下操作:

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)
Run Code Online (Sandbox Code Playgroud)

然而我不得不承认这看起来很丑陋,只有当我觉得我真的需要它时我才会使用它。