加入星計劃,您可以享受以下權益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 類和實例
    • 數(shù)據(jù)封裝
    • 訪問限制
    • 繼承 & 多態(tài)
    • 動態(tài)語言
    • 獲取對象信息
    • 實例屬性和類屬性
  • 推薦器件
  • 相關推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

一文極速回顧面向對象編程OOP

07/01 11:29
636
閱讀需 26 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

面向對象編程——Object Oriented Programming,簡稱OOP,是一種程序設計思想。OOP把對象作為程序的基本單元,一個對象包含了數(shù)據(jù)和操作數(shù)據(jù)的函數(shù)。

  • 面向過程的程序設計把計算機程序視為一系列的命令集合,即一組函數(shù)的順序執(zhí)行。為了簡化程序設計,面向過程把函數(shù)繼續(xù)切分為子函數(shù),即把大塊函數(shù)通過切割成小塊函數(shù)來降低系統(tǒng)的復雜度。
  • 面向對象的程序設計把計算機程序視為一組對象的集合,而每個對象都可以接收其他對象發(fā)過來的消息,并處理這些消息,計算機程序的執(zhí)行就是一系列消息在各個對象之間傳遞。

Python中,所有數(shù)據(jù)類型都可以視為對象,當然也可以自定義對象。自定義的對象數(shù)據(jù)類型就是面向對象中的**類(Class)**的概念。Class是一種抽象概念,比如我們定義的Class——Student,是指學生這個概念,而實例(Instance)則是一個個具體的Student,比如,Bart Simpson和Lisa Simpson是兩個具體的Student。

所以,面向對象的設計思想是抽象出Class,根據(jù)Class創(chuàng)建Instance。面向對象的抽象程度又比函數(shù)要高,因為一個Class既包含數(shù)據(jù),又包含操作數(shù)據(jù)的方法。

class Student(object):
	def __init__(self, name, score):
        self.name = name
        self.score = score

	def print_score(self):
        print('%s: %s' % (self.name, self.score))

給對象發(fā)消息實際上就是調(diào)用對象對應的關聯(lián)函數(shù),我們稱之為對象的方法(Method)。面向對象的程序寫出來就像這樣:

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()

數(shù)據(jù)封裝、繼承多態(tài)是面向對象的三大特點,下面逐個介紹!

類和實例

class Student(object):
		pass

class后面緊接著是類名,即Student,類名通常是大寫開頭的單詞,緊接著是(object),表示該類是從哪個類繼承下來的,繼承的概念我們后面再講,通常,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。

定義好了Student類,就可以根據(jù)Student類創(chuàng)建出Student的實例

由于類可以起到模板的作用,因此,可以在創(chuàng)建實例的時候,把一些我們認為必須綁定的屬性強制填寫進去。通過定義一個特殊的__init__方法,在創(chuàng)建實例的時候,就把namescore等屬性綁上去:

class Student(object):
	def __init__(self, name, score):
        self.name = name
        self.score = score

注意:特殊方法“init”前后分別有兩個下劃線!??!

注意到__init__方法的第一個參數(shù)永遠是self,表示創(chuàng)建的實例本身,因此,在__init__方法內(nèi)部,就可以把各種屬性綁定到self,因為self就指向創(chuàng)建的實例本身。

有了__init__方法,在創(chuàng)建實例的時候,就不能傳入空的參數(shù)了,必須傳入與__init__方法匹配的參數(shù),但self不需要傳,Python解釋器自己會把實例變量傳進去:

>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

和普通的函數(shù)相比,在類中定義的函數(shù)只有一點不同,就是第一個參數(shù)永遠是實例變量self,并且,調(diào)用時不用傳遞該參數(shù)。除此之外,類的方法和普通函數(shù)沒有什么區(qū)別,所以,你仍然可以用默認參數(shù)、可變參數(shù)、關鍵字參數(shù)和命名關鍵字參數(shù)。

數(shù)據(jù)封裝

在上面的Student類中,每個實例就擁有各自的namescore這些數(shù)據(jù)。我們可以通過函數(shù)來訪問這些數(shù)據(jù),比如打印一個學生的成績:

