当存在更改子级方法签名的“坏主意”时,我尝试找出 Python 继承原则中的最佳实践是什么。
假设我们有一些基类
BaseClient
,其中已经实现了 create
方法(和一些抽象方法),适合几乎所有“后代”,除了一个:
class BaseClient(object):
def __init__(self, connection=None):
pass
def create(self, entity_id, data=None):
pass
class ClientA(BaseClient):
pass
class ClientB(BaseClient):
pass
唯一的类
ClientC
需要另一个 create
方法的实现,并带有另一个方法签名
class ClientC(BaseClient):
....
def create(self, data):
pass
所以问题是如何以更“Pythonic”的方式实现这一点,同时考虑到Python的最佳实践?当然,我们可以在父(子)方法中使用
*args, **kwargs
和其他类似**kwargs
的方法,但我担心这会使我的代码可读性较差(自记录)。
我想说,只需将参数添加回默认值 None 的关键字即可。然后引发错误,解释部分输入数据丢失。
class ClientC(BaseClient):
....
def create(self,entity_id=None, data):
if entity_id:
raise RedudantInformationError("Value for entity_id does nothing")
pass
这样,每当程序员尝试像其他子级一样处理子级 C 时,他都会收到警告提醒,但是他可以通过使用 try 语法轻松地逐步完成。
“我可以更改子方法的签名吗?”的答案是的,尽管如此,这是非常糟糕的做法。
如果您想要SOLID并且不违反LSP,则覆盖父类的子函数必须具有相同的签名。
上面的例子:
class BaseClient:
def create(self, entity_id, data=None):
pass
class EntityBasedClient(BaseClient):
def create(self, entity_id, data=None):
pass
class DataBasedClient(BaseClient):
def create(self, data):
pass
违反了原则,还会引发 linter 警告(“参数与重写的‘create’方法不同”)
按照@Sanitiy的建议,为了保持签名的一致性而提出
RedudantInformationError
,仍然违反了原则,因为如果使用父方法代替子方法,它会有不同的行为。
另请参阅: Python 方法重写,签名重要吗?
我不确定是否有一种Python式的方法可以做到这一点,因为你可以按照你在问题中所做的那样做。相反,我想说这更多的是 OOP,而不是 Python 的问题。
因此,我假设除了其他子项共享的
BaseClient
之外,create
中还实现了其他方法(否则,使 ClientC
成为 BaseClient
的子项是没有意义的)。在您的情况下,看起来 ClientC
与其他方法不同,需要不同的 create
方法签名。那么是不是可以考虑拆分呢?
例如,您可以让根
BaseClient
实现除create
之外的所有共享方法,然后再有两个“base”子级,如下所示:
class EntityBasedClient(BaseClient):
def create(self, entity_id, data=None):
pass
class DataBasedClient(BaseClient):
def create(self, data):
pass
所以现在你可以继承而不违反任何规则:
class ClientA(EntityBasedClient):
pass
class ClientB(EntityBasedClient):
pass
class ClientC(DataBasedClient):
pass
此外,如果这两个版本的
create
实现非常相似,您可以通过在 BaseClient
中实现带有签名 _create(self, entity_id=None, data=None)
的更通用的私有方法来避免代码重复,然后从内部使用适当的参数调用它EntityBasedClient
和 DataBasedClient
。
您可以随意命名参数,并添加任意数量的参数,只要参数分配给...
from typing import Any, Optional
class BaseClient(object):
def __init__(self, connection = None):
pass
def create(self, entity_id, data=None):
pass
class ClientA(BaseClient):
def __init__(self):
super(ClientA, self).__init__()
pass
class ClientB(BaseClient):
def __init__(self):
super(ClientB, self).__init__()
pass
class ClientC(BaseClient):
def __init__(self):
super(ClientC, self).__init__()
pass
def create(self, param1: Optional[Any] = None, param2: Optional[Any] = None, param3: Optional[Any] = None, param4: Optional[Any] = None):
pass
PyCharm 永远不会抱怨它,你在使用它时必须在方法中分配参数...
ClientC().create(param1 = '', param4 = '')
您还可以使用...
class ClientC(BaseClient):
def __init__(self):
super(ClientC, self).__init__()
pass
def create(self, **kwargs):
data = kwargs.get('data', None)
使用 PyCharm,您将获得下拉框以选择要使用的参数。