一个子类的方法与基类的相应方法具有相同的签名,这是一个很好的实践。如果有人违反了这一原则,PyCharm就会发出警告:
Signature of method does not match signature of base method in class
这个原则有一个例外(至少):Python方法__init__。通常,子类具有与其父类不同的初始化参数。它们可能有额外的参数,也可能有较少的参数,通常是通过对父类的参数使用一个常量值来获得的。
由于Python不支持具有不同签名的多个初始化方法,因此具有不同构造函数的Pythonic方法称为工厂方法(参见:https://stackoverflow.com/a/682545/10816965)。
PyCharm认为,这些工厂方法对子类的方法应该与其相应的父类具有相同的签名这一原则也不例外。当然,我可以忽略这些警告--因为这些工厂方法类似于__init__或__new__,我认为人们可能会认为这些警告是错误的。
然而,我想知道我是否遗漏了一些东西,而且我的编码风格也不是最佳实践。
因此,我的问题是:这是PyCharm的意外行为,还是确实有一种更为毕达通的方式来实现这种模式?
class A:
@classmethod
def from_something(cls, something):
self = cls()
# f(self, something)
return self
def __init__(self):
pass
class B(A):
@classmethod
def from_something(cls, something, param): # PyCharm warning:
# Signature of method 'B.from_something()' does not match
# signature of base method in class 'A'
self = cls(param)
# g(self, something, param)
return self
def __init__(self, param):
super().__init__()
self.param = param
class C:
@classmethod
def from_something(cls, something, param):
self = cls(param)
# f(self, something, param)
return self
def __init__(self, param):
self.param = param
class D(C):
@classmethod
def from_something(cls, something): # PyCharm warning: Signature of
# method 'D.from_something()' does not match signature of base
# method in class 'C'
self = cls()
# g(self, something)
return self
def __init__(self):
super().__init__(None)发布于 2021-01-15 18:20:21
要考虑的主要问题是:
为了充分解决和理解这一点,让我们首先考虑:
在Python中,只使用名称查找类似于的属性。您可以通过查看__dict__ (例如,Class.__dict__和instance.__dict__)来检查它们在实例或类上的存在情况。
自定义类,3.2。标准类型层次结构 类具有由字典对象实现的命名空间。类属性引用在本词典中被转换为查找,例如,
C.x被转换为C.__dict__["x"](尽管有许多钩子允许其他定位属性的方法)。当在那里找不到属性名时,属性搜索继续在基类中进行.。
如果我们在没有方法def right(self, one):的情况下定义def right(self, one):,我们将从__dict__中获得
>>> Bottom.__dict__
{'__module__': '__main__',
'__init__': <function Bottom.__init__ at 0x0000024BD43058B0>,
'__doc__': None}如果我们在类Bottom中重写def right(self, one):方法,那么__dict__现在将拥有
>>> Bottom.__dict__
{'__module__': '__main__',
'__init__': <function Bottom.__init__ at 0x0000024BD43058B0>,
'right': <function Bottom.right at 0x0000024BD43483A0>,
'__doc__': None}这与其他面向对象语言(如Java )不同,这些语言具有基于参数的数量和类型(而不仅仅是名称)的解析/查找方法过载。在这方面,Python是凌驾,因为查找仅针对名称/类/实例。Python确实支持类型提示“重载”(严格意义上),请参见函数/方法重载,PEP 484 --类型提示
9.5。继承,Python教程。 派生类可以重写其基类的方法。由于方法在调用同一对象的其他方法时没有特权,因此--一个基类的方法,调用在同一基类中定义的另一个方法,最终可能会调用重写它的派生类的方法。
让我们在操作中验证上面的内容,这是一个最小的例子:
class Top:
def left(self, one):
self.right(one, 2)
def right(self, one, two):
pass
def __init__(self):
pass
class Bottom(Top):
def right(self, one):
pass
def __init__(self, one):
super().__init__()运行示例:
>>> t = Top()
>>> t.left(1)
>>> b = Bottom()
>>> b.left(1)
Traceback (most recent call last):
File "<input>", line 18, in <module>
File "<input>", line 3, in left
TypeError: right() takes 2 positional arguments but 3 were given在这里,您可以看到,更改派生类中方法的签名可以中断基类内部的方法调用。
这是一个严重的副作用,因为派生类只是限制了基类。您创建了一个向上依赖,这是反模式。通常,您希望约束/依赖项在继承中向下移动,而不是向上移动。您刚刚从单向依赖关系转到双向依赖关系。(在实践中,这可以为程序员增加更多的精力,因为他们现在必须考虑并处理附加的依赖关系-这与最小惊讶原则背道而驰,因为下一个查看您的代码的程序员可能不会高兴。)
或者,对于这种模式,是否确实有一种更为毕达通的方式?
这里的Pythonic意味着你可以用两种方式来做,你可以选择:
class Top:
def __init__(self):
pass
@classmethod
def from_something(cls, *args, **kwargs) -> "Top":
"""Factory method initializes and returns an instance of the class.
The arguments should follow the signature of the constructor.
"""
class Bottom(Top):
def __init__(self, one):
super().__init__()
@classmethod
def from_something(cls, *args, **kwargs) -> "Bottom":
"""Factory method initializes and returns an instance of the class.
The arguments should follow the signature of the constructor.
"""__annotations__属性。然而,“是Pythonic”我们可以汇到BDFL员额,我不完全确定是否输入暗示返回违反了Liskov代换原理,但是Mypy和PyCharm linter似乎让我逃脱了.https://stackoverflow.com/questions/65740082
复制相似问题