>>>def print_score(std):
...     print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59

既然Student實例本身就擁有這些數(shù)據(jù),要訪問這些數(shù)據(jù),就沒有必要從外面的函數(shù)去訪問,可以直接在Student類的內(nèi)部定義訪問數(shù)據(jù)的函數(shù),這樣,就把“數(shù)據(jù)”給封裝起來了。這些封裝數(shù)據(jù)的函數(shù)是和Student
類本身是關聯(lián)起來的。

class Student(object):
	def __init__(self, name, score):
        self.name = name
        self.score = score

	def print_score(self):
        print('%s: %s' % (self.name, self.score))

要定義一個方法,除了第一個參數(shù)是self外,其他和普通函數(shù)一樣。要調(diào)用一個方法,只需要在實例變量上直接調(diào)用,除了self不用傳遞,其他參數(shù)正常傳入:

>>> bart.print_score()
Bart Simpson: 59

這樣一來,我們從外部看Student類,就只需要知道,創(chuàng)建實例需要給出namescore,而如何打印,都是在Student類的內(nèi)部定義的,這些數(shù)據(jù)和邏輯被“封裝”起來了,調(diào)用很容易,而且不用知道內(nèi)部實現(xiàn)的細節(jié)。

封裝的另一個好處是可以給Student類增加新的方法,比如get_grade

class Student(object):

    ...

	def get_grade(self):
		if self.score >= 90:
			return 'A'
		elif self.score >= 60:
			return 'B'
		else:
			return 'C'
lisa = Student('Lisa', 99)
bart = Student('Bart', 59)
print(lisa.name, lisa.get_grade())
print(bart.name, bart.get_grade())

結果就會打印出 Lisa A 和 Bart C。調(diào)用起來很方便。

訪問限制

如果要讓內(nèi)部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線__,在Python中,實例的變量名如果以__開頭,就變成了一個私有變量(private),只有內(nèi)部可以訪問,外部不能訪問,所以,我們把Student類改一改:

class Student(object):
	def __init__(self, name, score):
        self.__name = name
        self.__score = score

	def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

改完后,對于外部代碼來說,沒什么變動,但是已經(jīng)無法從外部訪問實例變量.__name實例變量.__score了:

>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

但是如果外部代碼要獲取name和score怎么辦?可以給Student類增加get_nameget_score這樣的方法:

class Student(object):
    ...

def get_name(self):
	return self.__name

def get_score(self):
	return self.__score

如果又要允許外部代碼修改score怎么辦?可以再給Student類增加set_score方法:

class Student(object):
    ...

def set_score(self, score):
        self.__score = score

繼承 & 多態(tài)

在OOP程序設計中,當我們定義一個class的時候,可以從某個現(xiàn)有的class繼承,新的class稱為子類(Subclass),而被繼承的class稱為基類、父類或超類(Base class、Super class)。繼承的最大好處就是子類可以繼承父類的全部功能

比如,我們已經(jīng)編寫了一個名為Animal的class,有一個run()方法可以直接打?。?/p>

class Animal(object):

	def run(self):
        print('Animal is running...')

當我們需要編寫DogCat類時,就可以直接從Animal繼承

class Dog(Animal):
	pass
	
class Cat(Animal):
	pass

對于Dog來說,Animal就是它的父類,對于Animal來說,Dog就是它的子類。 Cat和Dog類似。

class Dog(Animal):
	def run(self):
        print('Dog is running...')

class Cat(Animal):
	def run(self):
        print('Cat is running...')
dog = Dog()
dog.run()

cat = Cat()
cat.run()
Dog is running...
Cat is running...

當子類和父類都存在相同的run()方法時,我們說,子類的run()覆蓋了父類的run(),在代碼運行的時候,總是會調(diào)用子類的run()。這樣,我們就獲得了繼承的另一個好處:多態(tài)。

