繁体   English   中英

Inheritance 最佳实践:*args、**kwargs 或显式指定参数 [关闭]

[英]Inheritance best practice : *args, **kwargs or explicitly specifying parameters [closed]

我经常发现自己覆盖了父 class 的方法,并且永远无法决定我是应该显式列出给定参数还是只使用一揽子*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之禅)
  • 更容易掌握
  • function参数轻松获取

一揽子变体的感知好处

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

利斯科夫替代原则

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

显式签名的好处

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

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

可变长度参数和耦合

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

使用可变长度参数的位置

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

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

你做错了什么吗?

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

我的选择是:

class Child(Parent):

    def save(self, commit=True, **kwargs):
        super(Child, self).save(commit, **kwargs)
        # more logic

它避免了从*args**kwargs访问commit参数,如果Parent:save的签名Parent: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

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

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

对于它的价值,这也是很多Python库和框架(Django,Tornado,Requests,Markdown等)的首选方式。 虽然不应该根据这些事情做出选择,但我只是暗示这种做法非常普遍。

不是一个答案,而是一个侧面说明:如果你真的,真的想确保父类的默认值传播到子类,你可以做类似的事情:

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)

但是我不得不承认这看起来很难看,如果我觉得我真的需要它,我只会使用它。

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

除了其他答案:

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

如果您正在寻找脱钩,请查看合成而不是继承

为什么没有人指出 Parent class save() 方法未实现*args and **kwargs的事实,如果Blanket的实例尝试使用更多关键字或位置 arguments 调用 save 方法,这将导致错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM