【问题标题】:Static methods and designing for inheritance静态方法和继承设计
【发布时间】:2016-10-06 04:37:54
【问题描述】:

我不清楚使用旨在被覆盖的静态方法设计类的最佳方法是什么。我会尝试用一个例子来解释。


我们有一个类Goat 和一个方法can_climb。 (顺便说一下,这是 Python 3,在 Python 2 中我会写 class Goat(object):。)

class Goat:
    def __init__(self, *args):
        ...

    def can_climb(self, mountain):
        return mountain.steepness < 10

billy = Goat("Billy")
if billy.can_climb(mount_everest):
    print("wow Billy, impressive")

这按预期工作,但方法can_climb 不使用self。将其设为静态方法似乎更简洁,pylint 甚至对上述方法发出警告。所以让我们改变一下:

class Goat:
    def __init__(self, *args):
        ...

    @staticmethod
    def can_climb(mountain):
        return mountain.steepness < 10

billy = Goat("Billy")
if billy.can_climb(mount_everest):
    print("wow Billy, impressive")

接近结尾的billy.can_climb 可以改为Goat.can_climb,在这个例子中不会有什么不同。有些人甚至可能认为通过类而不是实例调用静态方法更清晰、更直接。

但是,当我们使用继承和引入多态时,这会导致一个微妙的错误:

class Goat:
    def __init__(self, *args):
        ...

    @staticmethod
    def can_climb(mountain):
        return mountain.steepness < 10

class MountaineeringGoat(Goat):
    @staticmethod
    def can_climb(mountain):
        return True

billy = get_ourselves_a_goat_called_billy()
if Goat.can_climb(mount_everest):     # bug
    print("wow Billy, impressive")

调用Goat.can_climb 的代码知道billyGoat 的一个实例,并错误地假设它的类型是Goat,而实际上它可能是Goat 的任何子类型。或者它错误地假设子类不会覆盖静态方法。

在我看来,这似乎是一个容易犯的错误;可能Goat 在引入这个bug 的时候没有任何子类,所以没有注意到这个bug。


我们如何设计和记录一个类以避免这种错误?特别是,此示例中的 can_climb 应该是静态方法还是应该使用其他方法?

【问题讨论】:

  • 您已经演示了如何避免该错误;除非您需要特定的类,否则请在实例上调用方法(和属性)。并进行单元测试来检测功能回归。
  • 我还不相信我的设计是最好的设计。也许我真的应该使用实例方法?
  • @jonrsharpe 如果可能的话,我正在寻找从班级方面而不是调用方方面避免此问题的方法。
  • 你不能,如果调用者明确选择了类,那就是(并且应该!)被调用,没有什么可修复的。即使您将其设为实例方法,调用者仍然可以执行Goat.can_climb(billy, ...)。至于“最佳设计”,很难用一个抽象的例子来说明,至少部分基于意见。

标签: python inheritance polymorphism static-methods


【解决方案1】:

实际上,至少在 Python 3.8 中,我确实看到了 billy.can_climbGoat.can_climb 之间的区别,据我所知,代码并没有错误地假设billyGoat

class Mountain:
        def __init__(self, steepness):
            self.steepness = steepness

class Goat:
    @staticmethod
    def can_climb(mountain):
        return mountain.steepness < 10

class MountaineeringGoat(Goat):
    @staticmethod
    def can_climb(mountain):
        return True

def get_ourselves_a_goat_called_billy():
    return MountaineeringGoat()

mount_everest = Mountain(100)

billy = get_ourselves_a_goat_called_billy()
if billy.can_climb(mount_everest):
    print("wow Billy, impressive")

在上面的代码中,billy.can_climb(mount_everest) 实际上调用了MountaineeringGoat.can_climb(mount_everest),我确实看到了这个输出:
wow Billy, impressive

【讨论】:

    【解决方案2】:

    继承显然意味着对基类的了解。 @staticmethod 不知道它“附加”到的类(因此他们早先 - 而不是现在 - 称它为“未绑定”;现在从技术上讲 @staticmethod 根本不是一种方法;它是一个函数。

    但是@classmethod 完全知道它所附加的类;从技术上讲,它不是一个函数,而是一种方法。

    那为什么是@staticmethod呢?它是在派生类中继承的,但如前所述,没有基类的知识;我们可以像在派生类中定义它一样使用它。

    从非技术角度讲,@classmethods 是“有界的”; @staticmethods 不是。

    如果有人问我,我会建议将 @staticfunction 命名为 @staticmethod

    另外,为了确保调用最新的静态方法,您可以使用type(billy).can_climb(mountain)billy.__class__.can_climb(mountain)(虽然type(billy) 看起来更像pythonic),它从billy 的(子)类中调用静态方法.如果它没有被覆盖,它将使用基类的静态方法。

    【讨论】:

    • 既然你不止一次提到过,你能描述一下方法和函数在技术上的区别吗?
    • @ali 函数是无类的,方法是类内部的函数。这是与语言无关的 CompSci 事物
    • 我认为你最后的意思是@classmethod 而不是@staticfunction。此外,@staticmethod没有继承,也没有访问类成员的权限。
    • @whitey04 我刚刚为一个包含@staticmethod 的类创建了一个子类,而这个子类确实 继承了基类中staticmethod 中包装的方法。
    • @Jonathan 这个函数(无类)和方法(类附加)的定义可能是常见的/传统的,但它并不是所有语言的通用/不可知论:例如,Julia 对什么有不同的概念方法是:“对函数的一种可能行为的定义称为方法。” (docs.julialang.org/en/v1/manual/methods)
    猜你喜欢
    • 2017-02-07
    • 1970-01-01
    • 2023-04-05
    • 2012-06-21
    • 2015-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-10
    相关资源
    最近更新 更多