Python 继承最佳实践:*args、**kwargs 或明确指定参数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14626279/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Inheritance best practice : *args, **kwargs or explicitly specifying parameters
提问by Maik Hoepfel
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, **kwargsconstruct. Is one version better than the other? Is there a best practice? What (dis-)advantages am I missing?
我经常发现自己覆盖了父类的方法,并且永远无法决定是应该明确列出给定的参数还是只使用总括*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
Perceived benefits of explicit variant
显式变体的感知好处
- More explicit (Zen of Python)
- easier to grasp
- function parameters easily accessed
- 更明确(Python 之禅)
- 更容易掌握
- 易于访问的函数参数
Perceived benefits of blanket variant
整体变体的感知优势
- more DRY
- parent class is easily interchangeable
- change of default values in parent method is propagated without touching other code
- 更干
- 父类很容易互换
- 父方法中默认值的更改在不涉及其他代码的情况下传播
采纳答案by dietbuddha
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. Explicit signatures:
同时,我不认为这是正确的为您的所有方法有一个签名*args,**kwargs。显式签名:
- 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
- provide implicit validation (missing required args throw obvious exceptions)
- 通过良好的参数名称帮助记录方法
- 通过指定需要哪些参数以及哪些具有默认值来帮助记录方法
- 提供隐式验证(缺少必需的参数会引发明显的异常)
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 (i.e. a decorator).
- Defining a parametric polymorphic function.
- When the arguments you can take really are completely variable (e.g. a generalized DB connection function). DB connection functions usually take a connection stringin many different forms, both in single arg form, and in multi-arg form. There are also different sets of options for different databases.
- ...
- 定义一个函数包装器(即装饰器)。
- 定义参数多态函数。
- 当您可以采用的参数确实完全可变时(例如,通用数据库连接函数)。DB 连接函数通常采用多种不同形式的连接字符串,包括单 arg 形式和多 arg 形式。不同的数据库也有不同的选项集。
- ...
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.
如果您发现您经常创建带有许多参数或具有不同签名的派生方法的方法,那么您在如何组织代码方面可能会遇到更大的问题。
回答by luc
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 *argsand **kwargsand it keeps things safe if the signature of Parent:savechanges (for example adding a new default argument).
它避免从*args和访问提交参数,并且**kwargs如果Parent:save更改签名(例如添加新的默认参数),它可以保证事情的安全。
Update: In this case, having the *args can cause troubles if a new positional argument is added to the parent. I would keep only **kwargsand manage only new arguments with default values. It would avoid errors to propagate.
更新:在这种情况下,如果将新的位置参数添加到父级,拥有 *args 可能会导致麻烦。我只会保留**kwargs和管理具有默认值的新参数。这样可以避免错误传播。
回答by dmg
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 supera few times is quite disgusting when you don't have args and kwargs.
在我看来,当你有多重继承时,这也是更好的方法,因为super当你没有 args 和 kwargs 时,调用几次是很恶心的。
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). Although one should not base his choices on such things, I'm merely implying that this approach is quite widespread.
就其价值而言,这也是不少 Python 库和框架(Django、Tornado、Requests、Markdown 等)中的首选方式。虽然人们不应该根据这些事情做出选择,但我只是暗示这种方法非常普遍。
回答by GeneralBecos
I prefer explicit arguments because auto complete allows you to see the method signature of the function while making the function call.
我更喜欢显式参数,因为自动完成允许您在进行函数调用时查看函数的方法签名。
回答by Clemens Klein-Robbenhaar
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.
但是我不得不承认这看起来很丑陋,只有当我觉得我真的需要它时我才会使用它。
回答by JACH
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
如果您正在寻找解耦,请查看组合而不是继承

