简体   繁体   English

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

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

I often find myself overwriting methods of a parent class, and can never decide if I should explicitly list given parameters or just use a blanket *args, **kwargs construct.我经常发现自己覆盖了父 class 的方法,并且永远无法决定我是应该显式列出给定参数还是只使用一揽子*args, **kwargs构造。 Is one version better than the other?一个版本比另一个好吗? Is there a best practice?有最佳实践吗? What (dis-)advantages am I missing?我缺少什么(不利)优势?

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

Perceived benefits of explicit variant显式变体的感知好处

  • More explicit (Zen of Python)更明确(Python之禅)
  • easier to grasp更容易掌握
  • function parameters easily accessed function参数轻松获取

Perceived benefits of blanket variant一揽子变体的感知好处

  • more DRY更干
  • parent class is easily interchangeable父母 class 很容易互换
  • change of default values in parent method is propagated without touching other code在不触及其他代码的情况下传播父方法中默认值的更改

Liskov Substitution Principle 利斯科夫替代原则

Generally you don't want you method signature to vary in derived types. 通常,您不希望方法签名在派生类型中有所不同。 This can cause problems if you want to swap the use of derived types. 如果要交换派生类型的使用,这可能会导致问题。 This is often referred to as the Liskov Substitution Principle . 这通常被称为Liskov替代原则

Benefits of Explicit Signatures 显式签名的好处

At the same time I don't think it's correct for all your methods to have a signature of *args , **kwargs . 与此同时,我认为你的所有方法都不具备*args**kwargs的签名是正确的。 Explicit signatures: 明确签名:

  • help to document the method through good argument names 通过良好的参数名称帮助记录方法
  • help to document the method by specifying which args are required and which have default values 通过指定哪些args是必需的以及哪些具有默认值来帮助记录方法
  • provide implicit validation (missing required args throw obvious exceptions) 提供隐式验证(缺少必需的args抛出明显的异常)

Variable Length Arguments and Coupling 可变长度参数和耦合

Do not mistake variable length arguments for good coupling practice. 不要将变长参数误认为是良好的耦合实践。 There should be a certain amount of cohesion between a parent class and derived classes otherwise they wouldn't be related to each other. 父类和派生类之间应该有一定的内聚力,否则它们就不会彼此相关。 It is normal for related code to result in coupling that reflects the level of cohesion. 相关代码导致耦合反映内聚水平是正常的。

Places To Use Variable Length Arguments 使用可变长度参数的位置

Use of variable length arguments shouldn't be your first option. 使用可变长度参数不应该是您的第一选择。 It should be used when you have a good reason like: 当你有充分的理由时应该使用它:

  • Defining a function wrapper (ie a decorator). 定义函数包装器(即装饰器)。
  • Defining a parametric polymorphic function. 定义参数化多态函数。
  • When the arguments you can take really are completely variable (eg a generalized DB connection function). 当您可以采用的参数确实是完全可变的(例如,通用的DB连接函数)。 DB connection functions usually take a connection string in many different forms, both in single arg form, and in multi-arg form. 数据库连接函数通常采用多种不同形式的连接字符串 ,包括单个arg形式和多arg形式。 There are also different sets of options for different databases. 不同的数据库也有不同的选项集。
  • ... ...

Are You Doing Something Wrong? 你做错了什么吗?

If you find you are often creating methods which take many arguments or derived methods with different signatures you may have a bigger issue in how you're organizing your code. 如果您发现自己经常创建带有许多参数的方法或带有不同签名的派生方法,那么您在编写代码时可能会遇到更大的问题。

My choice would be: 我的选择是:

class Child(Parent):

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

It avoids accessing commit argument from *args and **kwargs and it keeps things safe if the signature of Parent:save changes (for example adding a new default argument). 它避免了从*args**kwargs访问commit参数,如果Parent:save的签名Parent:save更改(例如添加新的默认参数),它可以保证安全。

Update : In this case, having the *args can cause troubles if a new positional argument is added to the parent. 更新 :在这种情况下,如果将新的位置参数添加到父级,则使用* args会导致麻烦。 I would keep only **kwargs and manage only new arguments with default values. 我只保留**kwargs并仅使用默认值管理新参数。 It would avoid errors to propagate. 它可以避免传播错误。

If you are certain that Child will keep the signature, surely the explicit approach is preferable, but when Child will change the signature I personally prefer to use both approaches: 如果你确定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

This way, changes in the signature are quite readable in Child, while the original signature is quite readable in Parent. 这样,签名中的更改在Child中非常易读,而原始签名在Parent中非常易读。

In my opinion this is also the better way when you have multiple inheritance, because calling super a few times is quite disgusting when you don't have args and kwargs. 在我看来,当你有多重继承时,这也是更好的方法,因为当你没有args和kwargs时,调用super几次是非常恶心的。

For what it's worth, this is also the preferred way in quite a few Python libs and frameworks (Django, Tornado, Requests, Markdown, to name a few). 对于它的价值,这也是很多Python库和框架(Django,Tornado,Requests,Markdown等)的首选方式。 Although one should not base his choices on such things, I'm merely implying that this approach is quite widespread. 虽然不应该根据这些事情做出选择,但我只是暗示这种做法非常普遍。

Not really an answer but more a side note: If you really, really want to make sure the default values for the parent class are propagated to the child classes you can do something like: 不是一个答案,而是一个侧面说明:如果你真的,真的想确保父类的默认值传播到子类,你可以做类似的事情:

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)

However I have to admit this looks quite ugly and I would only use it if I feel I really need it. 但是我不得不承认这看起来很难看,如果我觉得我真的需要它,我只会使用它。

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

In addition to the other answers: 除了其他答案:

Having variable arguments may "decouple" the parent from the child, but creates a coupling between the object created and the parent, which I think is worse, because now you created a "long distance" couple (more difficult to spot, more difficult to maintain, because you may create several objects in your application) 拥有变量参数可能会将父项与子项“解耦”,但会在创建的对象和父项之间创建耦合,我认为这更糟糕,因为现在你创建了一个“长距离”情侣(更难以发现,更难以维护,因为您可以在应用程序中创建多个对象)

If you're looking for decoupling, take a look at composition over inheritance 如果您正在寻找脱钩,请查看合成而不是继承

Why is nobody pointing out the fact that the Parent class save() method is not implementing the *args and **kwargs which would cause an error if instances of Blanket tries to call the save method with more keyword or positional arguments.为什么没有人指出 Parent class save() 方法未实现*args and **kwargs的事实,如果Blanket的实例尝试使用更多关键字或位置 arguments 调用 save 方法,这将导致错误。

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

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