>>classPizza(object):...def__init__(self,size):...self.size=size...defget_size(self):...returnself.size...>>>Pizza.get_sizePython在告訴你,屬性_get_size是類(lèi)Pizza的一個(gè)未綁定方法。這是什么意思呢?很快我們" />

日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

深入理解Python中各種方法的運(yùn)作原理

系統(tǒng) 1692 0

方法在Python中是如何工作的

方法就是一個(gè)函數(shù),它作為一個(gè)類(lèi)屬性而存在,你可以用如下方式來(lái)聲明、訪問(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在告訴你,屬性_get_size是類(lèi)Pizza的一個(gè)未綁定方法。這是什么意思呢?很快我們就會(huì)知道答案:
?

            
>>> 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類(lèi)的任何實(shí)例上,它需要一個(gè)實(shí)例作為第一個(gè)參數(shù)傳遞進(jìn)去(Python2必須是該類(lèi)的實(shí)例,Python3中可以是任何東西),嘗試一下:
?

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

          

太棒了,現(xiàn)在用一個(gè)實(shí)例作為它的的第一個(gè)參數(shù)來(lái)調(diào)用,整個(gè)世界都清靜了,如果我說(shuō)這種調(diào)用方式還不是最方便的,你也會(huì)這么認(rèn)為的;沒(méi)錯(cuò),現(xiàn)在每次調(diào)用這個(gè)方法的時(shí)候我們都不得不引用這個(gè)類(lèi),如果不知道哪個(gè)類(lèi)是我們的對(duì)象,長(zhǎng)期看來(lái)這種方式是行不通的。

那么Python為我們做了什么呢,它綁定了所有來(lái)自類(lèi)_Pizza的方法以及該類(lèi)的任何一個(gè)實(shí)例的方法。也就意味著現(xiàn)在屬性get_size是Pizza的一個(gè)實(shí)例對(duì)象的綁定方法,這個(gè)方法的第一個(gè)參數(shù)就是該實(shí)例本身。
?

            
>>> Pizza(42).get_size

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

            
          

和我們預(yù)期的一樣,現(xiàn)在不再需要提供任何參數(shù)給_get_size,因?yàn)樗呀?jīng)是綁定的,它的self參數(shù)會(huì)自動(dòng)地設(shè)置給Pizza實(shí)例,下面代碼是最好的證明:
?

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

          

更有甚者,你都沒(méi)必要使用持有Pizza對(duì)象的引用了,因?yàn)樵摲椒ㄒ呀?jīng)綁定到了這個(gè)對(duì)象,所以這個(gè)方法對(duì)它自己來(lái)說(shuō)是已經(jīng)足夠了。

也許,如果你想知道這個(gè)綁定的方法是綁定在哪個(gè)對(duì)象上,下面這種手段就能得知:
?

            
>>> m = Pizza(42).get_size
>>> m.__self__
<__main__.Pizza object at 0x7f3138827910>
>>> 
# You could guess, look at this:
...
>>> m == m.__self__.get_size
True

          

顯然,該對(duì)象仍然有一個(gè)引用存在,只要你愿意你還是可以把它找回來(lái)。

在Python3中,依附在類(lèi)上的函數(shù)不再當(dāng)作是未綁定的方法,而是把它當(dāng)作一個(gè)簡(jiǎn)單地函數(shù),如果有必要它會(huì)綁定到一個(gè)對(duì)象身上去,原則依然和Python2保持一致,但是模塊更簡(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)方法是一類(lèi)特殊的方法,有時(shí)你可能需要寫(xiě)一個(gè)屬于這個(gè)類(lèi)的方法,但是這些代碼完全不會(huì)使用到實(shí)例對(duì)象本身,例如:
?

            
class Pizza(object):
  @staticmethod
  def mix_ingredients(x, y):
    return x + y
 
  def cook(self):
    return self.mix_ingredients(self.cheese, self.vegetables)

          

這個(gè)例子中,如果把_mix_ingredients作為非靜態(tài)方法同樣可以運(yùn)行,但是它要提供self參數(shù),而這個(gè)參數(shù)在方法中根本不會(huì)被使用到。這里的@staticmethod裝飾器可以給我們帶來(lái)一些好處:

??? Python不再需要為Pizza對(duì)象實(shí)例初始化一個(gè)綁定方法,綁定方法同樣是對(duì)象,但是創(chuàng)建他們需要成本,而靜態(tài)方法就可以避免這些。
?

            
>>> Pizza().cook is Pizza().cook
False
>>> Pizza().mix_ingredients is Pizza.mix_ingredients
True
>>> Pizza().mix_ingredients is Pizza().mix_ingredients
True

          

??? 可讀性更好的代碼,看到@staticmethod我們就知道這個(gè)方法并不需要依賴(lài)對(duì)象本身的狀態(tài)。
??? 可以在子類(lèi)中被覆蓋,如果是把mix_ingredients作為模塊的頂層函數(shù),那么繼承自Pizza的子類(lèi)就沒(méi)法改變pizza的mix_ingredients了如果不覆蓋cook的話(huà)。

類(lèi)方法

話(huà)雖如此,什么是類(lèi)方法呢?類(lèi)方法不是綁定到對(duì)象上,而是綁定在類(lèi)上的方法。
?

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

                  
                
              
            
          

無(wú)論你用哪種方式訪問(wèn)這個(gè)方法,它總是綁定到了這個(gè)類(lèi)身上,它的第一個(gè)參數(shù)是這個(gè)類(lèi)本身(記住:類(lèi)也是對(duì)象)。

