简体   繁体   English

super().__init__(...) 和默认值

[英]super().__init__(...) and default values

Preamble前言

This is a rather basic question, I realize, but I haven't been able to find a sturdy reference for it, which would likely be a mixture of technical details and best practices for well-behaved classes.我意识到这是一个相当基本的问题,但我无法找到可靠的参考资料,这可能是技术细节和表现良好的课程的最佳实践的混合体。

Question问题

When a parent and child class both define the same initialization parameter, but with default values, what's the best way to get sane behavior when the child class is created?当父子 class 都定义相同的初始化参数但使用默认值时,创建子 class 时获得理智行为的最佳方法是什么?

My assumptions are:我的假设是:

  • Classes only accept named parameters , I don't need to deal with positional arguments.类只接受命名参数,我不需要处理位置 arguments。 That simplifies many things, both theoretically in reasoning about situations and practically in taking arguments from external config files, etc.这简化了许多事情,无论是在理论上推理情况还是在实践中从外部配置文件中获取 arguments 等。
  • __init__ methods may be more sophisticated than just setting self.foo = foo for their arguments - they may transform it before storing, use it to set other params, etc. and I'd like to be as respectful of that as possible. __init__方法可能比仅仅为他们的 arguments 设置self.foo = foo更复杂 - 他们可能会在存储之前对其进行转换,使用它来设置其他参数等,我希望尽可能尊重这一点。
  • Subclasses never break the interfaces of their parents, both for __init__ parameters and for attributes.对于__init__参数和属性,子类永远不会破坏其父类的接口。 Having a different default value is not considered "breaking".具有不同的默认值不被视为“破坏”。
  • Classes should never have to be aware of their subclasses, they should just do things in "reasonable ways" and it's up to subclasses to ensure everything still works properly.类永远不必知道它们的子类,它们应该以“合理的方式”做事,并且由子类来确保一切仍然正常工作。 However, it's sometimes necessary to modify a superclass to be "more reasonable" if it's doing things that aren't amenable to being subclassed - this can form a set of principles that help everyone get along well.但是,有时有必要将超类修改为“更合理”,如果它正在做的事情不适合被子类化 - 这可以形成一套帮助每个人相处融洽的原则。

Examples例子

In general, my idea of a "best practice" template for a derived class looks like this:一般来说,我对派生 class 的“最佳实践”模板的想法如下所示:

class Child(Parent):
    def __init__(self, arg=1, **kwargs):
        self.arg = arg
        super().__init__(**kwargs)

That works well in most situations - deal with our stuff, then delegate all the rest to our superclass.这在大多数情况下都很好——处理我们的东西,然后将所有 rest 委托给我们的超类。

However, it doesn't work well if arg is shared by both Child and Parent - neither the caller's argument nor the Child default are respected:但是,如果argChildParent共享,则效果不佳 - 调用者的参数和Child默认值都不受尊重:

class Parent:
    def __init__(self, arg=0):
        self.arg = arg


class Child(Parent):
    def __init__(self, arg=1, **kwargs):
        self.arg = arg
        super().__init__(**kwargs)


print(Child(arg=6).arg)
# Prints `0` - bad

A better approach is probably for Child to acknowledge that the argument is shared:更好的方法可能是让Child承认该论点是共享的:

class Parent:
    def __init__(self, arg=0):
        self.arg = arg


class Child(Parent):
    def __init__(self, arg=1, **kwargs):
        super().__init__(arg=arg, **kwargs)


print(Child(arg=6).arg)
# Prints `6` - good
print(Child().arg)
# Prints `1` - good

That successfully gets the defaults working according to expectations.这成功地使默认值按预期工作。 What I'm not sure of is whether this plays well with the expectations of Parent .我不确定这是否符合Parent的期望。 So I think my questions are:所以我认为我的问题是:

  • If Parent.__init__ does some Fancy Stuff with arg and/or self.arg , how should Child be set up to respect that?如果Parent.__init__arg和/或self.arg做了一些花哨的东西,那么应该如何设置Child来尊重它?
  • In general does this require knowing Too Much about the internals of Parent and how self.arg is used?一般来说,这是否需要对Parent的内部以及如何使用self.arg了解太多? Or are there reasonable practices that everyone can follow to draw that part of the interface contract in a clean way?或者是否有每个人都可以遵循的合理做法以干净的方式绘制接口契约的那部分?
  • Are there any specific gotchas to keep in mind?是否有任何具体的问题需要牢记?

Parent.__init__ only expects that the caller may choose to omit an argument for the arg parameter. Parent.__init__只期望调用者可以选择省略arg参数的参数。 It doesn't matter if any particular caller ( Child.__init__ , in this case) always provides an argument, nor does it matter how the caller produces the value it passes.任何特定的调用者(在这种情况下为Child.__init__总是提供参数并不重要,调用者如何产生它传递的值也无关紧要。

Your third example is what I would write, with the addition that Parent.__init__ itself also uses super().__init__ : it doesn't assume that it's the end of whatever MRO is in force for its self argument.你的第三个例子就是我要写的,除了Parent.__init__本身使用super().__init__ :它不假设它是任何 MRO 对其self参数有效的结束。

class Parent:
    def __init__(self, arg=0, **kwargs):
        super().__init__(**kwargs)
        self.arg = arg


class Child(Parent):
    def __init__(self, arg=1, **kwargs):
        super().__init__(arg=arg, **kwargs)

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

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