python中的静态方法如何调用_关于Python中如何使用静态、类、抽象方法的权威指南(译)...
對于Python中靜態(tài)、類、抽象方法的使用,我是一直很迷糊的。最近看到一篇技術(shù)文章對這方面解釋的很好,在此翻譯一下,加深印象,也為有需要的同學提供一個方便。
Python中方法是如何工作的:
方法即函數(shù),作為一個類的屬性存儲。你能像如下申明和訪問一個函數(shù):
>>> class Pizza(object):
... def __init__(self,size):
... self.size = size
... def get_size(self):
... return self.size
...
>>> Pizza.get_size
Python在這里告訴我們,Pizza類的get_size屬性的訪問時沒有綁定。這是什么意思呢?我們馬上就會知道只要我們繼續(xù)調(diào)用它一下:
>>> Pizza.get_size()
Traceback (most recent call last):
File "", line 1, in
TypeError: unbound method get_size() must be called with Pizza instance as first
argument (got nothing instead)
我們不能調(diào)用它,是因為它沒有綁定到任何Pizza的實例。方法需要一個實例作為它的第一個參數(shù)(在Python 2中它必須是該類的一個實例,在Python 3中它可以是任何實例),讓我們試一下:
>>> Pizza.get_size(Pizza(42))
42
它工作了!我們調(diào)用這個方法時,把一個實例作為它的第一個參數(shù),這樣就一切正常了。但是你會認同我的觀點:這并不是一個方便的方式來調(diào)用方法。我們每次想要調(diào)用方法的時候都要引用類。如果我們并不知道哪個類使我們的對象,在很長時間內(nèi)這中方式是行不通的。
因此,Python為我們做了綁定Pizza類的所有方法到該類的任意實例上。這就意味著Pizza類的實例的get_size屬性是一個綁定方法:該方法的第一個參數(shù)就是實例本身:
>>> Pizza(42).get_size
>
>>> Pizza(42).get_size()
42
意料之中,我們不再需要為get_size提供任何參數(shù)了,因為它是綁定的,它的self參數(shù)自動設置為我們的Pizza實例。這里有一個更好的證明:
>>> m = Pizza(42).get_size
>>> m()
42
事實上,你甚至不必維持一個到你Pizza對象的引用。它的方法被綁定到對象,所以該方法對自己而言已經(jīng)足夠了。
但是,如果你想知道這個綁定方法綁定的到底是哪個對象?這里有一個小竅門:
>>> m = Pizza(42).get_size
>>> m.__self__
>>>
>>> m == m.__self__.get_size
True
顯然,我們依然有一個到對象的引用,如果有需要可以找回來。
在Python 3中,附加到類的方法不再視為綁定方法了,僅作為簡單函數(shù)。如果有需要他們綁定到一個對象。原理依然保持不變,但是模型簡化了。
>>> class Pizza(object):
... def __init__(self,size):
... self.size = size
... def get_size(self):
... return self.size
...
>>> Pizza.get_size
靜態(tài)方法:
靜態(tài)方法是方法的一種特殊情況。有時候,你需要編寫屬于某個類的代碼,但是從不使用對象本身。例如:
>>> 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作為非靜態(tài)函數(shù)也能工作,但是必須提供一個self參數(shù)(不會被用到)。在這里,裝飾器@staticmethod為我們提供了幾件事情:
Python沒有實例化我們實例化的Pizza對象的綁定函數(shù)。綁定函數(shù)也是對象,創(chuàng)造它們是有開銷的。使用靜態(tài)函數(shù)可以避免這些:
>>> Pizza().cook is Pizza().cook
False
>>> Pizza().mix_ingredients is Pizza.mix_ingredients
True
>>> Pizza().mix_ingredients is Pizza().mix_ingredients
True
簡化了代碼的可讀性:看到@staticmethod,我們知道,該方法不依賴對象本身的狀態(tài);
它允許我們在子類中重載mix_ingredients方法。如果使用的一個定義在我們模塊最頂層的mix_ingredients函數(shù),繼承自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
>
>>> Pizza().get_radius
>
>>> Pizza.get_radius is Pizza().get_radius
False
>>> Pizza.get_radius()
42
不管你使用什么方式來訪問這個方法,它總是綁定于它依附的類,而且它的第一個參數(shù)是類本身(記住類也是對象)。
那么,什么時候時候這種類型的方法呢?class方法常用于一下兩種類型的方法中:
工廠方法,即用于創(chuàng)建一個類的實例用于某種預處理。如果我們使用@staticmethod代替,我們將不得不把Pizza類的名字硬編碼到我們的函數(shù)中。這樣使得繼承自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())
...
靜態(tài)方法調(diào)用靜態(tài)方法:如果你把靜態(tài)方法拆分到幾個靜態(tài)方法中,你不應該使用硬編碼而使用類方法。使用這種方法申明我們的方法,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)
...
抽象方法:
抽象方法定義在一個基類中,但是可能沒有提供任何實現(xiàn)。在Java中,這種方法被描述為接口。
在Python中最簡單的寫一個抽象方法的方式如下:
class Pizza(object):
def get_radius(self):
raise NotImplementedError
任何其他繼承自Pizza的類應該實現(xiàn)并且重載get_radius方法。否則一個異常將會拋出。
這種特殊的實現(xiàn)抽閑方法的方式有一個缺點。如果你寫一個繼承自Pizza的類并且忘記實現(xiàn)get_radius了,錯誤僅在你打算試用這個方法的時候拋出。
>>> Pizza()
>>> Pizza().get_radius()
Traceback (most recent call last):
File "", line 1, in
File "", line 3, in get_radius
NotImplementedError
有一種方法可以早點觸發(fā)這種方式,當對象被實例化之后,使用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 "", line 1, in
TypeError: Can't instantiate abstract class BasePizza with abstract methods get_
radius
混合靜態(tài)、類和抽象方法:
當構(gòu)建類和繼承的時候,你需要混合使用這些方式裝飾的時候一定會到來,在這里有關(guān)于它的一些技巧。
請記住聲明方法是抽象的,不會凍結(jié)該方法的原型。這就意味著,它必須被實現(xiàn),但是我能用任意參數(shù)列表來實現(xiàn)。
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對象中定義的接口要求。這意味著我們也能作為一個類或者靜態(tài)方法來實現(xiàn)它。例如:
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方法并不需要知道返回結(jié)果的對象其實是一個實現(xiàn)細節(jié),不是一個讓我們合約履行的標準。
因此,你不能強迫你的抽象方法的實現(xiàn)是一個普通的或者類或者靜態(tài)方法。從Python 3(這在Python 2是行不通的,參照issue5867)開始,它現(xiàn)在可以在@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實現(xiàn)為一個類的函數(shù)那就錯了。這只是意味著你在BasePizza類中實現(xiàn)的get_ingredients是一個類方法。
在一個抽象方法中的實現(xiàn)?是的,在Python中,與Java接口相反,你能在抽象方法中編碼并且使用super()調(diào)用它:
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()來獲取成分列表。
總結(jié)
以上是生活随笔為你收集整理的python中的静态方法如何调用_关于Python中如何使用静态、类、抽象方法的权威指南(译)...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python图例重复显示_matplot
- 下一篇: python可视化爬虫框架_8个最高效的