如何在Python中使用static、class、abstract方法(权威指南)

方法在Python中是如何工作的

方法就是一个函数,它作为一个类属性而存在,你可以用如下方式来声明、访问一个函数:

Python在告诉你,属性_get_size是类Pizza的一个未绑定方法。这是什么意思呢?很快我们就会知道答案:

我们不能这么调用,因为它还没有绑定到Pizza类的任何实例上,它需要一个实例作为第一个参数传递进去(Python2必须是该类的实例,Python3中可以是任何东西),尝试一下:

太棒了,现在用一个实例作为它的的第一个参数来调用,整个世界都清静了,如果我说这种调用方式还不是最方便的,你也会这么认为的;没错,现在每次调用这个方法的时候我们都不得不引用这个类,如果不知道哪个类是我们的对象,长期看来这种方式是行不通的。

那么Python为我们做了什么呢,它绑定了所有来自类_Pizza的方法以及该类的任何一个实例的方法。也就意味着现在属性get_sizePizza的一个实例对象的绑定方法,这个方法的第一个参数就是该实例本身。

和我们预期的一样,现在不再需要提供任何参数给_get_size,因为它已经是绑定的,它的self参数会自动地设置给Pizza实例,下面代码是最好的证明:

更有甚者,你都没必要使用持有Pizza对象的引用了,因为该方法已经绑定到了这个对象,所以这个方法对它自己来说是已经足够了。

也许,如果你想知道这个绑定的方法是绑定在哪个对象上,下面这种手段就能得知:

显然,该对象仍然有一个引用存在,只要你愿意你还是可以把它找回来。

在Python3中,依附在类上的函数不再当作是未绑定的方法,而是把它当作一个简单地函数,如果有必要它会绑定到一个对象身上去,原则依然和Python2保持一致,但是模块更简洁:

静态方法

静态方法是一类特殊的方法,有时你可能需要写一个属于这个类的方法,但是这些代码完全不会使用到实例对象本身,例如:

这个例子中,如果把_mix_ingredients作为非静态方法同样可以运行,但是它要提供self参数,而这个参数在方法中根本不会被使用到。这里的@staticmethod装饰器可以给我们带来一些好处:

  • Python不再需要为Pizza对象实例初始化一个绑定方法,绑定方法同样是对象,但是创建他们需要成本,而静态方法就可以避免这些。

  • 可读性更好的代码,看到@staticmethod我们就知道这个方法并不需要依赖对象本身的状态。
  • 可以在子类中被覆盖,如果是把mix_ingredients作为模块的顶层函数,那么继承自Pizza的子类就没法改变pizza的mix_ingredients了如果不覆盖cook的话。

类方法

话虽如此,什么是类方法呢?类方法不是绑定到对象上,而是绑定在类上的方法。

无论你用哪种方式访问这个方法,它总是绑定到了这个类身上,它的第一个参数是这个类本身(记住:类也是对象)。

什么时候使用这种方法呢?类方法通常在以下两种场景是非常有用的:

  • 工厂方法:它用于创建类的实例,例如一些预处理。如果使用@staticmethod代替,那我们不得不硬编码Pizza类名在函数中,这使得任何继承Pizza的类都不能使用我们这个工厂方法给它自己用。

  • 调用静态类:如果你把一个静态方法拆分成多个静态方法,除非你使用类方法,否则你还是得硬编码类名。使用这种方式声明方法,Pizza类名明永远都不会在被直接引用,继承和方法覆盖都可以完美的工作。

抽象方法

抽象方法是定义在基类中的一种方法,它没有提供任何实现,类似于Java中接口(Interface)里面的方法。

在Python中实现抽象方法最简单地方式是:

任何继承自_Pizza的类必须覆盖实现方法get_radius,否则会抛出异常。

这种抽象方法的实现有它的弊端,如果你写一个类继承Pizza,但是忘记实现get_radius,异常只有在你真正使用的时候才会抛出来。

还有一种方式可以让错误更早的触发,使用Python提供的abc模块,对象被初始化之后就可以抛出异常:

使用abc后,当你尝试初始化BasePizza或者任何子类的时候立马就会得到一个TypeError,而无需等到真正调用get_radius的时候才发现异常。

混合静态方法、类方法、抽象方法

当你开始构建类和继承结构时,混合使用这些装饰器的时候到了,所以这里列出了一些技巧。

记住,声明一个抽象的方法,不会固定方法的原型,这就意味着虽然你必须实现它,但是我可以用任何参数列表来实现:

这样是允许的,因为Calzone满足BasePizza对象所定义的接口需求。同样我们也可以用一个类方法或静态方法来实现:

