码迷,mamicode.com
首页 > 编程语言 > 详细

关于Python中如何使用静态、类、抽象方法的权威指南(译)

时间:2015-06-29 14:38:21      阅读:272      评论:0      收藏:0      [点我收藏+]

标签:

      对于Python中静态、类、抽象方法的使用,我是一直很迷糊的。最近看到一篇技术文章对这方面解释的很好,在此翻译一下,加深印象,也为有需要的同学提供一个方便。

      Python中方法是如何工作的:

      方法即函数,作为一个类的属性存储。你能像如下申明和访问一个函数:

>>> class Pizza(object):
...     def __init__(self,size):
...             self.size = size
...     def get_size(self):
...             return self.size
...
>>> Pizza.get_size
<unbound method Pizza.get_size>

      Python在这里告诉我们,Pizza类的get_size属性的访问时没有绑定。这是什么意思呢?我们马上就会知道只要我们继续调用它一下:

>>> Pizza.get_size()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method get_size() must be called with Pizza instance as first
 argument (got nothing instead)

      我们不能调用它,是因为它没有绑定到任何Pizza的实例。方法需要一个实例作为它的第一个参数(在Python 2中它必须是该类的一个实例,在Python 3中它可以是任何实例),让我们试一下:

>>> Pizza.get_size(Pizza(42))
42

      它工作了!我们调用这个方法时,把一个实例作为它的第一个参数,这样就一切正常了。但是你会认同我的观点:这并不是一个方便的方式来调用方法。我们每次想要调用方法的时候都要引用类。如果我们并不知道哪个类使我们的对象,在很长时间内这中方式是行不通的。

      因此,Python为我们做了绑定Pizza类的所有方法到该类的任意实例上。这就意味着Pizza类的实例的get_size属性是一个绑定方法:该方法的第一个参数就是实例本身:

>>> Pizza(42).get_size
<bound method Pizza.get_size of <__main__.Pizza object at 0x00000000025B3E48>>
>>> Pizza(42).get_size()
42

      意料之中,我们不再需要为get_size提供任何参数了,因为它是绑定的,它的self参数自动设置为我们的Pizza实例。这里有一个更好的证明:

>>> m = Pizza(42).get_size
>>> m()
42

      事实上,你甚至不必维持一个到你Pizza对象的引用。它的方法被绑定到对象,所以该方法对自己而言已经足够了。

      但是,如果你想知道这个绑定方法绑定的到底是哪个对象?这里有一个小窍门:

>>> m = Pizza(42).get_size
>>> m.__self__
<__main__.Pizza object at 0x0000000002A95CF8>
>>>
>>> m == m.__self__.get_size
True

      显然,我们依然有一个到对象的引用,如果有需要可以找回来。

      在Python 3中,附加到类的方法不再视为绑定方法了,仅作为简单函数。如果有需要他们绑定到一个对象。原理依然保持不变,但是模型简化了。

>>> class Pizza(object):
...     def __init__(self,size):
...             self.size = size
...     def get_size(self):
...             return self.size
...
>>> Pizza.get_size
<function Pizza.get_size at 0x0000000002907268>

      静态方法:

      静态方法是方法的一种特殊情况。有时候,你需要编写属于某个类的代码,但是从不使用对象本身。例如:

>>> class Pizza(object):
...     @staticmethod
...     def mix_ingredients(x,y):
...             return x+y
...     def cook(self):
...             return self.mix_ingredient(self.cheese,self.vegetables)
...

      在这种情况,将mix_ingredients作为非静态函数也能工作,但是必须提供一个self参数(不会被用到)。在这里,装饰器@staticmethod为我们提供了几件事情:

  •       Python没有实例化我们实例化的Pizza对象的绑定函数。绑定函数也是对象,创造它们是有开销的。使用静态函数可以避免这些:
>>> Pizza().cook is Pizza().cook
False
>>> Pizza().mix_ingredients is Pizza.mix_ingredients
True
>>> Pizza().mix_ingredients is Pizza().mix_ingredients
True
  •       简化了代码的可读性:看到@staticmethod,我们知道,该方法不依赖对象本身的状态;
  •       它允许我们在子类中重载mix_ingredients方法。如果使用的一个定义在我们模块最顶层的mix_ingredients函数,继承自Pizza的类在没有重载cook本身的情况下,不能改变我们用于混合pizza的成分。

 

      类方法:

      说了这么多,那么什么是类方法?类方法是不绑定到对象但是绑定到类的方法。(注意我下面标红的部分,与原文有出入,我在Python 2.7.9和Python 3.4.3下运行得到的都是False)

>>> class Pizza(object):
...     radius = 42
...     @classmethod
...     def get_radius(cls):
...             return cls.radius
...
>>> Pizza.get_radius
<bound method type.get_radius of <class__main__.Pizza‘>>
>>> Pizza().get_radius
<bound method type.get_radius of <class__main__.Pizza‘>>
>>> Pizza.get_radius is Pizza().get_radius

False

>>> Pizza.get_radius()
42

      不管你使用什么方式来访问这个方法,它总是绑定于它依附的类,而且它的第一个参数是类本身(记住类也是对象)。

      那么,什么时候时候这种类型的方法呢?class方法常用于一下两种类型的方法中:

  •       工厂方法,即用于创建一个类的实例用于某种预处理。如果我们使用@staticmethod代替,我们将不得不把Pizza类的名字硬编码到我们的函数中。这样使得继承自Pizza的类都无法使用我们的工厂供自己使用。
