本節(jié)課綱
- 類和對象
- 類的定義
- self參數(shù)
- 初始化方法init()
- _str_()方法
- 面向?qū)ο髒s面向過程
- 私有屬性-封裝
- 將實(shí)例用作屬性-對象組合
- 類屬性 類方法 靜態(tài)方法
- 繼承
- _new_()方法
- object
- 單例模式
- 函數(shù)參數(shù)注解
1)類和對象
1.1 萬物皆對象
分類是人們認(rèn)識世界的一個(gè)很自然的過程救欧,在日常生活中會不自覺地將對象進(jìn)行進(jìn)行分類
對象歸類
- 類是抽象的概念,僅僅是模板 比如說:“人”
- 對象是一個(gè)你能夠看得到钝计、摸得著的具體實(shí)體: 趙本山飞醉,劉德華削解,趙麗穎
舉例
user1 = 'zhangsan'
print(type(user1))
user2 = 'lisi'
print(type(user2))
輸出
<class 'str'>
<class 'str'>
以上str是類(python中的字符串類型)何陆,user1和user2是對象(以前我們叫變量)
研究對象
顧客類型
屬性:
姓名—張浩
年齡—20
體重—60kg
行為:
購買商品
收銀員類型
屬性:
員工號—10001
姓名—王淑華
部門—財(cái)務(wù)部
行為:
收款
打印賬單
1.2 對象的特征——屬性
- 屬性——對象具有的各種特征。 每個(gè)對象的每個(gè)屬性都擁有特定值戈二。 例如:顧客張浩和李明的年齡预柒、姓名不一樣队塘。
1.3 對象的特征——方法(操作,行為)
方法——對象執(zhí)行的操作(通常會改變屬性的值)宜鸯。 顧客對象的方法---購買商品憔古。 收銀員對象的方法----收款。
對象:用來描述客觀事物的一個(gè)實(shí)體顾翼,由一組屬性和方法構(gòu)成
舉例:
類型:狗
對象名:doudou
屬性:
顏色:白色
方法:
叫投放,跑奈泪,吃
對象同時(shí)具有屬性和方法兩項(xiàng)特性适贸。 對象的屬性和方法通常被封裝在一起,共同體現(xiàn)事物的特性涝桅, 二者相輔相承拜姿,不能分割。
課上練習(xí) 說一說教室里的對象冯遂,描述他們的屬性和方法
1.4 從對象抽象出類
抽取出下列對象的共同特征(屬性和方法)
類是模子蕊肥,定義對象將會擁有的特征(屬性)和行為(方法)。
再次強(qiáng)調(diào)
- 類是抽象的概念蛤肌,僅僅是模板壁却。 比如說:“人”, 類定義了人這種類型屬性(name裸准,age...)和方法(study,work...)展东。
- 對象是一個(gè)你能夠看得到、摸得著的具體實(shí)體: 趙本山炒俱,劉德華盐肃,趙麗穎,這些具體的人都具有人類型中定義的屬性和方法权悟,不同的是他們各自的屬性不同砸王。
根據(jù)類來創(chuàng)建對象被稱為實(shí)例化。
2)類的定義
2.1 定義只包含方法的類
在 Python 中要定義一個(gè)只包含方法的類峦阁,語法格式如下:
class 類名:
def 方法1(self, 參數(shù)列表):
pass
def 方法2(self, 參數(shù)列表):
pass
方法 的定義格式和之前學(xué)習(xí)過的函數(shù) 幾乎一樣谦铃。 區(qū)別在于第一個(gè)參數(shù)必須是 self,大家暫時(shí)先記住榔昔,稍后介紹 self驹闰。
2.2 創(chuàng)建對象
當(dāng)一個(gè)類定義完成之后凿跳,要使用這個(gè)類來創(chuàng)建對象,語法格式如下:
對象變量 = 類名()
第一個(gè)面向?qū)ο蟪绦?/p>
需求
- 小貓 愛 吃 魚疮方,小貓 要 喝 水控嗜。
分析
- 定義一個(gè)貓類 Cat。
- 定義兩個(gè)方法 eat 和 drink骡显。
class Cat:
"""這是一個(gè)貓類"""
def eat(self):
print("小貓愛吃魚")
def drink(self):
print("小貓?jiān)诤人?)
tom = Cat()
tom.drink()
tom.eat()
使用 Cat 類再創(chuàng)建一個(gè)對象
lazy_cat = Cat()
lazy_cat.eat()
lazy_cat.drink()
3)self參數(shù)
和我們以往的經(jīng)驗(yàn)不同疆栏,我們在調(diào)用對象的方法是不需要傳遞self參數(shù),這個(gè)self參數(shù)是系統(tǒng)自動傳遞到方法里的惫谤。我們需要知道這個(gè)self到底是什么壁顶?
3.1 給對象增加屬性
在 Python 中,要 給對象設(shè)置屬性溜歪,非常的容易若专,但是不推薦使用。 因?yàn)椋簩ο髮傩缘姆庋b應(yīng)該封裝在類的內(nèi)部蝴猪。 要 給對象設(shè)置屬性调衰,只需要在 類的外部的代碼 中直接通過.設(shè)置一個(gè)屬性即可
tom.name = "Tom"
lazy_cat.name = "大懶貓"
3.2 理解self到底是什么
class Cat:
"""這是一個(gè)貓類"""
def eat(self):
print(f"小貓愛吃魚,我是{self.name},self的地址是{id(self)}")
def drink(self):
print("小貓?jiān)诤人?)
tom = Cat()
print(f'tom對象的id是{id(tom)}')
tom.name = "Tom"
tom.eat()
print('-'*60)
lazy_cat = Cat()
print(f'lazy_cat對象的id是{id(lazy_cat)}')
lazy_cat.name = "大懶貓"
lazy_cat.eat()
輸出
tom對象的id是36120000
小貓愛吃魚,我是Tom,self的地址是36120000
------------------------------------------------------------
lazy_cat對象的id是36120056
小貓愛吃魚,我是大懶貓,self的地址是36120056
由輸出可見,當(dāng)我們使用
x對象.x方法()
的時(shí)候自阱,python會自動將x對象做為實(shí)參傳給x方法的self參數(shù)嚎莉。 也可以這樣記憶,誰點(diǎn)(.)的方法沛豌,self就是誰趋箩。
類中的每個(gè)實(shí)例方法的第一個(gè)參數(shù)都是self
4)初始化方法init()
之前代碼存在的問題 —— 在類的外部給對象增加屬性
- 將案例代碼進(jìn)行調(diào)整,先調(diào)用方法 再設(shè)置屬性加派,觀察一下執(zhí)行效果
tom = Cat()
tom.drink()
tom.eat()
tom.name = "Tom"
print(tom)
- 程序執(zhí)行報(bào)錯如下:
AttributeError: 'Cat' object has no attribute 'name'
屬性錯誤:'Cat' 對象沒有 'name' 屬性
提示
- 在日常開發(fā)中叫确,不推薦在 類的外部 給對象增加屬性。
- 如果在運(yùn)行時(shí)芍锦,沒有找到屬性竹勉,程序會報(bào)錯。
- 對象應(yīng)該包含有哪些屬性醉旦,應(yīng)該 封裝在類的內(nèi)部饶米。
初始化方法
- 當(dāng)使用 類名() 創(chuàng)建對象時(shí),會 自動 執(zhí)行以下操作:
1)為對象在內(nèi)存中 分配空間 —— 創(chuàng)建對象(調(diào)用new车胡,以后說)
2)為對象的屬性 設(shè)置初始值 —— 初始化方法(調(diào)用init并且將第一步創(chuàng)建的對象檬输,通過self參數(shù)傳給init) - 這個(gè) 初始化方法 就是 init 方法,init 是對象的內(nèi)置方法(有的書也叫魔法方法匈棘,特殊方法)
init 方法是 專門 用來定義一個(gè)類 具有哪些屬性并且給出這些屬性的初始值的方法丧慈!
在 Cat 中增加 init 方法,驗(yàn)證該方法在創(chuàng)建對象時(shí)會被自動調(diào)用
class Cat:
"""這是一個(gè)貓類"""
def __init__(self):
print("初始化方法")
在初始化方法內(nèi)部定義屬性
- 在 init 方法內(nèi)部使用 self.屬性名 = 屬性的初始值 就可以 定義屬性
- 定義屬性之后,再使用 Cat 類創(chuàng)建的對象逃默,都會擁有該屬性
class Cat:
def __init__(self):
print("這是一個(gè)初始化方法")
# 定義用 Cat 類創(chuàng)建的貓對象都有一個(gè) name 的屬性
self.name = "Tom"
def eat(self):
print("%s 愛吃魚" % self.name)
# 使用類名()創(chuàng)建對象的時(shí)候鹃愤,會自動調(diào)用初始化方法 __init__
tom = Cat()
tom.eat()
改造初始化方法 —— 初始化的同時(shí)設(shè)置初始值
- 在開發(fā)中,如果希望在 創(chuàng)建對象的同時(shí)完域,就設(shè)置對象的屬性软吐,可以對 init 方法進(jìn)行 改造
1). 把希望設(shè)置的屬性值,定義成 init 方法的參數(shù)
2). 在方法內(nèi)部使用 self.屬性 = 形參 接收外部傳遞的參數(shù)
3). 在創(chuàng)建對象時(shí)吟税,使用 類名(屬性1, 屬性2...) 調(diào)用
class Cat:
def __init__(self, name):
print("初始化方法 %s" % name)
self.name = name
...
tom = Cat("Tom")
...
lazy_cat = Cat("大懶貓")
...
5)_str_()方法
在 Python 中凹耙,使用 print 輸出 對象變量,默認(rèn)情況下肠仪,會輸出這個(gè)變量的類型肖抱,以及 在內(nèi)存中的地址(十六進(jìn)制表示)
class Cat:
def __init__(self, new_name):
self.name = new_name
print("%s 來了" % self.name)
def __str__(self):
return "我是小貓:%s" % self.name
tom = Cat("Tom")
print(tom)
輸出
Tom 來了
<__main__.Cat object at 0x0000000002852278>
如果在開發(fā)中,希望使用 print 輸出 對象變量 時(shí)异旧,能夠打印 自定義的內(nèi)容意述,就可以利用 str 這個(gè)內(nèi)置方法了
注意:str 方法必須返回一個(gè)字符串
class Cat:
def __init__(self, new_name):
self.name = new_name
print("%s 來了" % self.name)
def __str__(self):
return "我是小貓:%s" % self.name
tom = Cat("Tom")
print(tom)
Tom 來了
我是小貓:Tom
6)面向?qū)ο髒s面向過程
def CarInfo(type,price):
print ("the car's type %s,price:%d"%(type,price))
print('函數(shù)方式(面向過程)')
CarInfo('passat',250000)
CarInfo('ford',280000)
class Car:
def __init__(self,type,price):
self.type = type
self.price = price
def printCarInfo(self):
print ("the car's Info in class:type %s,price:%d"%(self.type,self.price))
print('面向?qū)ο?)
carOne = Car('passat',250000)
carTwo = Car('ford',250000)
carOne.printCarInfo()
carTwo.printCarInfo()
輸出
函數(shù)方式(面向過程)
the car's type passat,price:250000
the car's type ford,price:280000
面向?qū)ο?the car's Info in class:type passat,price:250000
the car's Info in class:type ford,price:250000
類能實(shí)現(xiàn)的功能,用函數(shù)也可以實(shí)現(xiàn)吮蛹,那么類的存在還有什么特殊的意義呢荤崇? 新需求---加一個(gè)行駛里程的功能 面向過程實(shí)現(xiàn)
def CarInfo(type,price):
print ("the car's type %s,price:%d"%(type,price))
def driveDistance(oldDistance,distance):
newDistance = oldDistance + distance
return newDistance
print('函數(shù)方式(面向過程)')
CarInfo('passat',250000)
distance = 0
distance = driveDistance(distance,100)
distance = driveDistance(distance,200)
print(f'passat已經(jīng)行駛了{(lán)distance}公里')
輸出
函數(shù)方式(面向過程)
the car's type passat,price:250000
passat已經(jīng)行駛了300公里
面向?qū)ο髮?shí)現(xiàn)
class Car:
def __init__(self,type,price):
self.type = type
self.price = price
self.distance = 0 #新車
def printCarInfo(self):
print ("the car's Info in class:type %s,price:%d"%(self.type,self.price))
def driveDistance(self,distance):
self.distance += distance
print(f'面向?qū)ο?)
carOne = Car('passat',250000)
carOne.printCarInfo()
carOne.driveDistance(100)
carOne.driveDistance(200)
print(f'passat已經(jīng)行駛了{(lán)carOne.distance}公里')
print(f'passat的價(jià)格是{carOne.price}')
輸出
the car's Info in class:type passat,price:250000
passat已經(jīng)行駛了300公里
passat的價(jià)格是250000
通過對比我們可以發(fā)現(xiàn):
面向?qū)ο蟮膶?shí)現(xiàn)方式封裝性更好,已經(jīng)行駛的公里數(shù)是對象內(nèi)部的屬性匹涮,對象自身負(fù)責(zé)管理天试,外部調(diào)用代碼無需管理槐壳。我們隨時(shí)可以調(diào)用對象的方法和屬性得知對象當(dāng)前的各種信息然低。而面向過程的方式而言,外部調(diào)用代碼會“手忙腳亂”
再加一個(gè)需求: 車每行駛1公里务唐,車的價(jià)值貶值10元 面向?qū)ο蟮膶?shí)現(xiàn)方式雳攘,只需添加一行代碼:
def driveDistance(self,distance):
self.distance += distance
self.price -= distance*10
全部代碼
class Car:
def __init__(self,type,price):
self.type = type
self.price = price
self.distance = 0 #新車
def printCarInfo(self):
print ("the car's Info in class:type %s,price:%d"%(self.type,self.price))
def driveDistance(self,distance):
self.distance += distance
self.price -= distance*10
print(f'面向?qū)ο?)
carOne = Car('passat',250000)
carOne.printCarInfo()
carOne.driveDistance(100)
carOne.driveDistance(200)
print(f'passat已經(jīng)行駛了{(lán)carOne.distance}公里')
print(f'passat的價(jià)格是{carOne.price}')
輸出
面向?qū)ο?the car's Info in class:type passat,price:250000
passat已經(jīng)行駛了300公里
passat的價(jià)格是247000
面向過程的方式,請自行實(shí)現(xiàn)(比較麻煩) 小結(jié) 可以拿公司運(yùn)營作為比喻來說明面向?qū)ο箝_發(fā)方式的優(yōu)點(diǎn)
外部調(diào)用代碼好比老板 面向過程中函數(shù)好比員工枫笛,讓員工完成一個(gè)任務(wù)吨灭,需要老板不斷的干涉,大大影響了老板的工作效率刑巧。 面向?qū)ο笾袑ο蠛帽葐T工喧兄,讓員工完成一個(gè)任務(wù),老板只要下命令即可啊楚,員工可以獨(dú)擋一面吠冤,大大節(jié)省了老板的時(shí)間。
還有一種說法
世界上本沒有類恭理,代碼寫多了拯辙,也就有了類
面向?qū)ο箝_發(fā)方式是晚于面向過程方式出現(xiàn)的,幾十年前颜价,面向?qū)ο筮€沒有出現(xiàn)以前涯保,很多軟件系統(tǒng)的代碼量就已經(jīng)達(dá)到幾十上百萬行诉濒,科學(xué)家為了組織這些代碼,將各種關(guān)系密切的數(shù)據(jù)夕春,和相關(guān)的方法分門別類未荒,放到一起管理,就形成了面向?qū)ο蟮拈_發(fā)思想和編程語言語法及志。
7)私有屬性-封裝
在實(shí)際開發(fā)中茄猫,對象 的 某些屬性或方法 可能只希望 在對象的內(nèi)部被使用,而 不希望在外部被訪問到
定義方式
在 定義屬性或方法時(shí)困肩,在 屬性名或者方法名前 增加 兩個(gè)下劃線__ 實(shí)際開發(fā)中私有屬性也不是一層不變的划纽。所以要給私有屬性提供外部能夠操作的方法。
7.1) 通過自定義getset方法提供私有屬性的訪問
class Person:
def __init__(self, name, age):
self.name = name
self.__age = age
#定義對私有屬性的get方法锌畸,獲取私有屬性
def getAge(self):
return self.__age
#定義對私有屬性的重新賦值的set方法勇劣,重置私有屬性
def setAge(self,age):
self.__age = age
person1 = Person("tom",19)
person1.setAge(20)
print(person1.name,person1.getAge()) #tom 20
7.2) 調(diào)用property方法提供私有屬性的訪問
class Student:
def __init__(self, name, age):
self.name = name
self.__age = age
#定義對私有屬性的get方法,獲取私有屬性
def getAge(self):
return self.__age
#定義對私有屬性的重新賦值的set方法潭枣,重置私有屬性
def setAge(self,age):
self.__age = age
p = property(getAge,setAge) #注意里面getAge,setAge不能帶()
s1 = Student("jack",22)
s1.p = 23 #如果使用=,則會判斷為賦值比默,調(diào)用setAge方法。
print(s1.name,s1.p) #jack 23 盆犁,直接使用s1.p會自動判斷會取值命咐,調(diào)用getAge
print(s1.name,s1.getAge()) #jack 23,這個(gè)時(shí)候set,get方法可以單獨(dú)使用。
7.3) 使用property標(biāo)注提供私有屬性的訪問
class Teacher:
def __init__(self, name, age,speak):
self.name = name
self.__age = age
self.__speak = speak
@property #注意1.@proterty下面默認(rèn)跟的是get方法谐岁,如果設(shè)置成set會報(bào)錯醋奠。
def age(self):
return self.__age
@age.setter #注意2.這里是使用的上面函數(shù)名.setter,不是property.setter.
def age(self,age):
if age > 150 and age <=0: #還可以在setter方法里增加判斷條件
print("年齡輸入有誤")
else:
self.__age = age
@property
def for_speak(self): #注意2.這個(gè)同名函數(shù)名可以自定義名稱伊佃,一般都是默認(rèn)使用屬性名窜司。
return self.__speak
@for_speak.setter
def for_speak(self, speak):
self.__speak = speak
t1 = Teacher("herry",45,"Chinese")
t1.age = 38 #注意4.有了property后,直接使用t1.age,而不是t1.age()方法了航揉。
t1.for_speak = "English"
print(t1.name,t1.age,t1.for_speak) #herry 38 English
8)將實(shí)例用作屬性-對象組合
使用代碼模擬實(shí)物時(shí)塞祈,你可能會發(fā)現(xiàn)自己給類添加的細(xì)節(jié)越來越多:屬性和方法清單以及文件都越來越長。在這種情況下帅涂,可能需要將類的一部分屬性和方法作為一個(gè)獨(dú)立的類提取出來议薪。你可以將大型類拆分成多個(gè)協(xié)同工作的小類。
8.1 實(shí)例1擺放家具
需求
- 房子(House) 有 戶型媳友、總面積 和 家具名稱列表
新房子沒有任何的家具 - 家具(HouseItem) 有 名字 和 占地面積斯议,其中
席夢思(bed) 占地 4 平米
衣柜(chest) 占地 2 平米
餐桌(table) 占地 1.5 平米 - 將以上三件 家具 添加 到 房子 中
- 打印房子時(shí),要求輸出:戶型庆锦、總面積捅位、剩余面積、家具名稱列表
剩余面積
- 在創(chuàng)建房子對象時(shí),定義一個(gè) 剩余面積的屬性艇搀,初始值和總面積相等
- 當(dāng)調(diào)用 add_item 方法尿扯,向房間 添加家具 時(shí),讓 剩余面積 -= 家具面積
思考:應(yīng)該先開發(fā)哪一個(gè)類焰雕?
答案 —— 家具類
1.家具簡單
2.房子要使用到家具衷笋,被使用的類,通常應(yīng)該先開發(fā)
創(chuàng)建家具
class HouseItem:
def __init__(self, name, area):
"""
:param name: 家具名稱
:param area: 占地面積
"""
self.name = name
self.area = area
def __str__(self):
return "[%s] 占地面積 %.2f" % (self.name, self.area)
# 1\. 創(chuàng)建家具
bed = HouseItem("席夢思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌", 1.5)
print(bed)
print(chest)
print(table)
小結(jié)
1.創(chuàng)建了一個(gè) 家具類矩屁,使用到 init 和 str 兩個(gè)內(nèi)置方法
2.使用 家具類 創(chuàng)建了 三個(gè)家具對象辟宗,并且 輸出家具信息
創(chuàng)建房間
class House:
def __init__(self, house_type, area):
"""
:param house_type: 戶型
:param area: 總面積
"""
self.house_type = house_type
self.area = area
# 剩余面積默認(rèn)和總面積一致
self.free_area = area
# 默認(rèn)沒有任何的家具
self.item_list = []
def __str__(self):
# Python 能夠自動的將一對括號內(nèi)部的代碼連接在一起
return ("戶型:%s\n總面積:%.2f[剩余:%.2f]\n家具:%s"
% (self.house_type, self.area,
self.free_area, self.item_list))
def add_item(self, item):
print("要添加 %s" % item)
# 2\. 創(chuàng)建房子對象
my_home = House("兩室一廳", 60)
my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
print(my_home)
小結(jié)
1.創(chuàng)建了一個(gè) 房子類,使用到 init 和 str 兩個(gè)內(nèi)置方法
2.準(zhǔn)備了一個(gè) add_item 方法 準(zhǔn)備添加家具
3.使用 房子類 創(chuàng)建了 一個(gè)房子對象
4.讓 房子對象 調(diào)用了三次 add_item 方法吝秕,將 三件家具 以實(shí)參傳遞到 add_item 內(nèi)部
添加家具
需求
1> 判斷 家具的面積 是否 超過剩余面積泊脐,如果超過,提示不能添加這件家具
2> 將 家具的名稱 追加到 家具名稱列表 中
3> 用 房子的剩余面積 - 家具面積
def add_item(self, item):
print("要添加 %s" % item)
# 1\. 判斷家具面積是否大于剩余面積
if item.area > self.free_area:
print("%s 的面積太大烁峭,不能添加到房子中" % item.name)
return
# 2\. 將家具的名稱追加到名稱列表中
self.item_list.append(item.name)
# 3\. 計(jì)算剩余面積
self.free_area -= item.area
小結(jié)
- 主程序只負(fù)責(zé)創(chuàng)建 房子 對象和 家具 對象
- 讓 房子 對象調(diào)用 add_item 方法 將家具添加到房子中
- 面積計(jì)算容客、剩余面積、家具列表 等處理都被 封裝 到 房子類的內(nèi)部
8.2 實(shí)例2英雄pk怪物
hero.py
from random import randint
class Hero:
def __init__(self, name,flood,strength):
self.name = name
self.flood = flood
self.strength = strength
def calc_health(self):
return self.flood
def take_damage(self, monster):
dmage = randint(monster.strength - 5, monster.strength + 5)
self.flood -= dmage
print(f"{self.name}你被{monster.name}攻擊约郁,受到了{(lán)str(dmage)}點(diǎn)傷害!還剩{str(self.flood)}滴血")
if self.calc_health() <= 0:
print(f"{self.name}你被殺死了!勝敗乃兵家常事 請重新來過缩挑。")
return True
else:
return False
monster.py
from random import randint
class Monster:
def __init__(self, name,flood,strength):
self.name = name
self.flood = flood
self.strength = strength
def calc_health(self):
return self.flood
def take_damage(self, hero):
dmage = randint(hero.strength - 5, hero.strength + 5)
self.flood -= dmage
print(f"{self.name}你被{hero.name}攻擊,受到了{(lán)str(dmage)}點(diǎn)傷害!還剩{str(self.flood)}滴血")
if self.calc_health() <= 0:
print(f"{self.name}你被殺死了!勝敗乃兵家常事 請重新來過鬓梅。")
return True
else:
return False
pkgame.py
from hero import Hero
from monsters import Monster
hero = Hero('張三',100, 10)
monster = Monster('小強(qiáng)',60,20)
while True:
is_monster_died = monster.take_damage(hero)
if is_monster_died:
break
is_hero_died = hero.take_damage(monster)
if is_hero_died:
break
某次輸出:
小強(qiáng)你被張三攻擊供置,受到了6點(diǎn)傷害!還剩54滴血
張三你被小強(qiáng)攻擊,受到了25點(diǎn)傷害!還剩75滴血
小強(qiáng)你被張三攻擊绽快,受到了9點(diǎn)傷害!還剩45滴血
張三你被小強(qiáng)攻擊芥丧,受到了19點(diǎn)傷害!還剩56滴血
小強(qiáng)你被張三攻擊,受到了10點(diǎn)傷害!還剩35滴血
張三你被小強(qiáng)攻擊谎僻,受到了23點(diǎn)傷害!還剩33滴血
小強(qiáng)你被張三攻擊娄柳,受到了11點(diǎn)傷害!還剩24滴血
張三你被小強(qiáng)攻擊,受到了22點(diǎn)傷害!還剩11滴血
小強(qiáng)你被張三攻擊艘绍,受到了10點(diǎn)傷害!還剩14滴血
張三你被小強(qiáng)攻擊,受到了24點(diǎn)傷害!還剩-13滴血
張三你被殺死了!勝敗乃兵家常事 請重新來過秫筏。
9)類屬性 類方法 靜態(tài)方法
9.1 類對象诱鞠,實(shí)例對象
類對象:類名 實(shí)例對象:類創(chuàng)建的對象
類屬性就是類對象所擁有的屬性,它被所有類對象的實(shí)例對象所共有这敬,在內(nèi)存中只存在一個(gè)副本航夺,這個(gè)和C++、Java中類的靜態(tài)成員變量有點(diǎn)類似崔涂。對于公有的類屬性阳掐,在類外可以通過類對象和實(shí)例對象訪問
# 類屬性
class people:
name="Tom" #公有的類屬性
__age=18 #私有的類屬性
p=people()
print(p.name) #實(shí)例對象
print(people.name) #類對象
# print(p.__age) #錯誤 不能在類外通過實(shí)例對象訪問私有的類屬性
print(people.__age) #錯誤 不能在類外同過類對象訪問私有的類屬性
實(shí)例屬性
class people:
name="tom"
p=people()
p.age=18
print(p.name)
print(p.age) #實(shí)例屬性是實(shí)例對象特有的,類對象不能擁有
print(people.name)
#print(people.age) #錯誤:實(shí)例屬性,不能通過類對象調(diào)用
我們經(jīng)常將實(shí)例屬性放在構(gòu)造方法中
class people:
name="tom"
def __init__(self,age):
self.age=age
p=people(18)
print(p.name)
print(p.age) #實(shí)例屬性是實(shí)例對象特有的缭保,類對象不能擁有
print(people.name)
# print(people.age) #錯誤:實(shí)例屬性汛闸,不能通過類對象調(diào)用
類屬性和實(shí)例屬性混合
class people:
name="tom" #類屬性:實(shí)例對象和類對象可以同時(shí)調(diào)用
def __init__(self,age): #實(shí)例屬性
self.age=age
p=people(18) #實(shí)例對象
p.sex="男" #實(shí)例屬性
print(p.name)
print(p.age) #實(shí)例屬性是實(shí)例對象特有的,類對象不能擁有
print(p.sex)
print(people.name) #類對象
# print(people.age) #錯誤:實(shí)例屬性艺骂,不能通過類對象調(diào)用
# print(people.sex) #錯誤:實(shí)例屬性诸老,不能通過類對象調(diào)用
如果在類外修改類屬性,必須通過類對象去引用然后進(jìn)行修改钳恕。如果通過實(shí)例對象去引用别伏,會產(chǎn)生一個(gè)同名的實(shí)例屬性,這種方式修改的是實(shí)例屬性忧额,不會影響到類屬性厘肮,并且如果通過實(shí)例對象引用該名稱的屬性,實(shí)例屬性會強(qiáng)制屏蔽掉類屬性睦番,即引用的是實(shí)例屬性轴脐,除非刪除了該實(shí)例屬性
class Animal:
name="Panda"
print(Animal.name) #類對象引用類屬性
p=Animal()
print(p.name) #實(shí)例對象引用類屬性時(shí),會產(chǎn)生一個(gè)同名的實(shí)例屬性
p.name="dog" #修改的只是實(shí)例屬性的抡砂,不會影響到類屬性
print(p.name) #dog
print(Animal.name) #panda
# 刪除實(shí)例屬性
del p.name
print(p.name)
9.2 類屬性的應(yīng)用場景
比如大咱,我們有一個(gè)班級類,創(chuàng)建班級對象的時(shí)候注益,需要按序號指定班級名稱碴巾,我們就需要知道當(dāng)前已經(jīng)創(chuàng)建了多少個(gè)班級對象,這個(gè)數(shù)量可以設(shè)計(jì)成類屬性
class NeuEduClass:
class_num = 0
def __init__(self):
self.class_name = f'東軟睿道Python{NeuEduClass.class_num+1}班'
NeuEduClass.class_num += 1
classList = [NeuEduClass() for i in range(10)]
for c in classList:
print(c.class_name)
輸出
東軟睿道Python1班
東軟睿道Python2班
東軟睿道Python3班
東軟睿道Python4班
東軟睿道Python5班
東軟睿道Python6班
東軟睿道Python7班
東軟睿道Python8班
東軟睿道Python9班
東軟睿道Python10班
9.3 類方法
類對象所擁有的方法丑搔,需要用修飾器@classmethod來標(biāo)識其為類方法厦瓢,對于類方法,第一個(gè)參數(shù)必須是類對象啤月,一般以cls作為第一個(gè)參數(shù)(當(dāng)然可以用其他名稱的變量作為其第一個(gè)參數(shù)煮仇,但是大部分人都習(xí)慣以'cls'作為第一個(gè)參數(shù)的名字),能夠通過實(shí)例對象和類對象去訪問谎仲。
class people:
country="china"
@classmethod
def getCountry(cls):
return cls.country
p=people()
print(p.getCountry()) #實(shí)例對象調(diào)用類方法
print(people.getCountry()) #類對象調(diào)用類方法
類方法還有一個(gè)用途就是可以對類屬性進(jìn)行修改:
class people:
country="china"
@classmethod
def getCountry(cls):
return cls.country
@classmethod
def setCountry(cls,country):
cls.country=country
p=people()
print(p.getCountry()) #實(shí)例對象調(diào)用類方法
print(people.getCountry()) #類對象調(diào)用類方法
p.setCountry("Japan")
print(p.getCountry())
print(people.getCountry())
9.4 靜態(tài)方法
需要通過修飾器@staticmethod來進(jìn)行修飾浙垫,靜態(tài)方法不需要定義參數(shù)
class people3:
country="china"
@staticmethod
def getCountry():
return people3.country
p=people3()
print(p.getCountry()) #實(shí)例對象調(diào)用類方法
print(people3.getCountry()) #類對象調(diào)用類方法
10)繼承
繼承的概念:子類 自動擁有(繼承) 父類 的所有 方法 和 屬性 1) 繼承的語法
class 類名(父類名):
pass
- 子類 繼承自 父類,可以直接 享受 父類中已經(jīng)封裝好的方法郑诺,不需要再次開發(fā)
- 子類 中應(yīng)該根據(jù) 職責(zé)夹姥,封裝 子類特有的 屬性和方法
- 當(dāng) 父類 的方法實(shí)現(xiàn)不能滿足子類需求時(shí),可以對方法進(jìn)行 重寫(override)
舉一個(gè)常見的例子辙诞。Circle 和 Rectangle 辙售,不同的圖形,面積(area)計(jì)算方式不同飞涂。
import math
class Circle:
def __init__(self, color, r):
self.r = r
self.color = color
def area(self):
return math.pi * self.r * self.r
def show_color(self):
print(self.color)
class Rectangle:
def __init__(self, color, a, b):
self.color = color
self.a, self.b = a, b
def area(self):
return self.a * self.b
def show_color(self):
print(self.color)
circle = Circle('red', 3.0)
print(circle.area())
circle.show_color()
rectangle = Rectangle('blue', 2.0, 3.0)
print(rectangle.area())
rectangle.show_color()
輸出
28.274333882308138
red
6.0
blue
我們看到Rectangle和Circle有同樣的屬性color和方法showcolor 我們可以定義一個(gè)父類Shape旦部,將Rectangle和Circle通用的部分提取到Shape類中祈搜,然后在子類的init方法中,通過 調(diào)用super()init(color). 把 color 傳給父類的 _init()
import math
class Shape:
def __init__(self, color):
self.color = color
def area(self):
return None
def show_color(self):
print(self.color)
class Circle(Shape):
def __init__(self, color, r):
super().__init__(color)
# Shape.__init__(self,color) #這樣也行士八,但是不好(考慮父類Shape的名字改變了容燕,怎么辦)
self.r = r
def area(self):
return math.pi * self.r * self.r
class Rectangle(Shape):
def __init__(self, color, a, b):
super().__init__(color)
# Shape.__init__(self, color) #這樣也行,但是不好(考慮父類Shape的名字改變了曹铃,怎么辦)
self.a, self.b = a, b
def area(self):
return self.a * self.b
circle = Circle('red', 3.0)
print(circle.area())
circle.show_color()
rectangle = Rectangle('blue', 2.0, 3.0)
print(rectangle.area())
rectangle.show_color()
輸出
28.274333882308138
red
6.0
blue
子類Circle和Rectangle本身并沒有定義show_color方法缰趋, 從父類Shape繼承了show_color方法。子類Circle和Rectangle改寫(Override)了父類的area方法陕见,分別提供了自己不同的實(shí)現(xiàn)秘血。
注釋掉Circle類中init方法中super()._init_()這行代碼:
class Circle(Shape):
def __init__(self, color, r):
# super().__init__(color)
# Shape.__init__(self,color) #這樣也行,但是不好(考慮父類Shape的名字改變了评甜,怎么辦)
self.r = r
再次運(yùn)行代碼灰粮,報(bào)錯:
File "C:/Users/Administrator/PycharmProjects/untitled16/shape.py", line 12, in show_color
print(self.color)
AttributeError: 'Circle' object has no attribute 'color'
錯誤輸出指定的出錯位置在:print(self.color),說Circle類型對象沒有color屬性
class Shape:
def show_color(self):
print(self.color)
問題原因是忍坷,Circle類型重寫了父類Shape的_init_()方法粘舟,導(dǎo)致父類Shape的__init_()方法沒有被調(diào)用到,所以
一定不要忘記在子類的init方法中調(diào)用super()._init_()
10.1) 重構(gòu)英雄pk怪物
我們發(fā)現(xiàn)Hero類和Monster類中有很多代碼是重復(fù)的佩研,我們把這些重復(fù)的代碼提取到一個(gè)父類Sprite(精靈)中柑肴。 sprite.py
from random import randint
class Sprite:
def __init__(self, flood,strength):
self.flood = flood
self.strength = strength
def calc_health(self):
return self.flood
def take_damage(self, attack_sprite):
damage = randint(attack_sprite.strength - 5, attack_sprite.strength + 5)
self.flood -= damage
print(f"{self.name}你被{attack_sprite.name}攻擊,受到了{(lán)str(damage)}點(diǎn)傷害!還剩{str(self.flood)}滴血")
if self.calc_health() <= 0:
print(f"{self.name}你被殺死了!勝敗乃兵家常事 請重新來過旬薯。")
return True
else:
return False
hero.py
from sprite import Sprite
class Hero(Sprite):
def __init__(self, name,flood,strength):
self.name = name
super().__init__(flood,strength)
monster.py
from sprite import Sprite
class Monster(Sprite):
def __init__(self, name,flood,strength):
self.name = name
super().__init__(flood, strength)
pkgame.py不變
from hero import Hero
from monsters import Monster
hero = Hero('張三',100, 10)
monster = Monster('小強(qiáng)',60,20)
while True:
is_monster_died = monster.take_damage(hero)
if is_monster_died:
break
is_hero_died = hero.take_damage(monster)
if is_hero_died:
break
輸出
小強(qiáng)你被張三攻擊晰骑,受到了5點(diǎn)傷害!還剩55滴血
張三你被小強(qiáng)攻擊,受到了19點(diǎn)傷害!還剩81滴血
小強(qiáng)你被張三攻擊绊序,受到了9點(diǎn)傷害!還剩46滴血
張三你被小強(qiáng)攻擊硕舆,受到了25點(diǎn)傷害!還剩56滴血
小強(qiáng)你被張三攻擊,受到了12點(diǎn)傷害!還剩34滴血
張三你被小強(qiáng)攻擊骤公,受到了21點(diǎn)傷害!還剩35滴血
小強(qiáng)你被張三攻擊抚官,受到了5點(diǎn)傷害!還剩29滴血
張三你被小強(qiáng)攻擊,受到了23點(diǎn)傷害!還剩12滴血
小強(qiáng)你被張三攻擊阶捆,受到了13點(diǎn)傷害!還剩16滴血
張三你被小強(qiáng)攻擊凌节,受到了16點(diǎn)傷害!還剩-4滴血
張三你被殺死了!勝敗乃兵家常事 請重新來過。
進(jìn)一步改造 假設(shè)我們想傷害值damage的大小趁猴,不在Sprite類里統(tǒng)一處理刊咳,而由各個(gè)具體的精靈(Hero或Monster)自己來決定,代碼可以改造成如下: 在Sprite類中添加get_damage_value方法儡司,不提供實(shí)現(xiàn)
from random import randint
class Sprite:
def __init__(self, flood,strength):
self.flood = flood
self.strength = strength
def calc_health(self):
return self.flood
def get_damage_value(self):
pass
def take_damage(self, attack_sprite):
# damage = randint(attack_sprite.strength - 5, attack_sprite.strength + 5)
damage = attack_sprite.get_damage_value();
self.flood -= damage
print(f"{self.name}你被{attack_sprite.name}攻擊,受到了{(lán)str(damage)}點(diǎn)傷害!還剩{str(self.flood)}滴血")
if self.calc_health() <= 0:
print(f"{self.name}你被殺死了!勝敗乃兵家常事 請重新來過余指。")
return True
else:
return False
hero.py提供自己get_damage_value方法的實(shí)現(xiàn)
from random import randint
from sprite import Sprite
class Hero(Sprite):
def __init__(self, name,flood,strength):
self.name = name
super().__init__(flood,strength)
def get_damage_value(self):
return randint(self.strength - 3, self.strength + 3)
monster.py提供自己get_damage_value方法的實(shí)現(xiàn)
from random import randint
from sprite import Sprite
class Monster(Sprite):
def __init__(self, name,flood,strength):
self.name = name
super().__init__(flood, strength)
def get_damage_value(self):
return randint(self.strength - 7, self.strength + 7)
輸出
小強(qiáng)你被張三攻擊捕犬,受到了8點(diǎn)傷害!還剩52滴血
張三你被小強(qiáng)攻擊跷坝,受到了13點(diǎn)傷害!還剩87滴血
小強(qiáng)你被張三攻擊,受到了11點(diǎn)傷害!還剩41滴血
張三你被小強(qiáng)攻擊碉碉,受到了19點(diǎn)傷害!還剩68滴血
小強(qiáng)你被張三攻擊柴钻,受到了7點(diǎn)傷害!還剩34滴血
張三你被小強(qiáng)攻擊,受到了22點(diǎn)傷害!還剩46滴血
小強(qiáng)你被張三攻擊垢粮,受到了9點(diǎn)傷害!還剩25滴血
張三你被小強(qiáng)攻擊贴届,受到了25點(diǎn)傷害!還剩21滴血
小強(qiáng)你被張三攻擊,受到了9點(diǎn)傷害!還剩16滴血
張三你被小強(qiáng)攻擊蜡吧,受到了24點(diǎn)傷害!還剩-3滴血
張三你被殺死了!勝敗乃兵家常事 請重新來過毫蚓。
11)_new_()方法
python中定義的類在創(chuàng)建實(shí)例對象的時(shí)候,會自動執(zhí)行init()方法昔善,但是在執(zhí)行init()方法之前元潘,會執(zhí)行new()方法。
new()的作用主要有兩個(gè)君仆。
1.在內(nèi)存中為對象分配空間
2.返回對象的引用翩概。(即對象的內(nèi)存地址)
python解釋器在獲得引用的時(shí)候會將其傳遞給init()方法中的self。
class A:
def __new__(cls,*args,**kwargs):
print('__new__')
return super().__new__(cls)#這里一定要返回返咱,否則__init__()方法不會被執(zhí)行
def __init__(self):#這里的self就是new方法中的return返回值
print('__init__')
a = A()
輸出結(jié)果
__new__
__init__
我們一定要在new方法中最后調(diào)用
return super().__new__(cls)
否則init方法不會被調(diào)用
class A:
def __new__(cls,*args,**kwargs):
print('__new__')
# return super().__new__(cls)#這里一定要返回钥庇,否則__init__()方法不會被執(zhí)行
def __init__(self):#這里的self就是new方法中的return返回值
print('__init__')
a = A()
輸出
__new__
像以前一樣,我們不寫new方法試試
class A:
# def __new__(cls,*args,**kwargs):
# print('__new__')
# return super().__new__(cls)#這里一定要返回咖摹,否則__init__()方法不會被執(zhí)行
def __init__(self):#這里的self就是new方法中的return返回值
print('__init__')
a = A()
輸出
__init__
12)object
在Python中评姨,所有類型有個(gè)隱式的父類(object),上面的代碼相當(dāng)于
class A(object):
# def __new__(cls,*args,**kwargs):
# print('__new__')
# return super().__new__(cls)#這里一定要返回楞艾,否則__init__()方法不會被執(zhí)行
def __init__(self):#這里的self就是new方法中的return返回值
print('__init__')
a = A()
當(dāng)我們的類中沒有定義new方法時(shí)参咙,創(chuàng)建對象時(shí),Python會先調(diào)用父類(object)的new方法硫眯,在內(nèi)存中創(chuàng)建一個(gè)實(shí)例蕴侧,然后傳遞給我們的init方法。如果我們的自定義類型定義了new方法两入,就會覆蓋父類(object)的__new方法(父類的__new方法不會被自動調(diào)用)净宵,所以我們要顯式調(diào)用父類(object)的__new\方法,在內(nèi)存中創(chuàng)建一個(gè)實(shí)例裹纳。
那么new有什么作用呢择葡?我們可以改寫它,舉個(gè)例子剃氧,就比如說單例模式敏储。
13)單例模式
如果我們創(chuàng)建兩個(gè)實(shí)例:
a1 = A()
a2 = A()
那么id(a1)和id(a2)的值不一樣,也就是說python在內(nèi)容當(dāng)中創(chuàng)建了兩個(gè)實(shí)例對象朋鞍,用了兩份內(nèi)存已添。同樣的東西創(chuàng)建了兩份
如果想不管創(chuàng)建多少個(gè)實(shí)例對象妥箕,我們都讓它的id是一樣的。
也就是說更舞,先創(chuàng)建一個(gè)實(shí)例對象畦幢,之后不管創(chuàng)建多少個(gè),返回的永遠(yuǎn)都是第一個(gè)實(shí)例對象的內(nèi)存地址缆蝉∮畲校可以這樣實(shí)現(xiàn):
# 重寫new方法很固定,返回值必須是這個(gè)
# 這樣就避免了創(chuàng)建多份刊头。
# 創(chuàng)建第一個(gè)實(shí)例的時(shí)候黍瞧,_instance是None,那么會分配空間創(chuàng)建實(shí)例芽偏。
# 此時(shí)的類屬性已經(jīng)被修改雷逆,_instance不再為None
# 那么當(dāng)之后實(shí)例屬性被創(chuàng)建的時(shí)候,由于_instance不為None污尉。
# 則返回第一個(gè)實(shí)例對象的引用膀哲,即內(nèi)存地址。
# 這樣就應(yīng)用了單例模式被碗。
class A():
_instance = None
def __new__(cls,*args,**kwargs):
if A._instance == None:
A._instance = super().__new__(cls)
return A._instance
a1 = A()
print(id(a1))
a2 = A()
print(id(a2))
14)函數(shù)參數(shù)注解
你寫好了一個(gè)函數(shù)某宪,然后想為這個(gè)函數(shù)的參數(shù)增加一些額外的信息(每個(gè)參數(shù)的類型),這樣的話其他調(diào)用者就能清楚的知道這個(gè)函數(shù)應(yīng)該怎么使用锐朴。
解決方案
使用函數(shù)參數(shù)注解是一個(gè)很好的辦法兴喂,它能提示程序員應(yīng)該怎樣正確使用這個(gè)函數(shù)。 例如焚志,下面有一個(gè)被注解了的函數(shù):
def add(x:int, y:int) -> int:
return x + y
python解釋器不會對這些注解添加任何的語義衣迷。它們不會被類型檢查,運(yùn)行時(shí)跟沒有加注解之前的效果也沒有任何差距酱酬。 然而壶谒,對于那些閱讀源碼的人來講就很有幫助啦。第三方工具和框架可能會對這些注解添加語義膳沽。同時(shí)它們也會出現(xiàn)在文檔中汗菜。
>>> help(add)
Help on function add in module __main__:
add(x: int, y: int) -> int
>>>
盡管你可以使用任意類型的對象給函數(shù)添加注解(例如數(shù)字,字符串挑社,對象實(shí)例等等)陨界,不過通常來講使用類或者字符串會比較好點(diǎn)。
函數(shù)注解只存儲在函數(shù)的 annotations 屬性中痛阻。例如:
>>> add.__annotations__
{'y': <class 'int'>, 'return': <class 'int'>, 'x': <class 'int'>}
>>>
盡管注解的使用方法可能有很多種菌瘪,但是它們的主要用途還是文檔。 因?yàn)閜ython并沒有類型聲明阱当,通常來講僅僅通過閱讀源碼很難知道應(yīng)該傳遞什么樣的參數(shù)給這個(gè)函數(shù)麻车。 這時(shí)候使用注解就能給程序員更多的提示缀皱,讓他們可以正確的使用函數(shù)斗这。
函數(shù)參數(shù)注解還給我們帶來的好處有使得pycharm這樣的IDE可以自動提示該類型對象擁有的方法。
class A:
def dosomething(self):
print('hello')
def foo(a : A):
a.dosomething()
a = A()
foo(a)
在foo函數(shù)的定義中,如果我們沒有指定a:A拥褂,當(dāng)我們輸入a.時(shí)镇匀,IDE提示不出來dosomething方法