Python面向?qū)ο?- 屬性和方法

屬性

類屬性和實(shí)例屬性

屬性是面向?qū)ο蟮慕蟹ǎc變量一樣是用來存放程序運(yùn)行時(shí)需要用到的數(shù)據(jù)。區(qū)別在于,屬性一定有一個(gè)宿主堪嫂,根據(jù)數(shù)組的不同,分為類屬性和實(shí)例屬性:

  • 類屬性:屬性的宿主是類對象木柬,類的實(shí)例共享這個(gè)屬性皆串。任何一個(gè)類實(shí)例對類屬性進(jìn)行修改,其他類實(shí)例訪問這個(gè)類屬性的時(shí)候眉枕,值也相應(yīng)的發(fā)生變化恶复。
  • 實(shí)例屬性:屬性的宿主是實(shí)例對象,類的實(shí)例和實(shí)例之間各自保存實(shí)例屬性速挑,實(shí)例屬性的修改僅對修改該屬性的實(shí)例生效谤牡。

申明:為了描述上的方便,下文中遵循下面兩個(gè)規(guī)則:

  • class.xxxxx - 表示類屬性
  • obj.xxxxx - 表示實(shí)例屬性

類屬性和實(shí)例屬性的定義方式分別有兩種姥宝,一種在類內(nèi)部添加拓哟,另一種是在類外部添加,如下面代碼所示:

class ChinesePeople:
    # Class Attribute
    country = 'China'

    def __init__(self, name):
        # Instance Attribute
        self.name = name

p1 = ChinesePeople('Bill')
print(p1.name, p1.country, ChinesePeople.country)

p1.age = 18
ChinesePeople.color = 'yellow'
print(p1.age, p1.color, ChinesePeople.color)

#### Outputs ###
Bill China China
18 yellow yellow

上面的示例代碼涉及到屬性的創(chuàng)建和訪問伶授。對于屬性的刪除断序,用關(guān)鍵字del即可,即del class.attr/del obj.attr糜烹。實(shí)例屬性只能通過實(shí)例對象來訪問违诗,但是類屬性即可以通過類對象也可以通過實(shí)例對象來訪問。之所以這樣疮蹦,這和屬性的存儲和查找是關(guān)聯(lián)的诸迟。

在Python中,屬性和方法都是保存在dict這個(gè)內(nèi)置的字典中愕乎。相應(yīng)的阵苇,類屬性則保存在class.dict中,實(shí)例屬性保存在obj.dict中感论。因此绅项,對于類屬性和實(shí)例屬性的訪問,遵循以下規(guī)則:

  • class.attr:通過類對象訪問類屬性比肄,那么直接去class.dict中查找快耿,找不到則拋出異常。
  • obj.attr:通過實(shí)例對象訪問實(shí)例屬性或者類屬性芳绩,遵循一樣的順序掀亥。也就是,先從obj.dict中查找妥色,如果找不過則從class.__dict__(實(shí)際是obj.__class__.__dict__)中查找搪花,如果還是找不到,則拋出異常。
  • 相應(yīng)的撮竿,當(dāng)適用del關(guān)鍵字刪除實(shí)例屬性或者類屬性的時(shí)候丁稀,對應(yīng)的從相應(yīng)的dict中刪除對應(yīng)項(xiàng)。

我們可以通過打印出dict的值來了解上面的規(guī)則:

print(p1.__dict__)
print(ChinesePeople.__dict__)

del p1.name
del ChinesePeople.country

print(p1.__dict__)
print(ChinesePeople.__dict__)

#### Outputs ###
# p1.__dict__
{'name': 'Bill', 'age': 18} 

#ChinesePeople.__dict__
{'__module__': '__main__', 'country': 'China', '__init__': <function ChinesePeople.__init__ at 0x10df72a60>, '__dict__': <attribute '__dict__' of 'ChinesePeople' objects>, '__weakref__': <attribute '__weakref__' of 'ChinesePeople' objects>, '__doc__': None, 'color': 'yellow'} 

# p1.__dict__
{'age': 18}

#ChinesePeople.__dict__
{'__module__': '__main__', '__init__': <function ChinesePeople.__init__ at 0x10d6c5a60>, '__dict__': <attribute '__dict__' of 'ChinesePeople' objects>, '__weakref__': <attribute '__weakref__' of 'ChinesePeople' objects>, '__doc__': None, 'color': 'yellow'}

上面提到倚聚,通過實(shí)例對象訪問類屬性是完全沒有問題的线衫,而且實(shí)際中也經(jīng)常這么做。那可不可以通過實(shí)例對象來修改類屬性呢惑折?答案是不可以的授账,這樣做相當(dāng)于添加了一個(gè)實(shí)例屬性,這一點(diǎn)也可以通過查看obj.dict得以驗(yàn)證惨驶。

