第九章 面向?qū)ο?3
我們?cè)俣x一個(gè)有實(shí)際意義的類,員工類Worker,想想要包含哪些屬性哪些行為,或者以后我們都采用專業(yè)的說法,這個(gè)員工類包含哪些數(shù)據(jù)域和哪些方法。Have a try! 下面是我的程序:
class Worker:
??? # Construct a Worker object
??? def __init__(self, name="xiaoming",workID="2020011001",department="市場(chǎng)部",age=23,salary=2000):
??????? self.name = name
??????? self.department = department
??????? self.salary = salary
?
def main():
??? w1=Worker(name="小紅",salary=3000)
??? print(w1.salary)
?
main()
?
假設(shè)我是小紅的朋友,想給小紅多點(diǎn)錢,在main函數(shù)里加一句代碼:
??? w1.salary=40000
??? print(w1.salary)
或者在程序中還有很多函數(shù)f1……f10,每個(gè)函數(shù)都可以修改小紅的薪水,這就導(dǎo)致了混亂。因?yàn)樾薷男剿@件事只能由老板決定,而且還有可能程序修改的薪水值不小心寫錯(cuò)成上千萬,我們都無法控制這類情況,所以這個(gè)問題需要解決。也就是現(xiàn)在存在的問題是直接訪問對(duì)象的數(shù)據(jù)域?qū)е聰?shù)據(jù)被篡改,而且類會(huì)變得難以維護(hù)并且易于出錯(cuò),因此在Python中引入數(shù)據(jù)隱藏的概念,目的是防止直接訪問數(shù)據(jù)域。
數(shù)據(jù)隱藏可以通過定義私有數(shù)據(jù)域來完成,在Python中,私有數(shù)據(jù)域使用兩個(gè)下劃線定義,如self.__ salary,還可以以兩個(gè)下劃線開始來定義私有方法。私有數(shù)據(jù)域只能在類內(nèi)訪問,類外不可以訪問。按照數(shù)據(jù)隱藏的思路,上述程序可以改成下面的樣子:
class Worker:
??? # Construct a Worker object
??? def __init__(self, name="xiaoming",workID="2020011001",department="市場(chǎng)部",age=23,salary=2000):
??????? self.name = name
??????? self.department = department
??????? self.__salary = salary
?
def main():
??? w1=Worker(name="小紅",salary=3000)
??? print(w1.__salary)? #程序運(yùn)行時(shí)此處會(huì)出現(xiàn)錯(cuò)誤
?????????????? ?????#錯(cuò)誤提示:AttributeError: 'Worker' object has no attribute '__salary'
main()
?
錯(cuò)誤的原因是因?yàn)開_salary是私有成員,類外不能訪問,而類內(nèi)可以訪問,如setSalary方法中修改了__salary。我把main函數(shù)修改成下面這樣再運(yùn)行一下試試:
def main():
??? w1=Worker(name="小紅",salary=3000)
??? w1.__salary=40000
天哪!居然沒有錯(cuò)誤,Why?
把main函數(shù)修改成下面這樣再運(yùn)行一下:
def main():
??? w1=Worker(name="小紅",salary=3000)
??? w1.__salary=40000
??? print(w1.__salary)
又出錯(cuò)了,我 ?我 ?我 ,?

?這是為什么呢?Keep reading!
在Python中,有以下幾種方式來定義變量:
??? xx:公有變量。類內(nèi)類外均可訪問
??? _xx:單前置下劃線,私有化屬性或方法,類對(duì)象和子類可以訪問,禁止導(dǎo)入(我們已經(jīng)學(xué)習(xí)過導(dǎo)入,如果想用random里面的方法,就需要導(dǎo)入random)from somemodule import *
??? __xx:雙前置下劃線,私有化屬性或方法,無法在外部直接訪問(名字重新調(diào)整所以訪問不到)
??? __xx__:雙前后下劃線,系統(tǒng)定義名字(不要自己發(fā)明這樣的名字)
??? xx_:單后置下劃線,用于避免與Python關(guān)鍵詞的沖突
“名字重新調(diào)整所以訪問不到” 是什么鬼?就是在類內(nèi)定義的私有成員如__salary在類外被重新命名為_Worker__salary,所以print(w1.__salary) 會(huì)提示沒有__salary屬性,即類外不提供訪問。那既然改了名字,我可以用改過的名字訪問嗎?回答可以!看看下面的運(yùn)行結(jié)果:

