Python权威指南之如何使用静态类或抽象函数 分类: python学习 2015-05-12 18:18 57人阅读 评论(0) 收藏

来源:http://www.xdarui.com/archives/261.html
html



原文http://julien.danjou.info/blog/2013/guide-python-static-class-abstract-methodspython

代码审查对发现人们很难理解的问题来讲绝对是一种很赞的方式。我(Julien Danjou)最近在作OpenStack patches校对的时候发现人们不可以正确的理解与使用Python提供的不一样的函数修饰。因此我这里提供一个连接以便下次review的时候帖出。ide

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的实例中去,而方法的第一个参数但愿接收一个实例。(Python2中实例必需是对应的那个类,而Python3中能够是任何东西)让我这样试试:
>>> Pizza.get_size(Pizza(42))
42
能够运行了!咱们在调用的时候给第一个参数传入了它的实例,因此一切都正常了。可是你不得不诚认,这样调用不是很方便。每次调用这个方法的时候咱们都得引用一个类,并且咱们也不知道这个对象是属于哪一个类,这样确定是否是长久之计。

因此Python帮咱们作了这些事情,给全部Pizza的实例绑定全部它的方法。这意味着get_sizePizza实例的已绑定方法:方法的第一个参数是实例它自己。ui

>>> Pizza(42).get_size
<bound method Pizza.get_size of <__main__.Pizza object at 0x7f3138827910>>
>>> Pizza(42).get_size()
42
正如咱们所指望,咱们必没有给 get_size()方法传入任何参数,由于它已经绑定了, Pizza实例自身会自动设置成方法的第一个参数。这能够证实:
>>> m = Pizza(42).get_size
>>> m()
42
事实上,你甚至都不须要为你的 Pizza对象保存引用,因其方法与对象绑定了,因此其方法自己就够了。可是若是咱们想知道这个方法倒底绑定到了哪一个对象上该怎么作?这有一个小技巧:
>>> m = Pizza(42).get_size
>>> m.__self__
<__main__.Pizza object at 0x7f3138827910>
>>> # You could guess, look at this:
...
>>> m == m.__self__.get_size
True
显然,我仍有一个咱们这个对象的引用,咱们随时能够经过它找回。

在Python3中,直接使用类中的方法再也不视为未绑定方法,而是一个简单的函数,而只是在须要的时候才绑定到对象上。因此基本原则没变只是简化了模型。this

>>> 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 0x7f307f984dd0>

静态方法

静态方法是方法中的一个特例。有时,你写了一段属于某个类的代码,可是它并未使用这个类。好比:编码

class Pizza(object):
    @staticmethod
    def mix_ingredients(x, y):
        return x + y
 
    def cook(self):
        return self.mix_ingredients(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
    

类方法

说到这,倒底什么是类方法?类方法是指不是绑定在对象上而是类上的方法!。3d

>>> 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
True
>>> Pizza.get_radius()
42
不管你是如何使用类方法,它始终都是链接到类,并且第一个参数是类自己(记住类也是对象)。咱们什么时候使用这种方法?类方法大多有这两种用法:
  • 工厂方法,用于建立一个类的实例好比一些预处理。若是咱们用@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_circumference(radius):
             return math.pi * (radius ** 2)
     
        @classmethod
        def compute_volume(cls, height, radius):
             return height * cls.compute_circumference(radius)
     
        def get_volume(self):
            return self.compute_volume(self.height, self.radius)
    

      抽象方法

      抽象方法是基类中定义的方法,但却没有任何实现。在Java中,能够把方法申明成一个接口。而在Python中实现一个抽象方法的最简便方法是:code

      class Pizza(object):
          def get_radius(self):
              raise NotImplementedError
      
      任何从 Pizza继承下来的子类都必需实现 get_radius方法,不然就会产生一个错误。这种方式实如今抽象方法也有缺点,若是你写了一个类继承自 Pizza但却忘了实现 get_radius时,只有当你用到了那个方法时才会抛错。
      >>> Pizza()
      <__main__.Pizza object at 0x7fb747353d90>
      >>> Pizza().get_radius()
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "<stdin>", line 3, in get_radius
      NotImplementedError
      
      这里有一个简单的办法能够在类被实例化后触发它,使用Python提供的 abc模块。
      import 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
      

      混搭使用静态、类以及抽象方法

      当建立类及继承时,你就得使用混搭这些方式的方法了。这里一些提示。htm

      请记住,申明类时应先申明成抽象类,而且不要把类的原型写死。这意味着,抽象方法必需得实现,但我能够在实现抽象方法时能够任意指定参数列表。

      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方法返回结果时无需关心对象,不用关心有何约束。所以,你不能强制要求抽象方法或类的实现是有规律的,或者说不该该这样作。从Python3开始(Python2不会生效,见 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()经过默认机制获取配料表。
相关文章
相关标签/搜索