python(六)面向物件學習--類
內容概述
類(class)
屬性
例項變數(每個例項記憶體中)
類變數(類記憶體中)
私有屬性 __var
方法
構造方法
解構函式(預設就有,程式碼為空,寫了則相當於重構它)
物件(object):類的例項(例項化一個類之後得到的物件)
封裝:
把一些功能的實現細節不對外暴露
繼承:
(先覆蓋、再繼承、再新增)
程式碼複用
單繼承
多繼承
2.7 經典類 深度優先
新式類 廣度優先
3.x 均廣度優先
多型:
介面重用(一種介面,多種實現)
繼承方式:
繼承
組合
一、面向過程 VS 面向物件
1、程式設計正規化
程式設計正規化:(一組指令的集合,實現方式)
程式設計是 程式 員 用特定的語法+資料結構+演算法組成的程式碼來告訴計算機如何執行任務的過程 , 一個程式是程式設計師為了得到一個任務結果而編寫的一組指令的集合,正所謂條條大路通羅馬,實現一個任務的方式有很多種不同的方式,對這些不同的程式設計方式的特點進行歸納總結得出來的程式設計方式類別,即為程式設計正規化。
2、面向過程程式設計(Procedural Programming)
面向過程
使用一系列的指令告訴計算機一步一步的做什麼
就是程式從上到下一步步執行,一步步從上到下,從頭到尾的解決問題 。基本設計思路就是程式一開始是要著手解決一個大的問題,然後把一個大問題分解成很多個小問題或子過程,這些子過程再執行的過程再繼續分解直到小問題足夠簡單到可以在一個小步驟範圍內解決。
3、面向物件程式設計
面向物件介紹
世界萬物,皆可分類
世界萬物,皆為物件
只要是物件,就肯定屬於某種品類
只要是物件,就肯定有屬性
現實生活中就是這個樣子的,面向物件就是按照現實生活的這個規則
面向物件是利用“類”和“物件”來建立各種模型來實現對真實世界的描述,使用面向物件程式設計的原因一方面是因為它可以使程式的維護和擴充套件變得更簡單,並且可以大大提高程式開發效率 ,另外,基於面向物件的程式可以使它人更加容易理解你的程式碼邏輯,從而使團隊開發變得更從容。面向物件特性:
Class類: (屬性)# 例如:鳥的屬性
類即是對一類相同屬性的物件的抽象、藍圖、原形。
Object物件:(例項)# 例如:一個具體的鳥
一個物件即是一個類例項化後的例項
三大特性:
封裝:
再類中對資料的賦值、內部呼叫對外部使用者是透明的,這使類變成了一個膠囊或容器,裡面包含著類的資料和方法。比如人的消化系統、心臟、等封裝
繼承:一個類可以派生出子類,在這個父類裡定義的屬性、方法自動被子類繼承
多型:是面向物件的重要特性,簡單點說:“一個介面,多種實現”,提供統一介面
面向過程程式設計與面向的主要區別就是面向物件可以使程式更加容易擴充套件和易更改。
二、建立類和物件
1、建立類和物件
定義類:(object 所有類的父類)
class Dog(object): # 定義一個類, class是定義類的語法,Role是類名,(object)是新式類的寫法
n = 123 # 類變數
def __init__(self,name): # 建構函式--傳引數用
# 作用:在例項化時做一些累的初始化的工作。
self.name = name # 例項變數(靜態屬性),賦給例項
# 例項變數作用域就是例項本身
def bulk(self) # 類的方法(動態屬性),功能
print("%s wang wang!" % self.name)
建立例項(物件):
# 通過一個類建立一個具體物件的過程叫 例項化
dog1 = Dog("wangcai") # 建立例項,並賦值給變數
# dog1 是物件,又叫Dog這個類的例項
dog1.bulk() # 呼叫內部方法
2、第一個引數self是什麼作用呢
初始化一個例項,就需要呼叫這個類一次:
dog1 = Dog("wangcai") # 生成一個例項,會自動把引數傳給Dog下面的__init__(...)方法
上面建立dog1物件的時候,並沒有給self傳值,程式也沒未報錯,是因為,類在呼叫它自己的__init__建構函式時已經幫你給self引數賦值了
dog1 = Dog("wangcai") # 此時self 相當於 dog1 , dog1 = Dog(dog1, "wangcai")
當執行dog1 = Dog(“wangcai”)時,python的直譯器其實幹了兩件事:
- 在記憶體中開闢一塊空間指向dog1這個變數名
- 呼叫Dog這個類並執行其中的__init__(…)方法,相當於Dog.__init__(dog1, “wangcai”),這麼做是為什麼呢? 是為了把”wangcai”這個值跟剛開闢的dog1關聯起來,因為關聯起來後,你就可以直接dog1.name這樣來呼叫啦。所以,為實現這種關聯,在呼叫__init__方法時,就必須把dog1這個變數自己也傳進去,否則__init__不知道要把那引數跟誰關聯呀。
所以這個__init__(…)方法裡的,self.name = name等等的意思就是要把這幾個值 存到dog1的記憶體空間裡。
把變數dog1也傳進去了,再賦值給dog1,這就是self的作用
__init__(…)懂了,但後面的那幾個函式,噢 不對,後面那幾個方法 為什麼也還需要self引數麼? 不是在初始化例項的時候,就已經把屬性跟dog1繫結好了麼?
先來看一下上面類中的一個bulk的方法:
def bulk(self)
print("%s wang wang!" % self.name)
上面這個方法通過類呼叫的話要寫成如下:
dog1 = Dog("wangcai")
dog1.bulk() # #python 會自動幫你轉成 dog1.bulk(dog1)
依然沒給self傳值 ,但Python還是會自動的幫你把dog1 賦值給self這個引數, 為什麼呢? 因為,你在bulk(..)方法中可能要訪問dog1的一些其它屬性呀, 比如這裡就訪問了dog1的名字,怎麼訪問呢?你得告訴這個方法呀,於是就把dog1傳給了這個self引數,然後在bulk裡呼叫 self.name 就相當於呼叫dog1.name
總結一下2點:
- 上面的這個dog1 = Dog(“wangcai”)動作,叫做類的“例項化”, 就是把一個虛擬的抽象的類,通過這個動作,變成了一個具體的物件了, 這個物件就叫做例項
- 剛才定義的這個類體現了面向物件的第一個基本特性,封裝,其實就是使用構造方法將內容封裝到某個具體物件中,然後通過物件直接或者self間接獲取被封裝的內容
3、在誰的記憶體裡?(例項化過程)
上面定義了類Dog,並建立了一個例項dog1,那麼類變數和例項變數是什麼區別和聯絡?
類變數沒例項化就能列印,存在類的記憶體裡。
先找例項本身,例項本身沒有就去類裡找
例項化只拷貝建構函式,不拷貝類變數和其他方法,那些還只在類的記憶體中。
所以類變數可以存放所有例項一些共同的屬性,以節省空間。比如存放使用者資訊,使用者姓名等屬性可以放在例項變數裡,國籍都是中國,可以放在類變數裡。
但是,類變數和類方法方法不在例項中,呼叫怎麼呼叫呢?
dog1.bulk() # 實際是 :Dog.bulk(dog1)
>>> dog1 = Dog('wangcai')
>>> dog1.bulk()
wangcai wang wang
>>> Dog.bulk(dog1)
wangcai wang wang
所以類裡每個函式都必須至少有個self方法
4、例項屬性的增刪改
dog1.age = 3 # 增加屬性
del dog1.name # 刪除屬性
dog1.n = "111"
# 改類變數,實際不是改類變數,而是在例項裡新增一個屬性
Dog.n ="ccc" #改變類變數
5、解構函式、私有屬性和私有方法
解構函式:
跟建構函式相反
在例項釋放、銷燬的時候執行的,通常用於做一些收尾工作,如關閉一些資料庫連線、開啟的臨時檔案
如在上面的Dog類中新增一個解構函式
def __del__(self):
print("%s 掛了" %self.name)
類的私有屬性和私有方法:
現在類的私有屬性和私有方法,外邊可以直接呼叫,比如Dog.n。如果不想被呼叫,在前面加兩個下劃線,就變成了私有屬性或私有方法
三、面向物件的特性
1、封裝
前面提到:封裝,就是使用構造方法將內容封裝到某個具體物件中,然後通過物件直接或者self間接獲取被封裝的內容
封裝是面向物件的特徵之一,是物件和類概念的主要特性。
封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的資料和方法只讓可信的類或者物件操作,對不可信的進行資訊隱藏。
2、繼承
作用:省程式碼
面向物件程式設計 (OOP) 語言的一個主要功能就是“繼承”。繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴充套件。
- 通過繼承建立的新類稱為“子類”或“派生類”。
- 被繼承的類稱為“基類”、“父類”或“超類”。
繼承的過程,就是從一般到特殊的過程。
要實現繼承,可以通過“繼承”(Inheritance)和“組合”(Composition)來實現。
示例:
# class SchoolMember: 經典類寫法
class SchoolMember(object): # 新式類
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def tell(self):
pass
def __del__(self):
'''析構方法'''
print("\033[31;1mmember [%s] left the school\033[0m" %self.name)
class Teacher(SchoolMember): # 繼承父類SchoolMember
def __init__(self,name,age,sex,salary,course):
# 如何繼承父類的建構函式?直接定義則重構了父類的建構函式
# 繼承父類的建構函式,如下兩種寫法:
# SchoolMember.__init__(self,name,age,sex) # 經典類寫法
super(Teacher,self).__init__(name,age,sex) # 新式類寫法
self.salary = salary
self.course = course
def tell(self):
print('''
---- info of Teacher:%s ----
Name:%s
Age:%s
Sex:%s
Salary:%s
Course:%s
'''%(self.name,self.name,self.age,self.sex,self.salary,self.course))
def teach(self):
print("%s is teaching course [%s]" %(self.name,self.course))
class Student(SchoolMember):
def __init__(self,name,age,sex,stu_id,grade):
super(Student,self).__init__(name,age,sex)
self.stu_id = stu_id
self.grade = grade
def tell(self):
print('''
---- info of Student:%s ----
Name:%s
Age:%s
Sex:%s
Stu_id:%s
Grade:%s
''' % (self.name, self.name, self.age, self.sex, self.stu_id, self.grade))
def pay_tuition(self,amount):
print("%s has paid tution for $%s"% (self.name,amount) )
- 多繼承
上面提到了經典類和新式類:
經典類寫法:
class 類名: # 定義類
父類名.__init__(self,*args) # 繼承父類建構函式
新式類寫法:
class 類名(object): # 定義類
super(類名,self).__init__(name,*args) # 繼承父類
經典類和新式類區別主要體現在多繼承上順序問題,現在規範寫法用新式類
- 繼承順序
在python2和3版本中測試以下程式碼,分別註釋不同類中的建構函式,檢視D的例項繼承誰的建構函式
class A:
def __init__(self):
print("A")
class B(A):
# pass
def __init__(self):
print("B")
class C(A):
# pass
def __init__(self):
print("C")
class D(B,C):
# pass
def __init__(self):
print("D")
obj = D()
A # 父類
/ \
/ \
B C # B類和C類繼承A
\ /
\ /
D # D多繼承B、C
繼承查詢策略:
廣度優先:D->B->C->A(橫向先都查完)
繼承順序從左到右,找到建構函式就停下來,注意方法只是定義在記憶體中
深度優先:D->B->A, D->C->A
python 3: 都是統一廣度優先
python 2: 經典類是按深度優先繼承,新式類是按廣度優先繼承的
深度沒有廣度效率高
多繼承示例:
class People(object): # 新式類
def __init__(self,name,age):
self.name = name
self.age = age
self.friends = []
def eat(self):
print("%s is eating..." % self.name)
def talk(self):
print("%s is talking..." % self.name)
def sleep(self):
print("%s is sleeping..." % self.name)
class Relation(object):
# def __init__(self,n1,n2):
# print("init in relation")
def make_friends(self,obj): #w1
print("%s is making friends with %s" % (self.name,obj.name))
self.friends.append(obj.name)
class Man(Relation,People):
def __init__(self,name,age,money=10):
# People.__init__(self,name,age)
super(Man,self).__init__(name,age) # 新式類寫法
self.money = money
print("%s 一出生就有%s $" %(self.name,self.money))
def sleep(self):
People.sleep(self)
print("man is sleeping ")
class Woman(People,Relation):
def get_birth(self):
print("%s is born a baby...." % self.name)
m1 = Man("XiaoMing",22)
w1 = Woman("XiaoHong",20)
m1.make_friends(w1)
- 組合
另外一種繼承方式,嚴格意義上說不叫繼承
class Teacher(SchoolMember):
def __init__(self,name,age,sex,sourse, school_obj):
super(Teacher,self).__init__(name,age,sex)
# SchoolMember.__init__(self,name,age,sex)
self.school = school_obj # 組合方式,“繼承”
self.course = course
3、多型
一種介面,多種形態。
比如列印每類動物的叫聲,每次都呼叫不同的類名,不僅麻煩,而且要記住不同的類名,因此:
class Animal:
def __init__(self, name): # Constructor of the class
self.name = name
def talk(self): # Abstract method, defined by convention only
pass #raise NotImplementedError("Subclass must implement abstract method")
@staticmethod
def animal_talk(obj):
obj.talk()
class Cat(Animal):
def talk(self):
print('%s Meow!'%self.name)
class Dog(Animal):
def talk(self):
print('%s Woof! Woof!'%self.name)
d = Dog("GreyHound")
#d.talk()
c = Cat("Ocelot")
#c.talk()
# def animal_talk(obj):
# obj.talk()
Animal.animal_talk(c)
Animal.animal_talk(d)