要理解什么是多態(tài),我們首先要對數(shù)據(jù)類型再作一點說明。當我們定義一個class的時候,我們實際上就定義了一種數(shù)據(jù)類型。我們定義的數(shù)據(jù)類型和Python自帶的數(shù)據(jù)類型,比如str、list、dict沒什么兩樣:

a = list() # a是list類型
b = Animal() # b是Animal類型
c = Dog() # c是Dog類型

但是等等,試試:

>>> isinstance(c, Animal)
True

看來c不僅僅是Dog,c還是Animal!

在繼承關系中,如果一個實例的數(shù)據(jù)類型是某個子類,那它的數(shù)據(jù)類型也可以被看做是父類,狗是狗,也是動物。但是,反過來就不行,狗是動物,但動物不是狗。

要理解多態(tài)的好處,我們還需要再編寫一個函數(shù),這個函數(shù)接受一個Animal類型的變量:

def run_twice(animal):
    animal.run()
    animal.run()
>>> run_twice(Animal())
Animal is running...
Animal is running...
>>> run_twice(Dog())
Dog is running...
Dog is running...
>>> run_twice(Cat())
Cat is running...
Cat is running...

看上去沒啥意思,但是仔細想想,現(xiàn)在,如果我們再定義一個Tortoise類型,也從Animal派生:

class Tortoise(Animal):
	def run(self):
        print('Tortoise is running slowly...')

當我們調(diào)用run_twice()時,傳入Tortoise的實例:

>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...

你會發(fā)現(xiàn),新增一個Animal的子類,不必對run_twice()做任何修改,實際上,任何依賴Animal作為參數(shù)的函數(shù)或者方法都可以不加修改地正常運行,原因就在于多態(tài)。

對于一個變量,我們只需要知道它是Animal類型,無需確切地知道它的子類型,就可以放心地調(diào)用run()方法,而具體調(diào)用的run()方法是作用在Animal、DogCat還是Tortoise對象上,由運行時該對象的確切類型決定,這就是多態(tài)真正的威力:調(diào)用方只管調(diào)用,不管細節(jié),而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調(diào)用的。 這就是著名的“開閉”原則:

  • 對擴展開放:允許新增Animal子類;
  • 對修改封閉:不需要修改依賴Animal類型的run_twice()等函數(shù)。

繼承還可以一級一級地繼承下來,就好比從爺爺?shù)桨职?、再到兒子這樣的關系。而任何類,最終都可以追溯到根類object,這些繼承關系看上去就像一顆倒著的樹。比如如下的繼承樹:

在這里插入圖片描述

動態(tài)語言

對于靜態(tài)語言(例如Java)來說,如果需要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,否則,將無法調(diào)用run()方法。

對于Python這樣的動態(tài)語言來說,則不一定需要傳入Animal類型。我們只需要保證傳入的對象有一個run()方法就可以了:

class Timer(object):
	def run(self):
        print('Start...')

這就是動態(tài)語言的“鴨子類型”!,它并不要求嚴格的繼承體系,一個對象只要“看起來像鴨子,走起路來像鴨子”,那它就可以被看做是鴨子。

繼承可以把父類的所有功能都直接拿過來,這樣就不必重零做起,子類只需要新增自己特有的方法,也可以把父類不適合的方法覆蓋重寫。

動態(tài)語言的鴨子類型特點決定了繼承不像靜態(tài)語言那樣是必須的。

獲取對象信息

type()

當我們拿到一個對象的引用時,如何知道這個對象是什么類型、有哪些方法呢?

基本類型都可以用type()判斷:

>>> type(123)
<class 'int'>
>>>type('str')
<class 'str'>
>>>type(None)
<type(None) 'NoneType'>

如果一個變量指向函數(shù)或者類,也可以用type()判斷:

>>> type(abs)
<class 'builtin_function_or_method'>
>>>type(a)
<class '__main__.Animal'>

但是type()函數(shù)返回的是什么類型呢?它返回對應的Class類型。如果我們要在if語句中判斷,就需要比較兩個變量的type類型是否相同:

在這里插入代碼片
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False

isinstance()

我們回顧上次的例子,如果繼承關系是:

