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