關於Python中如何使用靜態、類、抽象方法的權威指南(譯)

來源:互聯網
上載者:User

標籤:

      對於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屬性是一個Binder 方法:該方法的第一個參數就是執行個體本身:

>>> 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對象的引用。它的方法被綁定到對象,所以該方法對自己而言已經足夠了。

      但是,如果你想知道這個Binder 方法綁定的到底是哪個對象?這裡有一個小竅門:

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

      顯然,我們依然有一個到對象的引用,如果有需要可以找回來。

      在Python 3中,附加到類的方法不再視為Binder 方法了,僅作為簡單函數。如果有需要他們綁定到一個對象。原理依然保持不變,但是模型簡化了。

>>> 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方法常用於一下兩種類型的方法中:

  •       Factory 方法,即用於建立一個類的執行個體用於某種預先處理。如果我們使用@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中如何使用靜態、類、抽象方法的權威指南(譯)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.