静态方法和继承设计

flo*_*ake 7 python polymorphism inheritance static-methods

我不清楚使用静态方法设计类的最佳方法是什么,这些方法可以覆盖.我将尝试用一个例子来解释.


我们有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")
Run Code Online (Sandbox Code Playgroud)

这按预期工作,但该方法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")
Run Code Online (Sandbox Code Playgroud)

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")
Run Code Online (Sandbox Code Playgroud)

调用的代码Goat.can_climb知道它billy是一个实例,Goat并且Goat当它确实是任何子类型时,会做出错误的假设,即它的类型Goat.或者它可能错误地假定子类不会覆盖静态方法.

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


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

小智 13

继承显然意味着对基类的了解.@staticmethod不知道它所附属的那个阶级(因此他们早些时候 - 不是现在 - 称它为'未绑定的';现在技术上@staticmethod根本不是一种方法;它是一种功能.

但是@classmethod完全了解它附属的课程; 它在技术上不是一种功能,而是一种方法.

为什么@staticmethod呢?它在派生类中继承,但如前所述,不知道基类; 我们可以"使用它",就像我们在派生类中定义它一样.

从技术上讲,@classmethods 'bounded';@staticmethod不是.

曾有人问我,我会建议名称@staticfunction@staticmethod.

  • 既然您不止一次提到过,您能描述一下方法和功能之间的技术区别是什么吗? (2认同)
  • @whitey04我刚刚为一个包含`@staticmethod`的类创建了一个子类,并且这个子类*确实*继承了基类中`staticmethod`中包装的方法。 (2认同)

Dim*_*tri 7

实际上,至少在 Python 3.8 中,我确实看到billy.can_climb了和之间的区别,Goat.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")
Run Code Online (Sandbox Code Playgroud)

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