首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >子类的工厂方法的签名

子类的工厂方法的签名
EN

Stack Overflow用户
提问于 2021-01-15 16:43:07
回答 1查看 1K关注 0票数 2

一个子类的方法与基类的相应方法具有相同的签名,这是一个很好的实践。如果有人违反了这一原则,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的意外行为,还是确实有一种更为毕达通的方式来实现这种模式?

代码语言:javascript
复制
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)
EN

回答 1

Stack Overflow用户

发布于 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__中获得

代码语言:javascript
复制
>>> Bottom.__dict__

{'__module__': '__main__',
'__init__': <function Bottom.__init__ at 0x0000024BD43058B0>,
'__doc__': None}

如果我们在类Bottom中重写def right(self, one):方法,那么__dict__现在将拥有

代码语言:javascript
复制
>>> 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教程。 派生类可以重写其基类的方法。由于方法在调用同一对象的其他方法时没有特权,因此--一个基类的方法,调用在同一基类中定义的另一个方法,最终可能会调用重写它的派生类的方法。

让我们在操作中验证上面的内容,这是一个最小的例子:

代码语言:javascript
复制
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__()

运行示例:

代码语言:javascript
复制
>>> 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意味着你可以用两种方式来做,你可以选择:

  1. 使用不同的签名覆盖该方法,需要:
  • 意识到它的含义并增加双向依赖。
  • 如果您选择沉默linter警告,这可能会使程序员在您之后更加不高兴(他现在被剥夺了公平的警告)。
  1. 使用4.7.4.任意参数列表
  • 这很可能是笛卡尔式的选择,因为它更简单。您可以做的是在docstring中文档,工厂方法返回类的实例,变量签名中传递的参数应该遵循构造函数的参数,如下所示:
代码语言:javascript
复制
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.
        """
  • 对于提示返回类型的类型的最后"gotcha“,请参见这个极佳的职位,在本例中,为了简单起见,使用了”前进声明“。它不改变签名,只更改方法的__annotations__属性。然而,“是Pythonic”我们可以汇到BDFL员额,我不完全确定是否输入暗示返回违反了Liskov代换原理,但是Mypy和PyCharm linter似乎让我逃脱了.
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65740082

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档