object -> Animal -> Dog -> Husky

那么,isinstance()就可以告訴我們,一個對象是否是某種類型。先創(chuàng)建3種類型的對象:

>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Husky)
True

能用type()判斷的基本類型也可以用isinstance()判斷:

>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True

并且還可以判斷一個變量是否是某些類型中的一種,比如下面的代碼就可以判斷是否是list或者tuple

>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

總是優(yōu)先使用isinstance()判斷類型,可以將指定類型及其子類“一網(wǎng)打盡”。

dir()

使用dir() 獲得一個對象的所有屬性和方法。

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

getattr()、setattr()、hasattr()

僅僅把屬性和方法列出來是不夠的,配合getattr()、setattr()以及hasattr(),我們可以直接操作一個對象的狀態(tài):

>>> hasattr(obj, 'x')# 有屬性'x'嗎?
True
>>> obj.x
9
>>> hasattr(obj, 'y')# 有屬性'y'嗎?
False
>>> setattr(obj, 'y', 19)# 設置一個屬性'y'
>>> hasattr(obj, 'y')# 有屬性'y'嗎?
True
>>> getattr(obj, 'y')# 獲取屬性'y'
19
>>> obj.y# 獲取屬性'y'
19

實例屬性和類屬性

由于Python是動態(tài)語言,根據(jù)類創(chuàng)建的實例可以任意綁定屬性。直接在class中定義屬性,這種屬性是類屬性,歸Student類所有,直接給實例綁定屬性,叫實例屬性,實例屬性優(yōu)先級比類屬性高,它會屏蔽掉類的name屬性,但是類屬性并未消失,當實例屬性沒有找到,自動調(diào)用類屬性

>>>class Student(object): 
...     name = 'Student'
...
>>> s = Student()# 創(chuàng)建實例s
>>> print(s.name)# 打印name屬性,因為實例并沒有name屬性,所以會繼續(xù)查找class的name屬性
Student
>>> print(Student.name)# 打印類的name屬性
Student
>>> s.name = 'Michael'# 給實例綁定name屬性
>>> print(s.name)# 由于實例屬性優(yōu)先級比類屬性高,因此,它會屏蔽掉類的name屬性
Michael
>>> print(Student.name)# 但是類屬性并未消失,用Student.name仍然可以訪問
Student
>>>del s.name# 如果刪除實例的name屬性
>>> print(s.name)# 再次調(diào)用s.name,由于實例的name屬性沒有找到,類的name屬性就顯示出來了
Student

千萬不要對實例屬性和類屬性使用相同的名字,因為相同名稱的實例屬性將屏蔽掉類屬性,但是當你刪除實例屬性后,再使用相同的名稱,訪問到的將是類屬性。

OOP高級編程還有多重繼承、定制類、元類等概念,之后繼續(xù)學習再補充筆記。

附上Python文章的鏈接:

《Python簡介,無代碼》

《高效掌握Python——必備基礎》

《高效掌握Python——函數(shù)》

《高效掌握Python——高級特性》

《高效掌握Python——函數(shù)式編程》

《高效掌握Python——模塊,包》

本文內(nèi)容屬于筆記,大部分內(nèi)容源自 廖雪峰老師的博客, 非常推薦大家去他的網(wǎng)站學習!

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風險等級 參考價格 更多信息
AT24CM01-SSHD-T 1 Atmel Corporation EEPROM, 128KX8, Serial, CMOS, PDSO8, 0.150 INCH, GREEN, PLASTIC, MS-012AA, SOIC-8

ECAD模型

下載ECAD模型
$2.39 查看
KSZ8721BLI 1 Microchip Technology Inc DATACOM, ETHERNET TRANSCEIVER, PQFP48

ECAD模型

下載ECAD模型
$4.07 查看
TLP292-4(TP,E 1 Toshiba America Electronic Components AC INPUT-TRANSISTOR OUTPUT OPTOCOUPLER

ECAD模型

下載ECAD模型
$1.43 查看

相關推薦

電子產(chǎn)業(yè)圖譜