继承:更改子方法的签名

问题描述 投票:0回答:4

当存在更改子级方法签名的“坏主意”时,我尝试找出 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
的方法,但我担心这会使我的代码可读性较差(自记录)。

python oop inheritance overriding
4个回答
2
投票

我想说,只需将参数添加回默认值 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 语法轻松地逐步完成。


1
投票

“我可以更改子方法的签名吗?”的答案是的,尽管如此,这是非常糟糕的做法。

如果您想要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 方法重写,签名重要吗?


0
投票

我不确定是否有一种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


0
投票

您可以随意命名参数,并添加任意数量的参数,只要参数分配给...

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,您将获得下拉框以选择要使用的参数。

© www.soinside.com 2019 - 2024. All rights reserved.