雖然_Worker__salary被高亮顯示提示有問題,但是確實(shí)輸出了結(jié)果3000!等一下,再看看有沒有問題!給你1分鐘時(shí)間。
w1.__salary=40000 這行代碼為什么沒有錯(cuò)誤,為什么輸出的不是40000?
呃,This is an extraordinarily interesting question.I must think fast.
再加一行代碼??? print(w1.__salary)??? 輸出試一下,結(jié)果如下:

這是因?yàn)轭惖膶?shí)例可以動(dòng)態(tài)增加屬性和方法,也就是說Worker類里面又增加了一個(gè)__salary數(shù)據(jù)域。但是建議不要這樣做。由此可見,Python是有多么任性!下面引入一個(gè)例子,看一下不明白也沒有關(guān)系!
?
class Person(object):# object這個(gè)問題下一節(jié)解釋(9.2繼承與多態(tài))
??? count = 0? #此變量為類變量,每個(gè)對(duì)象都可以訪問,類Person也可以訪問(深入了解請(qǐng)自學(xué))
??? def __init__(self, name):
??????? self.name = name
?
# 實(shí)例化
p1 = Person("tom")
print(p1.name)? # tom
print(p1.count)? # 0
?
p2 = Person("jack")
print(p2.name)? # jack
print(p2.count)? # 0
#
# 通過對(duì)象修改類變量
print("*******通過對(duì)象修改類變量*********")
p1.count = 2
print(p1.count)? # 2
print(p2.count)? # 0
print(Person.count)? # 0
#
# # 通過類修改類變量
print("*******通過對(duì)象修改類變量*********")
Person.count = 3
print(p1.count)? # 2
print(p2.count)? # 3
print(Person.count)? # 3
#
# 給對(duì)象增加屬性
print("*******給對(duì)象增加屬性age*********")
p1.age = 23
print(p1.age)? # 23
#
# 給對(duì)象增加方法
print("*******給對(duì)象p1增加方法set_age*********")
def set_age(self, age):
??? self.age = age
?
from types import MethodType
p1.set_age = MethodType(set_age, p1)
p1.set_age(25)
print(p1.age)? # 25
?
print("*******對(duì)象p2沒有方法set_age*********")
# print(p2.age)
# AttributeError: 'Person' object has no attribute 'age'
?
運(yùn)行結(jié)果如下:
tom
0
jack
0
*******通過對(duì)象修改類變量*********
2
0
0
*******通過對(duì)象修改類變量*********
2
3
3
*******給對(duì)象增加屬性age*********
23
*******給對(duì)象p1增加方法set_age*********
25
*******對(duì)象p2沒有方法set_age*********
Process finished with exit code 0
?
如果去掉倒數(shù)第二行# print(p2.age) 的 # 符號(hào),則運(yùn)行結(jié)果如下:
tom
0
jack
0
*******通過對(duì)象修改類變量*********
2
0
0
Traceback (most recent call last):
*******通過對(duì)象修改類變量*********
2
3
? File "F:/教學(xué)/2019秋/Python code/first.py", line 45, in <module>
3
*******給對(duì)象增加屬性age*********
??? print(p2.age)
23
*******給對(duì)象p1增加方法set_age*********
AttributeError: 'Person' object has no attribute 'age'
25
*******對(duì)象p2沒有方法set_age*********
Process finished with exit code 1
Ok,這個(gè)問題就說到這兒,但是還有一個(gè)問題,類內(nèi)不能訪問私有成員,那這些私有成員還有什么用?如何使用類的這些成員呢?
注意:是類外不能訪問,類內(nèi)是可以的,所以可以定義方法,一般是一對(duì)setter和getter。在方法里訪問這些成員,然后類外調(diào)用這些方法就可以了。那這不是更費(fèi)事嗎?當(dāng)然不是。我們可以給允許外界訪問的成員定義一對(duì)setter和getter,不允許外界修改的就不定義了,所以一切盡在我掌控!
定義有setter和getter方法的Worker類如下,試著理解一下:
class Worker:
??? # Construct a Worker object
??? def __init__(self, name="xiaoming",workID="2020011001",department="市場(chǎng)部",age=23,salary=2000):
??????? self.name = name
??????? self.department = department
??????? self.__salary = salary
?
def setSalary(self, salary):
??????? self.__salary = salary
?
??? def getSalary(self):
??????? return self.__salary
def main():
??? w1=Worker(name="小紅",salary=3000)
??? print(w1.name," salary:",w1.getSalary())
??? if w1.getSalary()<5000:
??????? w1.setSalary(5500)
??? print("漲工資了!")
??? print(w1.name, " salary:", w1.getSalary())
main()