print(p1.__dict__)
p1.country = 'Great China'
print(p1.__dict__)

#### Outputs ###
{'name': 'Bill'}
{'name': 'Bill', 'country': 'Great China'}
限制屬性的添加

python中添加一個(gè)屬性很靈活白热,但有時(shí)候作為類的創(chuàng)建者,并不希望類的使用者在對類添加額外的屬性粗卜,或者對添加的屬性進(jìn)行限制屋确,這種情況下我們只需要對slots列表賦值即可:

  • 如果slots賦值成空列表,那么就不允許在類的內(nèi)部或者外部添加任何屬性续扔。
  • 如果允許添加特定名字的屬性攻臀,那只需要把這些名字存放在slots中。

需要注意的是:

  • slots只能對實(shí)例屬性起限制的作用
  • 定義了slots以后纱昧,實(shí)例屬性的讀取就不再通過obj.dict來獲取
  • 如果類屬性與slots中的變量同名刨啸,則該類屬性被設(shè)置為readonly,并且會覆蓋同名的實(shí)例屬性
class ChinesePeople:
    # Class Attribute
    country = 'China'

    def __init__(self, name):
        # Instance Attribute
        self.name = name

    __slots__ = ['name']

p1.age = 18 # AttributeError: 'ChinesePeople' object has no attribute 'age'
屬性的訪問權(quán)限

與C#, Java不一樣识脆,python中并沒有像private/protect/public這樣的關(guān)鍵字來修飾屬性活著方法的訪問權(quán)限设联。那在python中如何實(shí)現(xiàn)屬性的私有化和只讀?

在python中灼捂,如果一個(gè)屬性是以雙下劃線開頭(例如__name)离例,那么這屬性就是私有的(注意,這里要和類內(nèi)置的屬性區(qū)分開悉稠,例如前面提到的dict)宫蛆。來看看下面的代碼:

class ChinesePeople:
    # Class Attribute
    country = 'China'
    __province = 'Taiwan'

    def __init__(self, name):
        # Instance Attribute
        self.name = name
        self.__salary = 5000

p1 = ChinesePeople('Bill')
print(ChinesePeople.__province) # AttributeError: type object 'ChinesePeople' has no attribute '__province'
print(p1.__salary) # AttributeError: 'ChinesePeople' object has no attribute '__salary'

下面,我們先分別來看看class.dict和obj.dict的值偎球,然后再來談?wù)刾ython中的私有化屬性洒扎。

print(p1.__dict__)
print(ChinesePeople.__dict__)

#### Outputs ###
{'name': 'Bill', '_ChinesePeople__salary': 5000}
{'__module__': '__main__', 'country': 'China', '_ChinesePeople__province': 'Taiwan', '__init__': <function ChinesePeople.__init__ at 0x1073e3a60>, '__dict__': <attribute '__dict__' of 'ChinesePeople' objects>, '__weakref__': <attribute '__weakref__' of 'ChinesePeople' objects>, '__doc__': None}

通過上面代碼的輸出,我們一定會產(chǎn)生一個(gè)疑問衰絮,_ChinesePeople__salary和_ChinesePeople__province是什么鬼,我們明明沒有定義這個(gè)兩個(gè)屬性磷醋,我們定義的是__salary和__province猫牡,這是怎么回事?

其實(shí)邓线,python中沒有私有化屬性的概念淌友,上面提到的私有化煌恢,實(shí)際上是"偽私有化"。當(dāng)python解釋器遇到以雙下劃線開頭的屬性震庭,那么會對這個(gè)屬性進(jìn)行重命名瑰抵,也就是在這個(gè)屬性前面添加classname_。例如器联,將__salary重命名為_ChinesePeople__salary二汛。這樣,當(dāng)我們通過__xxxx訪問一個(gè)屬性的時(shí)候:

  • 如果是在類內(nèi)部訪問拨拓,那么解釋也會做相應(yīng)的轉(zhuǎn)化肴颊。也就是說,訪問obj.__xxxx/class.__xxxx時(shí)渣磷,會轉(zhuǎn)換成訪問obj._classname__xxxx/class.__classname__婿着。很顯然,被重命名過的屬性醋界,是可以在dict中找到竟宋。
  • 如果是在類外部訪問,那么解釋器就不做轉(zhuǎn)化形纺,所以也就不能從dict中找到__xxxx袜硫。

對于屬性的只讀限制,這里先提供一下實(shí)現(xiàn)的方式挡篓,由于涉及到裝飾器和描述器的概念婉陷,后面將會有特定的筆記來說明。實(shí)現(xiàn)只讀屬性官研,大概的思路有下面三種:

  • 將屬性設(shè)置為私有屬性秽澳,而后通過get方法進(jìn)行讀取
  • 通過@property這個(gè)裝飾器來實(shí)現(xiàn)
  • 通過描述器來實(shí)現(xiàn)

