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()經過默認機制獲取配料表。
相關文章
相關標籤/搜索