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

Python中方法的工做方式
方法是存儲在類屬性中的函數,你能夠用下面這種方式聲明和訪問一個函數html

>>> 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是unbound(未綁定的),這表明什麼含義?咱們調用一下就明白了:python

>>> 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)

咱們沒法調用它(get_size),由於它沒有綁定到Pizza的任何實例上,並且一個方法須要一個實例做爲它的第一個參數(Python2中必須是類的實例,Python3沒有這個強制要求),讓咱們試一下:函數

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

咱們使用一個實例做爲這個方法的第一個參數來調用它,沒有出現任何問題。可是若是我說這不是一個方便的調用方法的方式,你將會贊成個人觀點。咱們每次調用方法都要涉及(這裏我理解是引用)類this

來看Python打算爲咱們作些什麼,就是它從Pizza類中綁定全部的方法到這個類的任何實例上。意思就是Pizza實例化後get_size這個屬性是一個綁定方法,方法的第一個參數會是實例對象本身編碼

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

意料之中,咱們不須要爲get_size傳任何參數,自從被綁定後,它的self參數會自動設置爲Pizza實例,下面是一個更明顯的例子:3d

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

事實上是,你甚至不須要對Pizza引用,由於這個方法已經綁定到了這個對象code

若是你想知道這個綁定方法綁定到了哪個對象,這裏有個快捷的方法:htm

>>> 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中,類中的函數再也不被認爲是未綁定的方法(應該是做爲函數存在),若是須要,會做爲一個函數綁定到對象上,因此原理是同樣的(和Python2),只是模型被簡化了blog

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

簡化了代碼的可讀性,看到@staticmethod咱們就會知道這個方法不會依賴這個對象的狀態(一國兩制,高度自治)
容許在子類中重寫mix_ingredients方法。若是咱們在頂級模型中定義了mix_ingredients函數,繼承自Pizza的類除了重寫,不然沒法改變mix_ingredients的功能

類方法
什麼是類方法,類方法是方法不會被綁定到一個對象,而是被綁定到一個類中

>>> 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 == Pizza().get_radius
    True
    >>> Pizza.get_radius()
    42

不管以何種方式訪問這個方法,它都會被綁定到類中,它的第一個參數必須是類自己(記住類也是對象)

何時使用類方法,類方法在如下兩種場合會有很好的效果:

一、工廠方法,爲類建立實例,例如某種程度的預處理。若是咱們使用@staticmethod代替,咱們必需要在代碼中硬編碼Pizza(寫死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)

抽象方法
抽象方法是定義在基類中的,能夠是不提供任何功能代碼的方法
在Python中簡單的寫抽象方法的方式是:

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

繼承自Pizza的類都必需要實現並重寫get_redius,不然就會報錯

這種方式的抽象方法有一個問題,若是你忘記實現了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或者繼承它,都會獲得TypeError錯誤

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

備註:我使用Python3.6實現的代碼  

In [8]: import abc
       ...:
       ...: class BasePizza(abc.ABC):
       ...:
       ...:     @abc.abstractmethod
       ...:     def get_radius(self):
       ...:          """:return"""
       ...:

    In [9]: BasePizza()
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-9-70b53ea21e68> in <module>()
    ----> 1 BasePizza()

    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_ingredioents方法不須要知道對象返回的結果,
所以,你不須要強制抽象方法實現成爲常規方法、類或者靜態方法。在python3中,能夠將@staticmethod和@classmethod裝飾器放在@abstractmethod上面

import abc

        class BasePizza(object):
            __metaclass__  = abc.ABCMeta

            ingredient = ['cheese']

            @classmethod
            @abc.abstractmethod
            def get_ingredients(cls):
                 """Returns the ingredient list."""
                 return cls.ingredients

和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()來獲取default_ingredients

文章來源:https://www.cnblogs.com/flash...
推薦閱讀:https://www.roncoo.com/course...

相關文章
相關標籤/搜索