这同样是正确的,因为它遵循抽象类BasePizza设定的契约。事实上get_ingredients方法并不需要知道返回结果是什么,结果是实现细节,不是契约条件。

因此,你不能强制抽象方法的实现是一个常规方法、或者是类方法还是静态方法,也没什么可争论的。从Python3开始(在Python2中不能如你期待的运行,见issue5867),在abstractmethod方法上面使用@staticmethod@classmethod装饰器成为可能。

别误会了,如果你认为它会强制子类作为一个类方法来实现get_ingredients那你就错了,它仅仅表示你实现的get_ingredientsBasePizza中是一个类方法。

可以在抽象方法中做代码的实现?没错,Python与Java接口中的方法相反,你可以在抽象方法编写实现代码通过super()来调用它。(译注:在Java8中,接口也提供的默认方法,允许在接口中写方法的实现)

这个例子中,你构建的每个pizza都通过继承BasePizza的方式,你不得不覆盖get_ingredients方法,但是能够使用默认机制通过super()来获取ingredient列表。

打赏支持我翻译更多好文章,谢谢!

打赏译者

打赏支持我翻译更多好文章,谢谢!

4 14 收藏 10 评论

关于作者:刘志军

个人博客:http://foofish.net,微信公众号:Python之禅(ID:VTtalk) 个人主页 · 我的文章 · 43 ·   

相关文章

可能感兴趣的话题



直接登录
最新评论
  • er3456qi 学生 2015/06/23

    问个问题,在类里可以定义没有装饰器,也没有self的函数,这是哪一类?

    • 刘志军 Engineer 2015/06/23

      我貌似至今没有见过这一类,你在哪里碰到过?

      • er3456qi 学生 2015/06/23

        首先说,这一类确实是合法的。我是在think python那本书中看到的,书里只说了下这是类的方法。我以为是装饰器classmethod的简写,但是后来测试发现和装饰器的方式有区别。我一直试图弄明白,但是都没找到资料,所以一直搞不懂。另外,我用的是python3。

        • 刘志军 Engineer 2015/06/24

          你有运行过书上那段代码吗,试试能否正常执行?即使是Python3我也没看到那样的写法

          • er3456qi 学生 2015/06/24

            运行过啊,你也可以写一段自己试试。可以被类正常调用,但是不能被类对象调用。

      • er3456qi 学生 2015/06/23

        对了,再补充一点,这种函数和静态函数有点像,在python3里,输出类型都是,但是有@staticmethod的函数可以被类对象调用,而什么都没有的这个不能被调用。

  • 刘志军 Engineer 2015/06/24

    @er3456qi
    class Hello(object):
    def foo():
    print("foo")

    @classmethod
    def bar(cls):
    print("bar")

    @staticmethod
    def foo_bar():
    print("foo_bar")

    def foo_bar2(self):
    print("foobar")

    if __name__ == "__main__":
    print(Hello.foo)
    print(Hello().foo)
    print(Hello.bar)
    print(Hello.foo_bar)
    print(Hello.foo_bar2)
    print(Hello().foo_bar2)

    输出结果:

    <bound method Hello.foo of >
    <bound method type.bar of >

    <bound method Hello.foo_bar2 of >

    Python3:定义在类中如果没有任何参数和修饰符的函数,那么它就是一个普通的函数,从打印结果可以看出,当然在Python2中这是没法正常执行的。

    的确从测试结果来看验证了原文说的那句话:“在Python3中,依附在类上的函数不再当作是未绑定的方法,而是把它当作一个简单地函数,如果有必要它会绑定到一个对象身上去,原则依然和Python2保持一致,但是模块更简洁”。

    最后你说的可以被类调用(严格说是类对象),不能被类对象调用(严格说是实例对象),这个歌就很好理解,如果是被实例对象调用的话,Python默认会把当前的实例对象(即self)当作第一个参数传递给foo函数,但是foo函数定义时不接收任何参数,因此会报错,但它是可以绑定到实例对象上去了。从第二个print语句可以看出。

    • er3456qi 学生 2015/06/26

      你这个例子我之前写过类似的做测试,我知道这个结果。不过当时我不知道这句话“在Python3中,依附在类上的函数不再当作是未绑定的方法,而是把它当作一个简单地函数,如果有必要它会绑定到一个对象身上去,原则依然和Python2保持一致,但是模块更简洁”。而且,python2我没学过,所以也不是特别了解。

      不过现在的疑问是,这个函数到底有啥用?确实没见过有人用过。

  • gnlm_admin 程序员 2016/04/11

    不错噢

  • 谢谢UP  很棒!  受益很大

跳到底部
返回顶部