Python 继承:更改子方法的签名
我试图找出Python继承原则中的最佳实践是什么,当有一个“坏主意”在子级中更改方法签名时 假设我们有一些基类Python 继承:更改子方法的签名,python,oop,inheritance,overriding,Python,Oop,Inheritance,Overriding,我试图找出Python继承原则中的最佳实践是什么,当有一个“坏主意”在子级中更改方法签名时 假设我们有一些基类BaseClient,其中包含已实现的create方法(以及一些抽象方法),这些方法适用于几乎所有的“后代”,除了一个: class BaseClient(object): def __init__(self, connection=None): pass def create(self, entity_id, data=None):
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
所以问题是,考虑到最佳python实践,如何以更“pythonic”的方式实现这一点?当然,我们可以在父(子)方法中使用
*args、**kwargs
和其他类似**kwargs
的方法,但我担心这会降低我的代码的可读性(自文档化)。我不确定是否有一种类似python的方法可以做到这一点,因为你可以像在问题中那样做。相反,我想说的是,这更多的是关于OOP,而不是Pythonic
因此,我假设在BaseClient
中实现了其他方法,而不是其他子级共享的create
(否则,没有必要将ClientC
作为BaseClient
的子级)。在您的例子中,似乎ClientC
与其他方法有所不同,它要求对create
方法进行不同的签名。那么也许是考虑分裂它们的原因了?
例如,您可以让rootBaseClient
实现除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
中使用签名\u create(self,entity\u id=None,data=None)实现更通用的私有方法来避免代码重复,然后从EntityBasedClient
和DataBasedClient
内部使用适当的参数调用它,我想说的是,只需将参数添加回默认值为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语法一步一步地轻松地做到这一点。对“我可以更改孩子方法的签名吗?”的回答是肯定的,尽管这是一种非常糟糕的做法
如果您希望在不违反父类的情况下,重写父类的子函数必须具有相同的签名
上面的例子是:
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
违反了原则,并且还会发出警告(“参数与重写的“create”方法不同”)
此外,按照@Sanitiy的建议,为了保持签名的一致性,提出了一个RedudantInformationError
,这仍然违反了原则,因为如果用父方法代替子方法,则会有不同的行为
另请看:
替换原则:如果子类不能完成基类所能做的事情,那么设计通常是不好的-x.create(entity_id)
适用于BaseClient
,因此它应该适用于它的子类。为什么ClientC需要另一个create实现?zch说的是:子类型可以向方法签名添加新的参数,但是它不应该删除它们,因为这违反了。IMO最好在ClientC
中添加一个类方法,该类方法接受所需的参数并返回该参数的实例。这是可以接受的,因为类的用户在创建实例时已经知道他们想要该子类(因此可能也只知道它拥有的类方法)。如果没有任何其他上下文,我会声明BaseClient
应该有两种方法:create\u with_entity
,它与当前的create
具有相同的签名,以及一个新的create
,它不接受实体id
,可能只是create\u with\u entity
的包装器。我认为这是令人困惑的,因为以前编写的预期BaseClient的函数现在只适用于EntityBasedClient。这使得代码很难理解。ClientC中的create\u from_data()
方法似乎更健壮。正如Martineau所说,需要从_data()调用create_的代码知道它处理ClientC实例“……以前编写的预期BaseClient的函数现在只能与EntityBasedClient一起工作”。为什么会是这样?正如我所提到的,所有共享方法(也共享签名)都应该在BaseClient
中实现,因此可以从任何子级访问。或者我遗漏了什么?你的意思是,只要不调用create(),它们仍然可以工作。必须仔细检查每个函数。从OOP的角度来看,在这种情况下,如果函数需要BaseClient
对象,那么它不应该调用create
,因为调用哪个实现是不明确的。同样,如果要使用create\u from\u data
方法,则需要确保对象是ClientC
的实例。这与确保对象是DataBasedClient
子对象的实例相同。我想在某些情况下,您的设计可能是正确的,但我们对这些客户机类知之甚少。例如,名为create()的函数
可能是某种构造函数,这可能会改变它的处理方式。这就是为什么我要求OP告诉我们为什么ClientC需要一个不同的签名。