【问题标题】:Python class design for inheritance with varied number of inherited methods用于继承的 Python 类设计,具有不同数量的继承方法
【发布时间】:2018-04-20 13:08:04
【问题描述】:

我正在为 SOAP API 构建 Python API 包装器。我需要设计一组 API 类,其中包含不同数量的动词动作(addgetlistremove 等)。此外,由于 SOAP API 中对象的性质,这些对象还可能包含来自(syncoptionsapplyrestartreset)子集的其他方法。每个动词的内部意味着很少有情况需要覆盖并且可以很容易地被继承。我的问题是某些端点是单例的,或者出于某种原因可能只支持这些方法的一个子集。意思是:

端点A

  • 仅限get

端点B

  • add, get, list, remove
  • applyrestart

端点C

  • 仅限 addget

端点D

  • add, get, list, remove
  • apply, restart, reset

我有超过 100 个端点。最符合以下共同主题:

  • add, get, list, remove

不过,也有很多例外。

在我当前的设计中,所有端点都使用Client 属性进行实例化,该属性控制在线上的 SOAP 连接、请求和响应。我正在寻找一种方法来灵活地创建类设计,允许我在不需要复制代码或无意中继承 API 端点不支持的方法的情况下插入方法。

平面继承是有问题的,因为我没有足够的灵活性来处理方法的所有排列。

BaseAPI(object):

    def __init___(self, client):
        self.client = client


ChildAPI(BaseAPI):

    def __init___(self, client):
        super().__init__(client)

    def get(self, **kwargs):
        soap_method = methodcaller("".join(["get", self.__class__.__name__]), **kwargs)
        resp = soap_method(self.client.service)
        return resp

    def list(self, **kwargs):
        soap_method = methodcaller("".join(["list", self.__class__.__name__]), **kwargs)
        resp = soap_method(self.client.service)
        return stuff

    # same for add and remove...


EndpointA(BaseAPI):

    def __init___(self, client):
        super().__init__(client)

    # now i have a problem as i only wanted the get method...


EndpointD(BaseAPI):

    def __init___(self, client):
        super().__init__(client)

    # I have all the methods I wanted...

我在考虑 mixins,但正如您所见,动词方法依赖于公共客户端。据我了解,mixin 应该只继承自object

谁能建议如何布置我的类设计以尽可能促进重用,并避免缺乏灵活性?

【问题讨论】:

    标签: python-3.x inheritance multiple-inheritance


    【解决方案1】:

    我是否正确理解该方法的实现,如果端点支持它,对于所有端点都是一样的?在这种情况下,您可以使用包含支持的方法名称并被每个 Endpoint 子类覆盖的小列表来解决它:

    import functools
    
    class BaseAPI:
        def __init___(self, client):
            self.client = client
    
    class ChildAPI(BaseAPI):
        supported_methods = ["add", "get", "list", "remove", "apply", "restart", "reset"]
    
        @classmethod
        def assert_supported(cls, func):
            """Decorator looks up func's name in self.supported_methods."""
            @functools.wraps(func)
            def wrapper(self, *args, **kwargs):
                if func.__name__ not in self.supported_methods:
                    raise AttributeError("This Endpoint does not support this method.")
                return func(self, *args, **kwargs)
            return wrapper
    
        # implementation of methods here, each decorated with @assert_supported
        @assert_supported
        def get(self, **kwargs):
            soap_method = methodcaller("".join(["get", self.__class__.__name__]), **kwargs)
            resp = soap_method(self.client.service)
            return resp
    
        @assert_supported
        def list(self, **kwargs):
            soap_method = methodcaller("".join(["list", self.__class__.__name__]), **kwargs)
            resp = soap_method(self.client.service)
            return stuff
    
    class EndpointA(BaseAPI):
        supported_methods = ["get"]
    
    class EndpointB(BaseAPI):
        supported_methods = ["add", "get", "list", "remove", "apply", "restart"]
    
    class EndpointC(BaseAPI):
        supported_methods = ["add", "get"]
    
    class EndpointD(BaseAPI):
        pass   # no change to supported_methods => all of them
    

    这减少了将基本方法调整为每个类一行的工作量,并且仍然让您可以灵活地添加任何必要的代码。

    【讨论】:

    • 这似乎是支持这一点的最可行方法。我能看到的唯一缺点是这些方法似乎可以从 IDE 中获得,但付出的代价很小。热衷于对此进行测试。
    • @1:是的。您仍然可以在名称前加上“_”,这应该在大多数自动完成场景中隐藏_supported_methods。 @2:两者都是可能的,但我个人更喜欢将装饰器函数直接保留在应该使用的地方,这仅在此类中,因为它强烈依赖于此类的 supported_methods 属性。
    • 是的,我会用_supported_methods 隐藏,但我的意思是更多关于add 出现在自动完成中,当端点不支持时。同样,与我现在的情况相比,这是一个很好的问题......建议的解决方案对我来说很棒。
    • 我剩下的只是一个极端情况,其中一些端点需要不符合基本方法签名的方法,因此覆盖实际上是一个重载,它违反了 LSP。我正在考虑为这些扩展BaseAPI。我不确定在 Python 中如何更好地处理这个问题。
    • 啊,对不起,我将“方法”与“支持的方法”属性混淆了。我在想一个不同的装饰器,如果不支持,它将从类中删除该方法,但装饰器无法访问该类。或者至少我没有让它工作。嗯 LSP ......我承认我不得不用谷歌搜索:D 但如果你真的需要它,也许有一种方法可以让你的重载只是签名中的附加关键字参数?
    猜你喜欢
    • 2011-04-07
    • 1970-01-01
    • 2021-09-28
    • 2017-02-07
    • 2023-02-09
    • 1970-01-01
    • 1970-01-01
    • 2014-11-15
    • 1970-01-01
    相关资源
    最近更新 更多