方法

方法和函數(shù)

比起屬性和變量,方法和函數(shù)好像更復(fù)雜一點(diǎn)戏羽。從本質(zhì)上講担神,方法和函數(shù)都是可調(diào)用的對象,它們的區(qū)別在于:

  • 函數(shù)(function):沒有隱式的參數(shù)始花。簡單點(diǎn)說妄讯,如果一個(gè)函數(shù)需要傳遞進(jìn)去兩個(gè)參數(shù),那么調(diào)用者必須傳遞兩個(gè)參數(shù)酷宵,解釋器不會幫忙隱式傳遞
  • 方法(method):方法的第一個(gè)參數(shù)是self或者cls亥贸,分別表示類的實(shí)例對象和類對象。對于調(diào)用者而言浇垦,第一個(gè)不需要顯示傳遞炕置,因?yàn)榻忉屍饕呀?jīng)幫忙傳遞了第一個(gè)參數(shù)

來看看下面的代碼:

def hello(self, name):
    print(self,',', name)

class ChinesePeople:
     # Class Attribute
    country = 'China'
    
    def __init__(self, name):
        # Instance Attribute
        self.name = name
   
    def say_hi(self, name):
        print(self,',', name)
    
p1 = ChinesePeople('Bill')

print(hello, p1.say_hi)
hello(1000, 'Jim')
p1.say_hi('Jim')

#### Outputs ###
<function hello at 0x00000245A3FC6D08> <bound method ChinesePeople.say_hi of <__main__.ChinesePeople object at 0x00000245A3FF6E48>>
1000 , Jim
<__main__.ChinesePeople object at 0x00000245A3FF6E48> , Jim

上面的代碼中,hello就是一個(gè)function,而類中定義的say_hi則是一個(gè)method朴摊。通過實(shí)例對象調(diào)用say_hi的時(shí)候默垄,解釋器幫忙傳遞了self,因此我們只需要顯示的傳遞一個(gè)參數(shù)甚纲。(其實(shí)這里參數(shù)的名字定義為self口锭,只是為了好理解,其他的名字也是沒有問題介杆,但建議使用self)

實(shí)例方法鹃操,類方法和靜態(tài)方法

上面提到了方法和函數(shù)的差別在于解釋器是否幫忙傳遞第一個(gè)參數(shù)。那根據(jù)解釋器傳遞的第一個(gè)參數(shù)的值的不同这溅,又分為下面三種方法:

  • 實(shí)例方法:第一個(gè)參數(shù)傳遞的是實(shí)例對象
  • 類方法:第一個(gè)參數(shù)傳遞的是類對象
  • 靜態(tài)方法:不存在隱式傳遞的第一個(gè)參數(shù) (這個(gè)其實(shí)就等價(jià)于函數(shù)了)

以上三種方法组民,都是需要通過各自的裝飾器來定義,如下面的代碼:

def hello(self, name):
    print(self,',', name)

class ChinesePeople:
     # Class Attribute
    country = 'China'
    
    def __init__(self, name):
        # Instance Attribute
        self.name = name
   
    def say_hi(self, name):
        print(self,',', name)
    
    @classmethod
    def class_say_hi(cls, name):
        print(cls,',', name)
    
    @staticmethod
    def static_say_hi(name):
        print(name)
    
p1 = ChinesePeople('Bill')

print(p1.say_hi, ChinesePeople.class_say_hi, ChinesePeople.static_say_hi)
print(p1.say_hi, p1.class_say_hi, p1.static_say_hi)
p1.say_hi('Jim')
p1.class_say_hi('Jim')
p1.static_say_hi('Jim')

ChinesePeople.class_say_hi('Jim')
ChinesePeople.static_say_hi('Jim')

#### Outputs ###
<bound method ChinesePeople.say_hi of <__main__.ChinesePeople object at 0x00000245A4036710>> <bound method ChinesePeople.class_say_hi of <class '__main__.ChinesePeople'>> <function ChinesePeople.static_say_hi at 0x00000245A403F510>
<bound method ChinesePeople.say_hi of <__main__.ChinesePeople object at 0x00000245A4036710>> <bound method ChinesePeople.class_say_hi of <class '__main__.ChinesePeople'>> <function ChinesePeople.static_say_hi at 0x00000245A403F510>
<__main__.ChinesePeople object at 0x00000245A4036710> , Jim
<class '__main__.ChinesePeople'> , Jim
Jim
<class '__main__.ChinesePeople'> , Jim
Jim