什么時(shí)候使用這種方法呢?類(lèi)方法通常在以下兩種場(chǎng)景是非常有用的:

??? 工廠方法:它用于創(chuàng)建類(lèi)的實(shí)例,例如一些預(yù)處理。如果使用@staticmethod代替,那我們不得不硬編碼Pizza類(lèi)名在函數(shù)中,這使得任何繼承Pizza的類(lèi)都不能使用我們這個(gè)工廠方法給它自己用。
?

            
class Pizza(object):
  def __init__(self, ingredients):
    self.ingredients = ingredients
 
  @classmethod
  def from_fridge(cls, fridge):
    return cls(fridge.get_cheese() + fridge.get_vegetables())

          

??? 調(diào)用靜態(tài)類(lèi):如果你把一個(gè)靜態(tài)方法拆分成多個(gè)靜態(tài)方法,除非你使用類(lèi)方法,否則你還是得硬編碼類(lèi)名。使用這種方式聲明方法,Pizza類(lèi)名明永遠(yuǎn)都不會(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)

          

抽象方法

抽象方法是定義在基類(lèi)中的一種方法,它沒(méi)有提供任何實(shí)現(xiàn),類(lèi)似于Java中接口(Interface)里面的方法。

在Python中實(shí)現(xiàn)抽象方法最簡(jiǎn)單地方式是:
?

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

          

任何繼承自_Pizza的類(lèi)必須覆蓋實(shí)現(xiàn)方法get_radius,否則會(huì)拋出異常。

這種抽象方法的實(shí)現(xiàn)有它的弊端,如果你寫(xiě)一個(gè)類(lèi)繼承Pizza,但是忘記實(shí)現(xiàn)get_radius,異常只有在你真正使用的時(shí)候才會(huì)拋出來(lái)。
?

            
>>> Pizza()
<__main__.Pizza object at 0x7fb747353d90>
>>> Pizza().get_radius()
Traceback (most recent call last):
 File "
            
              ", line 1, in 
              
                
 File "
                
                  ", line 3, in get_radius
NotImplementedError

                
              
            
          

還有一種方式可以讓錯(cuò)誤更早的觸發(fā),使用Python提供的abc模塊,對(duì)象被初始化之后就可以?huà)伋霎惓#?
?

            
import abc
 
class BasePizza(object):
  __metaclass__ = abc.ABCMeta
 
  @abc.abstractmethod
  def get_radius(self):
     
"""Method that should do something."""

          

使用abc后,當(dāng)你嘗試初始化BasePizza或者任何子類(lèi)的時(shí)候立馬就會(huì)得到一個(gè)TypeError,而無(wú)需等到真正調(diào)用get_radius的時(shí)候才發(fā)現(xiàn)異常。
?

            
>>> 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)你開(kāi)始構(gòu)建類(lèi)和繼承結(jié)構(gòu)時(shí),混合使用這些裝飾器的時(shí)候到了,所以這里列出了一些技巧。

記住,聲明一個(gè)抽象的方法,不會(huì)固定方法的原型,這就意味著雖然你必須實(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滿(mǎn)足BasePizza對(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

          

這同樣是正確的,因?yàn)樗裱橄箢?lèi)BasePizza設(shè)定的契約。事實(shí)上get_ingredients方法并不需要知道返回結(jié)果是什么,結(jié)果是實(shí)現(xiàn)細(xì)節(jié),不是契約條件。

因此,你不能強(qiáng)制抽象方法的實(shí)現(xiàn)是一個(gè)常規(guī)方法、或者是類(lèi)方法還是靜態(tài)方法,也沒(méi)什么可爭(zhēng)論的。從Python3開(kāi)始(在Python2中不能如你期待的運(yùn)行,見(jiàn)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

          

別誤會(huì)了,如果你認(rèn)為它會(huì)強(qiáng)制子類(lèi)作為一個(gè)類(lèi)方法來(lái)實(shí)現(xiàn)get_ingredients那你就錯(cuò)了,它僅僅表示你實(shí)現(xiàn)的get_ingredients在BasePizza中是一個(gè)類(lèi)方法。

可以在抽象方法中做代碼的實(shí)現(xiàn)?沒(méi)錯(cuò),Python與Java接口中的方法相反,你可以在抽象方法編寫(xiě)實(shí)現(xiàn)代碼通過(guò)super()來(lái)調(diào)用它。(譯注:在Java8中,接口也提供的默認(rèn)方法,允許在接口中寫(xiě)方法的實(shí)現(xiàn))
?

            
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è)例子中,你構(gòu)建的每個(gè)pizza都通過(guò)繼承BasePizza的方式,你不得不覆蓋get_ingredients方法,但是能夠使用默認(rèn)機(jī)制通過(guò)super()來(lái)獲取ingredient列表。


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 临湘市| 吉林省| 金坛市| 璧山县| 柳州市| 出国| 常宁市| 苏尼特左旗| 北安市| 朝阳区| 礼泉县| 高陵县| 宝兴县| 霞浦县| 海门市| 宜州市| 巢湖市| 东城区| 万年县| 白银市| 班玛县| 章丘市| 汝城县| 康马县| 长子县| 平江县| 焦作市| 正安县| 蓝田县| 贵州省| 随州市| 华宁县| 碌曲县| 图木舒克市| 盐源县| 监利县| 修水县| 赤峰市| 齐齐哈尔市| 酉阳| 潼关县|