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

Python 继承最佳实践:*args、**kwargs或显式指定参数,python,inheritance,method-signature,Python,Inheritance,Method Signature,我经常发现自己在覆盖父类的方法,并且永远无法决定是否应该显式列出给定的参数,或者仅仅使用一个总括的*args,**kwargs构造。一个版本比另一个好吗?是否有最佳实践?我缺少什么(缺点)优势 class Parent(object): def save(self, commit=True): # ... class Explicit(Parent): def save(self, commit=True): super(Explicit,

我经常发现自己在覆盖父类的方法,并且永远无法决定是否应该显式列出给定的参数,或者仅仅使用一个总括的
*args,**kwargs
构造。一个版本比另一个好吗?是否有最佳实践?我缺少什么(缺点)优势

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
显性变体的感知益处

  • 更明确(Python的Zen)
  • 容易掌握
  • 易于访问的功能参数
毛毯变体的感知好处

  • 更干燥
  • 父类很容易互换
  • 父方法中默认值的更改将在不接触其他代码的情况下传播
    • 我的选择是:

      class Child(Parent):
      
          def save(self, commit=True, **kwargs):
              super(Child, self).save(commit, **kwargs)
              # more logic
      
      它避免了从
      *args
      **kwargs
      访问提交参数,并且如果
      父级:save
      的签名发生更改(例如添加新的默认参数),它可以确保事情的安全


      更新:在这种情况下,如果将新的位置参数添加到父参数,则使用*args可能会导致问题。我将只保留
      **kwargs
      ,并且只管理带有默认值的新参数。这样可以避免错误传播。

      如果您确定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
      
      这样,签名中的更改在子级中是可读的,而原始签名在父级中是可读的

      在我看来,当您有多重继承时,这也是一种更好的方法,因为当您没有args和kwargs时,多次调用
      super
      是非常令人厌恶的


      值得一提的是,这也是许多Python库和框架(Django、Tornado、Requests、Markdown等等)中首选的方法。尽管人们不应该基于这些东西来选择,但我只是暗示这种方法非常普遍。

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

      Liskov替换原则

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

      明确签名的好处

      同时,我不认为所有的方法都有
      *args
      **kwargs
      的签名是正确的。明确签名:

      • 通过好的参数名帮助记录方法
      • 通过指定哪些参数是必需的,哪些参数具有默认值,帮助记录该方法
      • 提供隐式验证(缺少必需的参数会引发明显的异常)
      可变长度参数和耦合

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

      使用可变长度参数的位置

      使用可变长度参数不应该是您的第一选择。当您有以下充分理由时,应使用:

      • 定义函数包装器(即装饰器)
      • 定义参数多态函数
      • 当您可以采用的参数确实是完全可变的(例如,广义DB连接函数)。DB连接函数通常采用多种不同的形式,包括单参数形式和多参数形式。对于不同的数据库,也有不同的选项集
      你做错什么了吗?


      如果您发现您经常创建包含许多参数的方法或具有不同签名的派生方法,那么您在如何组织代码方面可能会遇到更大的问题。

      这不是一个真正的答案,而是一个旁注:如果您真的,如果确实要确保父类的默认值传播到子类,可以执行以下操作:

      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)
      

      然而,我不得不承认这看起来很难看,我只会在我真的需要的时候使用它。

      除了其他答案:

      使用可变参数可能会将父对象与子对象“解耦”,但会在所创建的对象与父对象之间创建耦合,我认为这更糟糕,因为现在您创建了“长距离”耦合(更难发现,更难维护,因为您可能会在应用程序中创建多个对象)


      如果您正在寻找解耦,请查看

      这在很大程度上取决于您正在子类化什么。。。如果您很有可能会向基类的方法添加(或者其他人会添加)额外的kwargs,那么坚持使用
      **kwargs
      是很有意义的。如果这不太可能,那么从可读性的角度来看,显式指定kwargs肯定要好得多。显式优于隐式以及所有这些。您有两种选择:1)显式命名参数并禁止在派生类中更改签名(否则goodluck with
      super
      和多重继承),或2)使用
      **kwargs
      并允许更改方法的签名。使用*args和**kwargs很难创建正确的docstring,在vsc中使用intellisense,pycharm编辑器不会选择它们。我相信这是最好的答案,因为它解决了问题所暴露的需求。即:保持参数的明确性,而不是将来向父级添加参数的可能性。尽管@d给出了答案