從上面代碼可知悲靴,不論是實(shí)例方法臭胜、類方法和靜態(tài)方法,都是可以通過實(shí)例對象來訪問癞尚,并且解釋器都是會正確的傳遞一個(gè)參數(shù)耸三;但是對于類對象而言,是無法調(diào)用實(shí)例方法浇揩。

動態(tài)添加方法

上文屬性的部分仪壮,我們了解到屬性是可以在類外部動態(tài)來添加。那對于方法而言胳徽,同樣的方式是否適用积锅?

def hello(self, name):
    print(self,',', name)
    
    
def hello1(self, name):
    print(self,',', name)
    

class ChinesePeople:
     # Class Attribute
    country = 'China'
    
    def __init__(self, name):
        # Instance Attribute
        self.name = name
    
    def say_hi(self, name):
        print(self, ',', name)

    
p1 = ChinesePeople('Bill')

p1.hello = hello
ChinesePeople.hello1 = hello1

print(hello, p1.hello)
print(p1.hello1, ChinesePeople.hello1)
print(p1.say_hi, ChinesePeople.say_hi)

#### Outputs ###
<function hello at 0x00000245A40702F0> <function hello at 0x00000245A40702F0>

<bound method hello1 of <__main__.ChinesePeople object at 0x00000245A406A0F0>> <function hello1 at 0x00000245A40701E0>

<bound method ChinesePeople.say_hi of <__main__.ChinesePeople object at 0x00000245A406A0F0>> <function ChinesePeople.say_hi at 0x00000245A4070378>

從上面的輸出,可以得出以下結(jié)論:

  • 如果直接在實(shí)例對象上通過一個(gè)函數(shù)來賦值养盗,那么這個(gè)函數(shù)不會轉(zhuǎn)化成實(shí)例方法
  • 直接在類對象上通過一個(gè)函數(shù)來賦值缚陷,接著通過實(shí)例對象來調(diào)用,那么相當(dāng)于是實(shí)例方法(類似于在類內(nèi)部定義了一個(gè)實(shí)例方法)往核。

對于類方法和靜態(tài)方法的添加箫爷,也是類似,前提是要在相應(yīng)的方法上加上@classmethod和@staticmethod裝飾器即可聂儒。

思考一個(gè)問題虎锚,在上述代碼基礎(chǔ)上,再創(chuàng)建一個(gè)p2實(shí)例衩婚,而后分別調(diào)用hello和hello1窜护,是什么結(jié)果?(自己動手谅猾,豐衣足食)柄慰。

上面的添加的實(shí)例方法鳍悠,是作用到類的所有實(shí)例中税娜。如果我們只想對特定的實(shí)例添加方法坐搔,可以通過types.MethodType把一個(gè)函數(shù)綁定到特定的實(shí)例上:

import types

def hello1(self, name):
    print(self,',', name)

class ChinesePeople:
    pass

p1 = ChinesePeople()
p2 = ChinesePeople()

p1.hello1 = types.MethodType(hello1, p1)


p1.hello1('name') # ok
p2.hello1('name') # AttributeError: 'ChinesePeople' object has no attribute 'hello1'
方法的添加限制和私有化

方法的添加限制和私有化與屬性類似,即通過slots限制敬矩、通過雙下劃線開頭實(shí)現(xiàn)私有化概行。這里就不再贅述。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弧岳,一起剝皮案震驚了整個(gè)濱河市凳忙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌禽炬,老刑警劉巖涧卵,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異腹尖,居然都是意外死亡柳恐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門热幔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乐设,“玉大人,你說我怎么就攤上這事绎巨〗校” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵场勤,是天一觀的道長戈锻。 經(jīng)常有香客問我,道長和媳,這世上最難降的妖魔是什么格遭? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮窗价,結(jié)果婚禮上如庭,老公的妹妹穿的比我還像新娘。我一直安慰自己撼港,他們只是感情好坪它,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帝牡,像睡著了一般往毡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上靶溜,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天开瞭,我揣著相機(jī)與錄音懒震,去河邊找鬼。 笑死嗤详,一個(gè)胖子當(dāng)著我的面吹牛个扰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播葱色,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼递宅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了苍狰?” 一聲冷哼從身側(cè)響起办龄,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淋昭,沒想到半個(gè)月后俐填,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翔忽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年英融,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呀打。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡矢赁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贬丛,到底是詐尸還是另有隱情撩银,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布豺憔,位于F島的核電站额获,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏恭应。R本人自食惡果不足惜抄邀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望昼榛。 院中可真熱鬧境肾,春花似錦、人聲如沸胆屿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽非迹。三九已至环鲤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間憎兽,已是汗流浹背冷离。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工吵冒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人西剥。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓痹栖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蔫耽。 傳聞我的和親對象是個(gè)殘疾皇子结耀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容