>>> class Pizza(object):
...     def __init__(self, ingredients):
...         self.ingredients = ingredients
...
...     @classmethod
...     def from_fridge(cls, fridge):
...         return cls(fridge.get_cheese() + fridge.get_vegetables())
...
  •       静态方法调用静态方法:如果你把静态方法拆分到几个静态方法中,你不应该使用硬编码而使用类方法。使用这种方法申明我们的方法,Pizza名字永远不会被引用和继承并且方法重载会工作的很好。
>>> class Pizza(object):
...     def __init__(self, radius, height):
...         self.radius = radius
...         self.height = height
...
...     @staticmethod
...     def compute_area(radius):
...          return math.pi * (radius ** 2)
...
...     @classmethod
...     def compute_volume(cls, height, radius):
...          return height * cls.compute_area(radius)
...
...     def get_volume(self):
...         return self.compute_volume(self.height, self.radius)
...

      抽象方法:

      抽象方法定义在一个基类中,但是可能没有提供任何实现。在Java中,这种方法被描述为接口。

      在Python中最简单的写一个抽象方法的方式如下:

class Pizza(object):
    def get_radius(self):
        raise NotImplementedError

      任何其他继承自Pizza的类应该实现并且重载get_radius方法。否则一个异常将会抛出。

      这种特殊的实现抽闲方法的方式有一个缺点。如果你写一个继承自Pizza的类并且忘记实现get_radius了,错误仅在你打算试用这个方法的时候抛出。

>>> Pizza()
<__main__.Pizza object at 0x0000000002B9C208>
>>> Pizza().get_radius()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in get_radius
NotImplementedError

      有一种方法可以早点触发这种方式,当对象被实例化之后,使用Python提供的abc模块。

>>>
... class BasePizza(object):
...     __metaclass__  = abc.ABCMeta
...
...     @abc.abstractmethod
...     def get_radius(self):
...          """Method that should do something."""
...

      利用abc和它特殊的类,只要你尝试实例化BasePizza或者任意继承自它的类,你都将得到一个类型错误。

>>> BasePizza()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can‘t instantiate abstract class BasePizza with abstract methods get_
radius

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

      当构建类和继承的时候,你需要混合使用这些方式装饰的时候一定会到来,在这里有关于它的一些技巧。

      请记住声明方法是抽象的,不会冻结该方法的原型。这就意味着,它必须被实现,但是我能用任意参数列表来实现。

import abc
 
class BasePizza(object):
    __metaclass__  = abc.ABCMeta
 
    @abc.abstractmethod
    def get_ingredients(self):
         """Returns the ingredient list."""
 
class Calzone(BasePizza):
    def get_ingredients(self, with_egg=False):
        egg = Egg() if with_egg else None
        return self.ingredients + egg

      这是有效的,因为Calzone满足我们在BasePizza对象中定义的接口要求。这意味着我们也能作为一个类或者静态方法来实现它。例如:

import abc
 
class BasePizza(object):
    __metaclass__  = abc.ABCMeta
 
    @abc.abstractmethod
    def get_ingredients(self):
         """Returns the ingredient list."""
 
class DietPizza(BasePizza):
    @staticmethod
    def get_ingredients():
        return None

      这也是正确的,符合我们与抽闲BasePizza类的合约。事实上,该get_ingredients方法并不需要知道返回结果的对象其实是一个实现细节,不是一个让我们合约履行的标准。

      因此,你不能强迫你的抽象方法的实现是一个普通的或者类或者静态方法。从Python 3(这在Python 2是行不通的,参照issue5867)开始,它现在可以在@abstractmethod的顶部使用@staticmethod@classmethod装饰符。

import abc
 
class BasePizza(object):
    __metaclass__  = abc.ABCMeta
 
    ingredient = [‘cheese‘]
 
    @classmethod
    @abc.abstractmethod
    def get_ingredients(cls):
         """Returns the ingredient list."""
         return cls.ingredients

      不要误读:如果你觉得这会迫使你的子类把get_ingredients实现为一个类的函数那就错了。这只是意味着你在BasePizza类中实现的get_ingredients是一个类方法。

      在一个抽象方法中的实现?是的,在Python中,与Java接口相反,你能在抽象方法中编码并且使用super()调用它:

import abc
 
class BasePizza(object):
    __metaclass__  = abc.ABCMeta
 
    default_ingredients = [‘cheese‘]
 
    @classmethod
    @abc.abstractmethod
    def get_ingredients(cls):
         """Returns the ingredient list."""
         return cls.default_ingredients
 
class DietPizza(BasePizza):
    def get_ingredients(self):
        return [‘egg‘] + super(DietPizza, self).get_ingredients()

      在这种情况下,你建立的每一个继承自BasePizza的pizza都不得不重载get_ingredients方法,但可以使用默认的机制,通过使用super()来获取成分列表。

      原文地址:https://julien.danjou.info/blog/2013/guide-python-static-class-abstract-methods

关于Python中如何使用静态、类、抽象方法的权威指南(译)

标签:

原文地址:http://www.cnblogs.com/hiccup/p/4607216.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!