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

2024-04-20 00:30:48 发布

您现在位置:Python中文网/ 问答频道 /正文

我经常发现自己在重写父类的方法,并且永远无法决定是应该显式地列出给定的参数,还是仅仅使用一个blanket*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的禅)
  • 容易掌握
  • 易于访问的功能参数

总括变量的感知益处

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

Tags: 方法selftrue参数savedefmoreargs
3条回答

Liskov替代原理

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

显式签名的好处

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

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

可变长度参数和耦合

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

使用可变长度参数的位置

使用可变长度参数不应该是第一个选择。当你有一个很好的理由,比如:

  • 定义函数包装器(即decorator)。
  • 定义参数多态函数。
  • 当你能接受的参数真的是完全可变的(例如,一个通用的数据库连接函数)。DB连接函数通常采用connection string多种形式,既有单arg形式,也有多arg形式。对于不同的数据库,也有不同的选项集。
  • 。。。

你做错什么了吗?

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

如果您确定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 lib和框架(Django、Tornado、Requests、Markdown等等)中的首选方法。虽然人们不应该把自己的选择建立在这样的基础上,但我只是暗示,这种方法相当普遍。

我的选择是:

class Child(Parent):

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

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

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

相关问题 更多 >