方法就是一個(gè)函數(shù),它作為一個(gè)類屬性而存在,可以使用如下方式進(jìn)行聲明、訪問(wèn)一個(gè)函數(shù):
class Student(object):
def __init__(self,sex):
self.sex = sex
def get_sex(self):
return self.sex
print Student.get_sex
<unbound method Student.get_sex>
Python在告訴你,屬性get_sex是類Student的一個(gè)未綁定的方法。這是什么意思
print print Student.get_sex()
Traceback (most recent call last):
File "D:/project_11/SmartCleaner_python/home_application/__init__.py", line 13, in <module>
print Student.get_sex()
TypeError: unbound method get_sex() must be called with Student instance as first argument (got nothing instead)
這樣不能進(jìn)行調(diào)用,因?yàn)樗€沒(méi)有綁定到Student類的任何實(shí)例對(duì)象上,它需要一個(gè)實(shí)例作為第一個(gè)參數(shù)傳遞進(jìn)去(Python2必須是該類的實(shí)例,Python3中可以是任何東西),如下:
print Student.get_sex(Student('OK'))
OK
現(xiàn)在用一個(gè)實(shí)例對(duì)象作為它的第一個(gè)參數(shù)來(lái)調(diào)用,但是這種還是不最方便的,如果每次調(diào)用這個(gè)方法,不得不引用這個(gè)類,隨著代碼量的增加,如果忘記了哪個(gè)類是所需要的,會(huì)造成程序的混亂,所以長(zhǎng)期看來(lái)這種方法是行不通的。
Python綁定了所有來(lái)自類Student的方法以及該類的任何一個(gè)實(shí)例方法,也就是以為著現(xiàn)在屬性get_sex是Student的一個(gè)實(shí)例對(duì)象的綁定方法,這個(gè)方法的第一個(gè)參數(shù)就是該實(shí)例本身。
print Student('OK').get_sex
<bound method Student.get_sex of <__main__.Student object at 0x0000000003116898>
print Student('OK').get_sex()
OK
現(xiàn)在不需要提供任何參數(shù)給get_sex,因?yàn)樗呀?jīng)是綁定的,它的self參數(shù)會(huì)自動(dòng)的設(shè)置給Pizza實(shí)例。
student = Student('OK').get_sex
print student()
OK
獲取綁定的方法在哪個(gè)對(duì)象上
student = Student('OK').get_sex
print student.__self__
print student == student.__self__.get_sex
<__main__.Student object at 0x0000000003116898>
True
在Python3中,依附在類上的函數(shù)不再當(dāng)作是未綁定的方法,二十把它當(dāng)作一個(gè)簡(jiǎn)單函數(shù),如果有必要它會(huì)綁定到一個(gè)對(duì)象身上去,原則依然和Python保持一致,但是模塊更簡(jiǎn)潔:
lass Student(object):
def __init__(self,sex):
self.sex = sex
def get_sex(self):
return self.sex
print Student.get_sex
<__main__.Student object at 0x0000000003116898>
靜態(tài)方法
靜態(tài)方法是一類特殊的方法,有時(shí)可能需要寫一個(gè)屬于這個(gè)類的方法,但是這些代碼完全不會(huì)使用到實(shí)例對(duì)象本身,例如:
class Student(object):
@staticmethod
def aver_age(x, y):
return x + y
def year(self):
return self.aver_age(self.month, self.day)
這個(gè)例子中,如果把a(bǔ)ver_age作為非靜態(tài)方法同樣可以運(yùn)行,但是它要提供self參數(shù),而這個(gè)參數(shù)在方法中根本不會(huì)被使用到。這里的@staticmethod裝飾器可以給我們帶來(lái)一些好處,Python不再需要為Student對(duì)象實(shí)例初始化一個(gè)綁定方法,綁定方法同樣是對(duì)象,但是創(chuàng)建需要成本,而靜態(tài)方法可以避免這些。
Student().year is Student().year
Student().aver_age is Student().aver_age
Student().aver_age is Student.aver_age
False
True
True
可讀性更好的代碼,看到@staticmethod我們就知道這個(gè)方法并不需要依賴對(duì)象本身的狀態(tài)。
可以在子類中被覆蓋,如果是把a(bǔ)ver_age作為模塊的頂層函數(shù),那么繼承自Student的子類就沒(méi)法改變Student的aver_age了如果不覆蓋year的話。
類方法
什么是類方法?類方法不是綁定到對(duì)象上,而是綁定在類上的方法。
class Student(object):
score = 100
@classmethod
def get_score(cls):
return cls.score
print Student.get_score
print Student().get_score
print Student().get_score is Student.get_score
print Student.get_score()
<bound method type.get_score of <class '__main__.Student'>>
<bound method type.get_score of <class '__main__.Student'>>
False
100
無(wú)論用哪種方式訪問(wèn)這個(gè)方法,它總是綁定到了這個(gè)類身上,它的第一個(gè)參數(shù)是這個(gè)類本身(類也是對(duì)象)
什么時(shí)候使用這種方法呢? 類方法通常在以下兩種場(chǎng)景是非常有用的:
- 工廠方法: 它用于創(chuàng)建類的實(shí)例,例如一些預(yù)處理。如果使用@staticmethod代替,那我們不得不硬編碼Student類名在函數(shù)中,這使得任何繼承Student的類都不能使用我們這個(gè)工廠方法給它自己用。
class Student(object):
def __init__(self, age):
self.age= age
@classmethod
def year_day(cls, year):
return cls(year.get_cheese() + fridge.get_day())
- 調(diào)用靜態(tài)類:如果把一個(gè)靜態(tài)放啊拆分多個(gè)靜態(tài)方法,除非使用類方法,否則還是得硬編碼類名。使用這種方式聲明方法,Student類名永遠(yuǎn)都不會(huì)再被直接飲用,繼承和方法覆蓋都可以完美的工作。
class Student(object):
def __init__(self,heigh,weigh):
self.heigh = heigh
self.weigh = weigh
@staticmethod
def compute(heigh):
return math.pi * (heigh ** 2)
@classmethod
def compute_value(cls,heigh,wegigh):
return weigh * cls.compute(heugh)
def get_value(self):
return self.conpute_value(self.heigh, self.weigh)
- 抽象方法
抽象方法是定義在基類中的一種方法,它沒(méi)有提供任何實(shí)現(xiàn),類似于Java中接口(Interface)里面的方法。
在Python中實(shí)現(xiàn)抽象方法最簡(jiǎn)單的方式:
class Student(object):
def get_score(self):
raise NotImplementedError
人格繼承自Student的類必須覆蓋實(shí)現(xiàn)方法get_score,否則會(huì)拋出異常。
這種抽象方法的實(shí)現(xiàn)有它的弊端,如果你寫一個(gè)類繼承Student,但是忘記實(shí)現(xiàn)get_score,異常只要在正在使用的時(shí)候才會(huì)拋出來(lái)。
>>> Student()
<__main__.Student object at 0x7fb747353d90>
>>> Student().get_score()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in get_score
NotImplementedError
還有一種方式可以讓錯(cuò)誤更早的觸發(fā),使用Python提供的abc模塊,對(duì)象被初始化之后就可以拋出異常:
import abc
class BaseStudent(obiect):
__metaclass__ = abc.ADCMeta
@abc.abstractmethod
def get_score(self):
"""Method that should do something."""
使用abc,當(dāng)你嘗試初始化BaseStudent或者任何子類的時(shí)候立馬就會(huì)得到一個(gè)TypeError,而無(wú)需等到真正調(diào)用get_score的時(shí)候才會(huì)發(fā)現(xiàn)異常。
BaseStudent()
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class BaseStudent with abstract methods get_score
-
混合靜態(tài)方法、類方法、抽象方法
當(dāng)開(kāi)始構(gòu)建類和繼承結(jié)構(gòu)時(shí),混個(gè)使用這些裝飾器的時(shí)候到了,所以這里列出了一些技巧,聲明一個(gè)抽象的方法,不會(huì)固定方法的原型,這就是意味著雖然雖然必須實(shí)現(xiàn)它,但是可以用任何參數(shù)列表來(lái)實(shí)現(xiàn):
import abc
class BaseStudent(obiect):
__metaclass__ = abc.ADCMeta
@abc.abstractmethod
def get_score(self):
""""Returns the score list."""
class Message(BaseStudent):
def get_msg(self,add,with_sex=False):
sex = Sex() if with_sex else None
return self.get_msg + sex
這樣是允許的因?yàn)镸essage滿足BaseStudent對(duì)象所定義的接口需求。同樣也可以使用一個(gè)類方法或靜態(tài)方法進(jìn)行實(shí)現(xiàn):
import abc
class BaseStudent(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def get_score(self):
"""Returns the score list."""
class Obj(BasePizza):
@staticmethod
def get_msg():
return None
這同樣是正確的,因?yàn)樗裱橄箢怋aseStudent設(shè)定的契約。事實(shí)上get_msg方法并不需要知道返回結(jié)果是什么,結(jié)果是實(shí)現(xiàn)細(xì)節(jié),不是契約條件。
因此,不能強(qiáng)制抽象方法的實(shí)現(xiàn)是一個(gè)常規(guī)方法、或者是類方法還是靜態(tài) 方法,也沒(méi)什么可爭(zhēng)論的。從Python3開(kāi)始(在Python2中不能如所期待的的運(yùn)行),在avstractmethod方法上面使用@staticmethod和@classmethod裝飾器為可能。