面向?qū)ο蠡A(chǔ)
- 面向?qū)ο缶幊蹋喊岩唤M數(shù)據(jù)結(jié)構(gòu)和處理它們的方法組成對(duì)象(object),把相同行為的對(duì)象歸納為類(class),通過(guò)類的封裝(encapsulation)隱藏內(nèi)部細(xì)節(jié),通過(guò)繼承(inheritance)實(shí)現(xiàn)類的特化(specialization)和泛化(generalization),通過(guò)多態(tài)(polymorphism)實(shí)現(xiàn)基于對(duì)象類型的動(dòng)態(tài)分派。
- 簡(jiǎn)單地說(shuō),類是對(duì)象的藍(lán)圖和模板,對(duì)象是類的實(shí)例。
- python中可以使用class關(guān)鍵字定義類,在類中通過(guò)函數(shù)定義方法,代碼如下。
class Student(object):
# __init__是一個(gè)特殊方法用于在創(chuàng)建對(duì)象時(shí)進(jìn)行初始化操作
# 通過(guò)這個(gè)方法我們可以為學(xué)生對(duì)象綁定name和age兩個(gè)屬性
def __init__(self, name, age):
self.name = name
self.age = age
def study(self, course_name):
print('%s正在學(xué)習(xí)%s.' % (self.name, course_name))
# PEP 8要求標(biāo)識(shí)符的名字用全小寫(xiě)多個(gè)單詞用下劃線連接
# 但是部分程序員和公司更傾向于使用駝峰命名法(駝峰標(biāo)識(shí))
def watch_movie(self):
if self.age < 18:
print('%s只能觀看《熊出沒(méi)》.' % self.name)
else:
print('%s正在觀看島國(guó)愛(ài)情大電影.' % self.name)
- 當(dāng)定義好類后,可以創(chuàng)建對(duì)象并給對(duì)象發(fā)消息。
def main():
# 創(chuàng)建學(xué)生對(duì)象并指定姓名和年齡
stu1 = Student('駱昊', 38)
# 給對(duì)象發(fā)study消息
stu1.study('Python程序設(shè)計(jì)')
# 給對(duì)象發(fā)watch_av消息
stu1.watch_movie()
stu2 = Student('王大錘', 15)
stu2.study('思想品德')
stu2.watch_movie()
if __name__ == '__main__':
main()
- 在python中,屬性和方法的訪問(wèn)權(quán)限只有兩種:公開(kāi)和私有。如果希望屬性是私有的,在給屬性命名時(shí)可以用兩個(gè)下劃線作為開(kāi)頭,代碼如下。
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
# AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# AttributeError: 'Test' object has no attribute '__foo'
print(test.__foo)
if __name__ == "__main__":
main()
- 但事實(shí)上,python并未從語(yǔ)法上嚴(yán)格保證其私有性,而只是給私有的屬性和方法換了個(gè)名字妨礙訪問(wèn),如果了解更換名字的規(guī)則就可以訪問(wèn)到,代碼如下。
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
test._Test__bar()
print(test._Test__foo)
if __name__ == "__main__":
main()
面向?qū)ο筮M(jìn)階
-
在實(shí)際開(kāi)發(fā)中,并不建議將屬性設(shè)為私有,這會(huì)導(dǎo)致子類無(wú)法訪問(wèn)。但如果直接將屬性暴露給外界也是有問(wèn)題的,建議是將屬性名以單下劃線開(kāi)頭,這樣既不會(huì)設(shè)為私有,又能夠表示其是受保護(hù)的,在訪問(wèn)該屬性時(shí)就要保持慎重。
如果想訪問(wèn)該類屬性,可以通過(guò)屬性的getter(訪問(wèn)器)和setter(修改器)方法進(jìn)行相應(yīng)的操作。
可以使用@property包裝器來(lái)包裝getter和setter方法,使得對(duì)屬性的訪問(wèn)既安全又方便。
class Person(object):
def __init__(self, name, age):
self._name = name
self._age = age
# 訪問(wèn)器 - getter方法
@property
def name(self):
return self._name
# 訪問(wèn)器 - getter方法
@property
def age(self):
return self._age
# 修改器 - setter方法
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飛行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('王大錘', 12)
person.play()
person.age = 22
person.play()
# person.name = '白元芳' # AttributeError: can't set attribute
if __name__ == '__main__':
main()
- python是一門動(dòng)態(tài)語(yǔ)言,允許在程序運(yùn)行時(shí)給對(duì)象綁定新的屬性或方法,也可對(duì)已綁定的進(jìn)行解綁。但如果需要限定自定義類型的對(duì)象只能綁定某些屬性,可以通過(guò)在類中定義_slots_變量來(lái)完成,_slots_的限定只對(duì)當(dāng)前類的對(duì)象生效,對(duì)子類不起作用。
class Person(object):
# 限定Person對(duì)象只能綁定_name, _age和_gender屬性
__slots__ = ('_name', '_age', '_gender')
def main():
person = Person('王大錘', 22)
person._gender = '男'
# AttributeError: 'Person' object has no attribute '_is_gay'
# person._is_gay = True
- 在類中定義的方法包括對(duì)象方法、靜態(tài)方法和類方法等。
- 對(duì)象方法是發(fā)送給對(duì)象的消息,而靜態(tài)方法只要定義了類,不必建立類的實(shí)例就可使用,屬于類本身而非類的某個(gè)對(duì)象。
from math import sqrt
class Triangle(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
@staticmethod # 靜態(tài)方法
def is_valid(a, b, c):
return a + b > c and b + c > a and a + c > b
def perimeter(self):
return self._a + self._b + self._c
def area(self):
half = self.perimeter() / 2
return sqrt(half * (half - self._a) *
(half - self._b) * (half - self._c))
def main():
a, b, c = 3, 4, 5
# 靜態(tài)方法和類方法都是通過(guò)給類發(fā)消息來(lái)調(diào)用的
if Triangle.is_valid(a, b, c):
t = Triangle(a, b, c)
print(t.perimeter())
# 也可以通過(guò)給類發(fā)消息來(lái)調(diào)用對(duì)象方法但是要傳入接收消息的對(duì)象作為參數(shù)
# print(Triangle.perimeter(t))
print(t.area())
# print(Triangle.area(t))
else:
print('無(wú)法構(gòu)成三角形.')
if __name__ == '__main__':
main()
- 類方法第一個(gè)參數(shù)約定名為cls,它代表當(dāng)前類相關(guān)信息的對(duì)象(類本身也是一個(gè)對(duì)象,也稱為類的元數(shù)據(jù)對(duì)象),通過(guò)該參數(shù)可以獲取和類相關(guān)的信息且創(chuàng)建類的對(duì)象。
from time import time, localtime, sleep
class Clock(object):
"""數(shù)字時(shí)鐘"""
def __init__(self, hour=0, minute=0, second=0):
self._hour = hour
self._minute = minute
self._second = second
@classmethod # 類方法
def now(cls):
ctime = localtime(time())
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
"""顯示時(shí)間"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
# 通過(guò)類方法創(chuàng)建對(duì)象并獲取系統(tǒng)時(shí)間
clock = Clock.now()
while True:
print(clock.show())
sleep(1)
clock.run()
if __name__ == '__main__':
main()
-
類與類之間有三種關(guān)系:is-a、has-a和use-a。
is-a關(guān)系即繼承或泛化,如學(xué)生-人。
has-a關(guān)系即關(guān)聯(lián),如部門-員工。如果是整體和部分的關(guān)聯(lián),即聚合關(guān)系;如果整體進(jìn)一步負(fù)責(zé)了部分的生命周期(整體和部分是不可分割的,同時(shí)同在也同時(shí)消亡),即為合成關(guān)系,屬于最強(qiáng)的關(guān)聯(lián)關(guān)系。
use-a關(guān)系即依賴,如司機(jī)的駕駛方法,其中參數(shù)用到了汽車。 -
面向?qū)ο笕筇匦裕悍庋b、繼承和多態(tài)。
封裝:隱藏一切可以隱藏的實(shí)現(xiàn)細(xì)節(jié),只向外界提供簡(jiǎn)單接口。在創(chuàng)建對(duì)象后,只需要調(diào)用方法就可以執(zhí)行,只需要知道方法的名字和傳入的參數(shù),而不需要知道方法內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)。
繼承:讓一個(gè)類從另一個(gè)類那里將屬性和方法直接繼承下來(lái),減少重復(fù)代碼的編寫(xiě)。提供信息的為父類,又稱超類或基類;繼承信息的為子類,又稱派生或衍生類。子類不僅可以繼承父類的屬性和方法,還可以定義自己的,在實(shí)際開(kāi)發(fā)中,通常會(huì)用子類對(duì)象替換父類對(duì)象,即里氏替換原則。
多態(tài):子類對(duì)父類的方法給出新的實(shí)現(xiàn)版本,稱為方法重寫(xiě)(override),從而讓父類的同一行為在子類中擁有不同版本。當(dāng)調(diào)用這個(gè)經(jīng)過(guò)子類重寫(xiě)的方法時(shí),不同子類對(duì)象會(huì)表現(xiàn)出不同的行為,即多態(tài)(poly-morphism)。
from abc import ABCMeta, abstractmethod
class Pet(object, metaclass=ABCMeta):
"""寵物"""
def __init__(self, nickname):
self._nickname = nickname
@abstractmethod
def make_voice(self):
"""發(fā)出聲音"""
pass
class Dog(Pet):
"""狗"""
def make_voice(self):
print('%s: 汪汪汪...' % self._nickname)
class Cat(Pet):
"""貓"""
def make_voice(self):
print('%s: 喵...喵...' % self._nickname)
def main():
pets = [Dog('旺財(cái)'), Cat('凱蒂'), Dog('大黃')]
for pet in pets:
pet.make_voice()
if __name__ == '__main__':
main()
- 上面代碼里,將Pet類處理為抽象類,即不能夠創(chuàng)建對(duì)象,只為讓其他類來(lái)繼承。python從語(yǔ)法層面沒(méi)有提供對(duì)抽象類的支持,但可以通過(guò)abc模塊的ABCMeta元類和abstractmethod包裝器來(lái)達(dá)到抽象類的效果,如果一個(gè)類中存在抽象方法它就不能實(shí)例化。Dog和Cat子類分別對(duì)Pet類中的make_voice抽象方法進(jìn)行了不同地重寫(xiě),在main函數(shù)中調(diào)用該方法時(shí),就表現(xiàn)出了多態(tài)行為。
整數(shù)比較
- 在python中比較兩個(gè)整數(shù)時(shí)有兩種運(yùn)算符:==和is:is比較的是兩者id值是否相等,即是否指向同一個(gè)地址;==比較兩者內(nèi)容是否相等,實(shí)際上調(diào)用了_eq_()方法。
- 矛盾1 代碼如下。
def main():
x = y = -1
while True:
x += 1
y += 1
if x is y:
print('%d is %d' % (x, y))
else:
print('Attention! %d is not %d' % (x, y))
break
x = y = 0
while True:
x -= 1
y -= 1
if x is y:
print('%d is %d' % (x, y))
else:
print('Attention! %d is not %d' % (x, y))
break
if __name__ == '__main__':
main()
- 矛盾1 解釋:python出于對(duì)性能的考慮,會(huì)將一些頻繁使用的整數(shù)對(duì)象緩存到一個(gè)叫small_ints的鏈表中,任何需要使用這些整數(shù)對(duì)象的時(shí)候,都不需要再重新創(chuàng)建,而是直接引用緩存。緩存的區(qū)間為[-5,256],當(dāng)使用is進(jìn)行比較時(shí),在該范圍內(nèi)的指向同一地址,而超出該范圍則為重新創(chuàng)建的,就會(huì)得到257 is 257結(jié)果為false的情況。
- 矛盾2 代碼如下:
import dis
a = 257
def main():
b = 257 # 第6行
c = 257 # 第7行
print(b is c) # True
print(a is b) # False
print(a is c) # False
if __name__ == "__main__":
main()
- 矛盾2 解釋:代碼塊是程序的最小執(zhí)行單位,在上述代碼中,a=257和main屬于兩個(gè)代碼塊。python為進(jìn)一步提高性能,規(guī)定在同一個(gè)代碼塊中創(chuàng)建的整數(shù)對(duì)象,即便超出[-5,256]的范圍,只要存在有值相同的整數(shù)對(duì)象,后續(xù)創(chuàng)建就直接引用。該規(guī)則對(duì)不在small_ints范圍內(nèi)的負(fù)數(shù)和負(fù)數(shù)值浮點(diǎn)數(shù)并不適用,但對(duì)非負(fù)浮點(diǎn)數(shù)和字符串都適用。因而c引用了b的257,而a與b不在同一個(gè)代碼塊內(nèi),才會(huì)得出注釋中的執(zhí)行結(jié)果。
- 導(dǎo)入dis模塊并在main()方法下添加“dis.dis(main)”一句,可進(jìn)行反匯編,從字節(jié)碼的角度來(lái)看該代碼。運(yùn)行結(jié)果中,代碼第6、7行的257,是從同一位置加載的,而第9行的a則是從不同地方加載的,因此引用的是不同的對(duì)象。
嵌套列表
- 把列表作為列表中的元素,即為嵌套列表,可以模擬現(xiàn)實(shí)中的表格、矩陣和棋盤(pán)等,但需謹(jǐn)慎使用,否則會(huì)出現(xiàn)問(wèn)題,代碼如下。
def main():
names = ['關(guān)羽', '張飛', '趙云', '馬超', '黃忠']
subjs = ['語(yǔ)文', '數(shù)學(xué)', '英語(yǔ)']
scores = [[0] * 3] * 5
for row, name in enumerate(names):
print('請(qǐng)輸入%s的成績(jī)' % name)
for col, subj in enumerate(subjs):
scores[row][col] = float(input(subj + ': '))
print(scores)
if __name__ == '__main__':
main()
- 上述代碼原本打算錄入五位同學(xué)的三門成績(jī),但最終輸出結(jié)果卻發(fā)現(xiàn),每個(gè)學(xué)生三門課程的成績(jī)是一樣的,而且就是最后錄入的那個(gè)學(xué)生的成績(jī)。解決此問(wèn)題,需要區(qū)分開(kāi)對(duì)象和對(duì)象的引用兩個(gè)概念,這就涉及到內(nèi)存中的棧和堆。
-
程序中可以使用的內(nèi)存從邏輯上可以分為五個(gè)部分,按照地址從高到低依次是:棧、堆、數(shù)據(jù)段、只讀數(shù)據(jù)段和代碼段。
其中,棧用來(lái)存儲(chǔ)局部、臨時(shí)變量,以及函數(shù)調(diào)用時(shí)保存和恢復(fù)現(xiàn)場(chǎng)需要用到的數(shù)據(jù),這部分內(nèi)存在代碼塊執(zhí)行開(kāi)始時(shí)自動(dòng)分配,結(jié)束時(shí)自動(dòng)釋放,通常由編譯器自動(dòng)管理。
堆的大小不固定,可以動(dòng)態(tài)地分配和回收,如果程序中有大量數(shù)據(jù)需要處理,通常都放在堆上。如果堆空間沒(méi)有被正確釋放,會(huì)引發(fā)內(nèi)存泄露的問(wèn)題,像Python、Java等都使用了垃圾回收機(jī)制(自動(dòng)回收不再使用的堆空間),來(lái)實(shí)現(xiàn)自動(dòng)化的內(nèi)存管理。 - 因此以如下代碼為例,變量a并不是真正的對(duì)象,而是對(duì)象的引用,相當(dāng)于記錄了對(duì)象在堆空間中的地址;同理,變量b是列表容器的引用,它引用了堆空間上的列表容器,而列表容器中并沒(méi)有保存真正的對(duì)象。
a = object()
b = ['apple', 'pitaya', 'grape']
- 再看最初的程序,對(duì)列表進(jìn)行[[0]*3]*5操作時(shí),僅僅是將[0,0,0]這個(gè)列表的地址進(jìn)行了復(fù)制,并沒(méi)有創(chuàng)建新的列表對(duì)象。所以容器中雖然有五個(gè)元素,卻都引用的是同一個(gè)列表對(duì)象,每次輸入都相當(dāng)于對(duì)該列表對(duì)象進(jìn)行了修改,因此最終顯示的即為最后一個(gè)學(xué)生的成績(jī),正確的代碼如下。
def main():
names = ['關(guān)羽', '張飛', '趙云', '馬超', '黃忠']
subjs = ['語(yǔ)文', '數(shù)學(xué)', '英語(yǔ)']
scores = [[]] * 5 # 或scores = [[0] * 3 for _ in range(5)]
for row, name in enumerate(names):
print('請(qǐng)輸入%s的成績(jī)' % name)
scores[row] = [0] * 3
for col, subj in enumerate(subjs):
scores[row][col] = float(input(subj + ': '))
print(scores)
if __name__ == '__main__':
main()
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
