想象一下您想要继承的基类:
class Shape:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
Run Code Online (Sandbox Code Playgroud)
在子类的方法中处理父类的 kwargs 似乎有两种常见的模式__init__。
您可以完全重述父级的界面:
class Circle(Shape):
def __init__(self, x: float, y: float, radius: float):
super().__init__(x=x, y=y)
self.radius = radius
Run Code Online (Sandbox Code Playgroud)
或者,您可以仅指定特定于子级的接口部分,并将剩余的 kwargs 交给父级__init__:
class Circle(Shape):
def __init__(self, radius: float, **kwargs):
super().__init__(**kwargs)
self.radius = radius
Run Code Online (Sandbox Code Playgroud)
这两者似乎都有很大的缺点,所以我很想听听什么是标准或最佳实践。
“重述接口”方法在玩具示例中很有吸引力,就像您在Python 继承的讨论中常见的那样,但是如果我们使用非常复杂的接口(例如pandas.DataFrameor )对某些东西进行子类化怎么办logging.Logger?
另外,如果父接口发生变化,我必须记住更改所有子类的接口以匹配、类型提示等。不是很干。
在这些情况下,您几乎肯定会选择该**kwargs选项。
但该**kwargs选项使用户不确定实际需要哪些参数。
在上面的玩具示例中,用户可能天真地写道:
circle = Circle() # Argument missing for parameter "radius"
Run Code Online (Sandbox Code Playgroud)
他们的 IDE(或 mypy 或 Pyright)很有帮助,并表示该radius参数是必需的。
circle = Circle(radius=5)
Run Code Online (Sandbox Code Playgroud)
IDE(或类型检查器)现在很满意,但代码实际上不会运行:
Traceback (most recent call last):
File "foo.py", line 13, in <module>
circle = Circle(radius=5)
File "foo.py", line 9, in __init__
super().__init__(**kwargs)
TypeError: Shape.__init__() missing 2 required positional arguments: 'x' and 'y'
Run Code Online (Sandbox Code Playgroud)
因此,我不得不在多次写出父接口和在错误使用子类时不被 IDE 警告之间进行选择。
该怎么办?
这个 mypy 问题与此松散相关。
这个 Reddit 帖子很好地排练了支持/反对我概述的每种方法的相关论点。
这个问题可能与这个问题重复。我所说的事实有__init__什么区别吗?
我发现了一个真正的重复,尽管答案有点深奥,而且似乎不符合最佳或正常的做法。
Dan*_*inn 18
如果父类具有必需的(位置)参数(就像您的Shape类一样),那么我认为您必须将这些参数包含在__init__子类 ( Circle) 中,以便能够传递“类似形状”的实例并确保 a 的Circle行为与任何其他形状一样。所以这将是你的Circle课程:
class Shape:
def __init__(x: float, y: float):
self.x = x
self.y = y
class Circle(Shape):
def __init__(x: float, y: float, radius: float):
super().__init__(x=x, y=y)
self.radius = radius
# The expectation is that this should work with all instances of `Shape`
def move_shape(shape: Shape, x: float, y: float):
shape.x = x
shape.y = y
Run Code Online (Sandbox Code Playgroud)
然而,如果父类使用可选的kwargs,那就是事情变得棘手的地方。您不必仅仅因为是 的可选参数而colour: str在您的Circle类上进行定义。由使用您的类的开发人员了解所有形状的接口,如果需要,询问代码并注意在传递给其父构造函数时可以接受:colourShapeCircleCirclecolour=green**kwargs
class Shape:
def __init__(x: float, y: float, colour: str = "black"):
self.x = x
self.y = y
self.colour = colour
class Circle(Shape):
def __init__(x: float, y: float, radius: float, **kwargs):
super().__init__(x=x, y=y, **kwargs)
self.radius = radius
def move_shape(shape: Shape, x: float, y: float):
shape.x = x
shape.y = y
def colour_shape(shape: Shape, colour: str):
shape.colour = colour
Run Code Online (Sandbox Code Playgroud)
一般来说,我的态度是文档字符串的存在是为了解释为什么某些东西是这样写的,而不是它在做什么。从代码中应该可以清楚地看出这一点。因此,如果您Circle需要在父类中使用xandy参数,那么它应该在签名中说明同样多的内容。如果父类有可选要求,那么**kwargs子类就足够了,开发人员有责任询问Circle并Shape查看有哪些选项。
joa*_*nis 10
我认为最合理的解决方案(尽管我意识到我所说的可能不规范)是重复所需的父类参数,但将可选参数保留给**kwargs.
好处:
| 归档时间: |
|
| 查看次数: |
2845 次 |
| 最近记录: |