前面一節(jié)重點(diǎn)學(xué)習(xí)了Python3中的面向?qū)ο笞罨A(chǔ)的類和對象的知識:類的定義、對象、構(gòu)造方法、屬性和方法等,在類中定義好的屬性,就可以賦值、取值等一系列操作了。
例如:定義一個SmartPhone類,源碼如下:
# 定義一個SmartPhone類
class SmartPhone(object):
os = "" # 系統(tǒng)
type = "" # 品牌
memory = "4GB" # 內(nèi)存大小 默認(rèn)單位GB
# 構(gòu)造方法
def __init__(self, os, type, memory):
self.os = os
self.type = type
self.memory = memory
# 定義一個打印智能手機(jī)屬性的方法
def print_phone_info(self):
print('os:', self.os, '\n品牌:', self.type, '\n內(nèi)存大小:', self.memory)
創(chuàng)建一個SmartPhone對象,打印調(diào)用打印信息方法print_phone_info()后,再修改屬性的值。但是,從前面SmartPhone類的定義來看,外部代碼還是可以自由地修改一個實(shí)例的type 、memory 、os等屬性的。如下:
# 創(chuàng)建一個SmartPhone類型的對象
phone1 = SmartPhone('Android', 'Galaxy', '6GB')
phone1.print_phone_info()
print('---------------------')
# 修改phone1指向的對象的屬性:品牌、內(nèi)存
phone1.type = "MeiZu 7Pro"
phone1.memory = '8GB'
phone1.print_phone_info()
運(yùn)行結(jié)果如下:
os: Android
品牌: Galaxy
內(nèi)存大小: 6GB
---------------------
os: Android
品牌: MeiZu 7Pro
內(nèi)存大小: 8GB
Java中是通過關(guān)鍵字:私有的private、公開的public、默認(rèn)受保護(hù)的protected等來限制屬性、方法的訪問權(quán)限的。如果在Python中,想要讓內(nèi)部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線,即:__。
私有屬性
在Python中規(guī)定:實(shí)例的變量名如果以__開頭,就變成了一個私有變量(private),只有類內(nèi)部可以訪問,類外部不能訪問。
所以,我們把SmartPhone類的定義做一下修改:
# 定義一個SmartPhone類
class SmartPhone(object):
__os = "" # 私有屬性:系統(tǒng)
__type = "" # 私有屬性:品牌
__memory = "4GB" # 私有屬性:內(nèi)存大小 默認(rèn)單位GB
# 構(gòu)造方法
def __init__(self, os, type, memory):
self.__os = os
self.__type = type
self.__memory = memory
def print_phone_info(self):
print('os:', self.__os, '\n品牌:', self.__type, '\n內(nèi)存大小:', self.__memory)
創(chuàng)建一個SmartPhone對象,打印調(diào)用打印信息方法print_phone_info()后,再訪問私有屬性,會報(bào)錯:AttributeError,如下:
# 創(chuàng)建一個SmartPhone類型的對象
phone1 = SmartPhone('Android', 'Galaxy', '6GB')
phone1.print_phone_info()
# 在類的外部訪問私有屬性__type,會報(bào)錯:AttributeError
phone1.__type
運(yùn)行結(jié)果:
Traceback (most recent call last):
os: Android
File "F:/python_projects/oop/23SmartPhone.py", line 21, in <module>
品牌: Galaxy
phone1.__type
內(nèi)存大小: 6GB
AttributeError: 'SmartPhone' object has no attribute '__type'
可以看到運(yùn)行結(jié)果報(bào)錯了,AttributeError: 'SmartPhone' object has no attribute '__type'意思是:屬性錯誤:SmartPhone的對象沒有__type屬性。
這是因?yàn)槲覀冊趖ype屬性前加了__,使__type變成了私有屬性,只有類內(nèi)部可以訪問,類外部不能訪問。
假如,我們在類的外部還想訪問、修改類里面的私有屬性怎么辦?可能你已經(jīng)想到了:給類添加訪問、修改私有屬性的方法!沒錯,修改如下:
# 定義一個SmartPhone類
class SmartPhone(object):
__os = "" # 私有屬性:系統(tǒng)
__type = "" # 私有屬性:品牌
__memory = "4GB" # 私有屬性:內(nèi)存大小 默認(rèn)單位GB
# 構(gòu)造方法
def __init__(self, os, type, memory):
self.__os = os
self.__type = type
self.__memory = memory
def print_phone_info(self):
print('os:', self.__os, '\n品牌:', self.__type, '\n內(nèi)存大小:', self.__memory)
# 對外界提供獲取__os的值的方法
def get_os(self):
return self.__os
# 對外界提供設(shè)置__os的值的方法
def set_os(self, os):
self.__os = os
# 對外界提供獲取__type的值的方法
def get_type(self):
return self.__type
# 對外界提供設(shè)置__type的值的方法
def set_type(self, types):
self.__type = types
# 對外界提供獲取__memory的值的方法
def get_memory(self):
return self.__memory
# 對外界提供設(shè)置__memory的值的方法
def set_memory(self, memory):
self.__memory = memory
創(chuàng)建對象,在外界獲取和設(shè)置屬性的值如下:
# 創(chuàng)建一個SmartPhone類型的對象
phone1 = SmartPhone('Android', 'Galaxy', '6GB')
phone1.print_phone_info()
print('-------------------')
# 通過提供的set方法在類的外部訪問私有屬性
phone1.set_type("MeiZu 7Pro")
phone1.set_memory("8GB")
# 通過提供的get方法在類的外部訪問私有屬性
print('外部訪問:\n', phone1.get_os(), '\n', phone1.get_type(), '\n', phone1.get_memory())
運(yùn)行結(jié)果:
os: Android
品牌: Galaxy
內(nèi)存大小: 6GB
-------------------
外部訪問:
Android
MeiZu 7Pro
8GB
可以看到運(yùn)行結(jié)果,在類的外部成功地獲取、修改私有屬性的值了。這樣做,不是自找麻煩嗎?那么,為什么要這樣多此一舉呢?這么做的意義非常大:在修改類中的屬性前,可以對外界傳入的數(shù)據(jù)做檢查。例如:外界傳入的品牌不是字符串,而是隨意傳入了一個整數(shù):
# 創(chuàng)建一個SmartPhone類型的對象
phone1 = SmartPhone('Android', 'Galaxy', '6GB')
phone1.print_phone_info()
print('-------------------')
phone1.set_type(2)
print(phone1.get_type())
運(yùn)行結(jié)果:
os: Android
品牌: Galaxy
內(nèi)存大小: 6GB
-------------------
2
這是未做檢查的運(yùn)行結(jié)果。在set_type()方法中做了數(shù)據(jù)檢查,其他代碼不變,更新的部分如下:
# 對外界提供設(shè)置__type的值的方法
def set_type(self, types):
if isinstance(types, str):
self.__type = types
else:
raise ValueError(" SmartPhone type should be str!")
重新運(yùn)行,運(yùn)行結(jié)果:
os: Android
Traceback (most recent call last):
品牌: Galaxy
File "F:/python_projects/oop/23SmartPhone.py", line 51, in <module>
內(nèi)存大小: 6GB
phone1.set_type(2)
-------------------
File "F:/python_projects/oop/23SmartPhone.py", line 33, in set_type
raise ValueError(" SmartPhone type should be str!")
起到了對傳入的無效數(shù)據(jù)檢查的作用。關(guān)于Python中異常的處理后面會學(xué)習(xí),此處先拿來用下。raise:表示拋出異常,拋出一個ValueError的異常,并提示用戶:SmartPhone type should be str!
私有方法
Python中規(guī)定,私有方法同樣以兩個下劃線開頭,聲明該方法為私有方法,只能在類的內(nèi)部調(diào)用,不能在類地外部調(diào)用,形如:self.__method_name。
在上面的SmartPhone類中添加一個私有方法__test(),代碼如下:
# 上接SmartPhone類,添加一個私有方法
# 添加一個私有方法
def __test(self):
print("__test()是私有方法")
創(chuàng)建一個SmartPhone類型的對象,
# 創(chuàng)建一個SmartPhone類型的對象,并在外部調(diào)用該私有方法,
phone1 = SmartPhone('Android', 'Galaxy', '6GB')
phone1.print_phone_info()
print('-------------------')
# 在外部調(diào)用該私有方法,
phone1.__test()
運(yùn)行結(jié)果:
os: Android
Traceback (most recent call last):
品牌: Galaxy
File "F:/python_projects/oop/23SmartPhone.py", line 55, in <module>
內(nèi)存大小: 6GB
phone1.__test()
-------------------
AttributeError: 'SmartPhone' object has no attribute '__test'
報(bào)錯AttributeError: 'SmartPhone' object has no attribute '__test',意思是:屬性錯誤:SmartPhone的對象沒有__test屬性。
提示
需要特別注意的是,在Python中,變量名類似__xxxx__的,也就是以雙下劃線開頭,并且以雙下劃線結(jié)尾的,是特殊變量。而特殊變量是可以直接訪問的,并不是私有的變量,所以我們自己在定義類中的私有屬性時,千萬不能用__xxxx__這樣的變量名,以免混淆。
有些時候,你會看到以一個下劃線開頭的實(shí)例變量名,比如_xxxx,這樣的實(shí)例變量外部也是可以訪問的。但是,按照Python約定俗成的規(guī)定,好的習(xí)慣是,當(dāng)你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變量,不要隨意訪問”。
注意:
雙下劃線開頭的實(shí)例變量是不是一定不能從外部訪問呢?其實(shí)也不是。
上面例子中,不能直接訪問__type是因?yàn)镻ython解釋器對外把__type變量改成了_SmartPhone__type,所以,仍然可以通過_SmartPhone__type來訪問__type變量。例如:
# 創(chuàng)建一個SmartPhone類型的對象
phone1 = SmartPhone('Android', 'Galaxy', '6GB')
phone1.print_phone_info()
print('-------------------')
print(phone1._SmartPhone__os)
print(phone1._SmartPhone__type)
print(phone1._SmartPhone__memory)
運(yùn)行結(jié)果:
os: Android
品牌: Galaxy
內(nèi)存大小: 6GB
-------------------
Android
Galaxy
6GB
但是強(qiáng)烈建議你不要這么干,因?yàn)椴煌姹镜腜ython解釋器可能會把__xxxx改成不同的變量名,不一定總是_類名__xxxx。
易錯點(diǎn)舉例
# 上接SmartPhone類定義
# 創(chuàng)建一個SmartPhone類型的對象
phone1 = SmartPhone('Android', 'Galaxy', '6GB')
phone1.print_phone_info()
print('-------------------')
# 修改phone1指向的對象的內(nèi)存為"8GB"
phone1.__memory = "8GB"
print('__memory :', phone1.__memory)
print('get_memory() :', phone1.get_memory())
print('_SmartPhone__memory :', phone1._SmartPhone__memory)
從表面上看,我們在外部代碼中“成功”地把"8GB"設(shè)置了__memory 變量,但實(shí)際上這個__memory變量和class內(nèi)部的__memory變量不是一個變量!內(nèi)部的__memory變量已經(jīng)被Python解釋器自動改成了_SmartPhone__memory,而外部代碼給phone1新增了一個__memory變量。
運(yùn)行結(jié)果:
os: Android
品牌: Galaxy
內(nèi)存大小: 6GB
-------------------
__memory : 8GB
get_memory() : 6GB
_SmartPhone__memory : 6GB
運(yùn)行結(jié)果印證了上面的話,這是一個易忽略點(diǎn),要留意。
說明
類中還預(yù)定義了一些方法,Python的class中還有許多這樣有特殊用途的函數(shù),可以幫助我們定制類,我們會在以后一一詳細(xì)學(xué)習(xí)。
類的專有方法:
__init__ : 構(gòu)造函數(shù),在生成對象時調(diào)用
__del__ : 析構(gòu)函數(shù),釋放對象時使用
__repr__ : 打印,轉(zhuǎn)換
__setitem__ : 按照索引賦值
__getitem__: 按照索引獲取值
__len__: 獲得長度
__cmp__: 比較運(yùn)算
__call__: 函數(shù)調(diào)用
__add__: 加運(yùn)算
__sub__: 減運(yùn)算
__mul__: 乘運(yùn)算
__div__: 除運(yùn)算
__mod__: 求余運(yùn)算
__pow__: 乘方
小結(jié)
本文主要通過幾個簡單的例子來學(xué)習(xí)類中私有屬性和方法,以及它們之間的區(qū)別,要熟練掌握。
如果你觀察的足夠仔細(xì),你會發(fā)現(xiàn),例子中我們屬性只有3個,增加了3對get\set方法,也就是6個。那么,如果屬性非常多時,我們上面的做法又會顯得非常麻煩,這有沒有好的解決方案呢?答案是有的。python中給我們提供了一些關(guān)鍵詞@property,專門幫我們解決上面的問題,我們會在后面詳細(xì)學(xué)習(xí)這些知識。