什麼是類
百度百科:
類是對象的抽象,對象是對客觀事物的抽象。
用通俗的話來說:
類是類別的意思,是數據類型。
對象是類別下的具體事物。
也就是說:
類是數據類型,對象是變量。
比如:
自定義一種數據類型:水果。那水果就是類。
由此數據類型創建一個變量:一個蘋果。那這個蘋果就是對象。
類是抽象的,對象是具體的。
類和對象的關係
類是一種數據類型,相當於一個模板,用於創建對象。
每一個對象都有自己獨立的內存空間,用於存儲數據成員。
比如:
人都有名字,年齡,性別等信息。這是一個類,是抽象的。
小明叫張明,18歲,男。這是一個對象,是具體的。
專業術語
- 類
封裝了變量和函數的一種自定義的數據類型
- 數據成員
類中的變量
- 方法
類中的函數
- 對象
由類創建的變量稱作爲對象
- 實例化
創建一個對象。
因爲類是抽象的,而對象是具體的,是一個實例,所以通過類創建對象的過程稱爲實例化。
- 繼承
根據父類創建子類,子類 繼承 父類中所有變量和方法
數據成員
示例:
class Student: #定義類
pass
s=Student() #創建對象(類的實例化)
s.i=12345 #創建或修改類的數據成員
print(s.i) #訪問類的數據成員
結果:
12345
解釋:
使用關鍵字class定義一個類
類名爲Student,通常類名首字母大寫。
對象名爲s
i 爲數據成員
訪問類中成員的語法:對象名.成員名
每個對象都有單獨的內存空間去存儲自己的普通數據成員(後面會講到靜態數據成員,就不一樣了)
方法
類中的函數稱爲方法
方法可以訪問此類所有的數據成員和方法。
示例:
class Student:
def f(self): #定義方法
print("hello world")
s=Student() #創建對象(類的實例化)
s.f() #調用類的方法
結果:
hello world
解釋:
f 爲方法名
訪問類中成員的語法:對象名.成員名
類的方法與普通的函數區別:第一個參數爲self。
self實際上是對象的地址。所以通過self訪問的成員都屬於當前對象。
帶有參數的方法:
class Student:
def f(self,s): #定義方法
print(s)
s=Student()
s.f("hello world") #調用類的方法 (輸出:hello world)
解釋:
字符串hello world將傳給s
self
示例:
class Student:
def f(self):
print(self.i) #訪問類中的屬性
s=Student()
s.i=12345
s.f()
結果:
12345
解釋:
類的方法與普通的函數區別:第一個參數爲self。
self實際上是對象的地址。
每一個對象的self地址都不一樣,它們都指向本身。
通過self可以訪問自身的數據成員和方法。
訪問數據成員和方法必須使用self,否則就是臨時變量,而不是數據成員
示例:
class Student:
def f(self):
i=200 #臨時變量
print(self.i) #數據成員
print(i)
s=Student()
s.i=100 #創建類的普通數據成員
s.f()
結果:
100
200
解釋:
通過結果可以看出,兩個i是不同的變量
通過self訪問的是對象內部的內存空間,是當前對象的成員。
構造方法
構造方法:創建對象時自動調用此方法
class Student:
def __init__(self):
print("對象被創建")
s=Student()
結果:
對象被創建
數據成員
普通數據成員
普通數據成員存儲在對象自己的內存空間中。
所以每個對象的普通數據成員都是獨立的,因爲它們的內存空間不一樣。
示例:
class Student:
def f(self):
print(self.i) #在類中訪問普通數據成員
s=Student()
s.i=100 #創建類的普通數據成員
s.i=200 #修改類的普通數據成員
print(s.i) #訪問類的普通數據成員
s.f()
解釋:
當數據成員不存在時, s.i=100將創建此數據成員
當數據成員存在時,s.i=100將修改此數據成員
self是對象的地址,用於訪問對象自己的屬性,包括數據成員和方法
一般在構造函數中創建普通數據成員
在構造函數中創建普通數據成員:
class Student:
def __init(self):
self.a=1 #定義普通數據成員
self.b=2 #定義普通數據成員
解釋:
當創建對象時會自動調用構造方法,所以數據成員也被自動創建。
若不使用構造方法,需要在手動的、依次給每一個對象創建這些數據成員。(很麻煩)
靜態數據成員
靜態數據成員存儲在類中,是所有對象共享的。
通過類名訪問靜態成員,而不是對象名。
示例:
class Student:
name="張三"
print(Student.name) #訪問靜態數據成員
Student.name="李四" #修改靜態數據成員
解釋:
靜態數據成員在類中直接定義
通過類名訪問靜態數據成員,因爲是共享的
通過對象名訪問普通數據成員,因爲每個對象之間是獨立的
注意:也可以通過對象名訪問靜態數據成員,但不建議這樣
class Student:
name="張三"
s1=Student()
s2=Student()
print("----創建之前:----")
print("Student.name :",Student.name)
print("s1.name :",s1.name)
print("----創建之後:----")
s1.name="李四"
print("Student.name :",Student.name)
print("s1.name :",s1.name)
結果:
----創建之前:----
Student.name : 張三
s1.name : 張三
----創建之後:----
Student.name : 張三
s1.name : 李四
解釋:
可以看到,當沒有創建普通數據成員時:s1.name表現爲靜態數據成員
當創建普通數據成員後:
s1.name表現爲普通數據成員
得出結論:不建議用對象名去修改靜態數據成員
私有數據成員
私有數據成員只能被當前類所訪問,外部和子類中都無法訪問。
這樣做的是爲了數據和代碼安全。
示例:
class Student:
__name="張三" #創建類的靜態私有數據成員
def __init__(self):
self.__age=18 #創建私有數據成員
s=Student()
解釋:
若在類的外部訪問類的私有成員,則會報錯:AttributeError。
私有數據成員名:使用雙下劃線開頭。
靜態私有數據成員:私有的,所有對象共享的,通過類名訪問
外部和子類中都無法訪問私有成員,那如何訪問它們呢?藉助類中非私有方法作爲橋樑
class Student:
def __init__(self):
self.__name="張三"
def getName(self): #通過此方法訪問靜態成員__name
return self.__name
def setName(self,name): #通過此方法修改靜態成員__name
self.__name=name
s=Student()
s.setName("李四")
print(s.getName()) #輸出:李四
解釋:
無法直接訪問私有數據成員
類的內部可以訪問私有數據成員。如非私有方法。
得出結論:通過非私有方法間接訪問私有數據成員。
特殊的數據成員
這些特殊的數據成員已經存在,無需自己創建
如__dict__將會返回類中所有的數據成員,以字典返回
class Student:
def __init__(self):
self.a=1
self.b=2
s=Student()
print(s.__dict__) #輸出{'a': 1, 'b': 2}
方法
普通方法
class Student:
def show(self,s):
print("我是"+s)
s=Student()
s.show("學生") #輸出:我是學生
私有方法
在類的外部和子類中無法訪問私有方法
私有方法僅供內部使用
私有方法名用雙下劃線開頭
class Student:
def __show(self): #外部無法直接訪問
print("Private")
s=Student()
s.__show() #外部無法訪問私有方法,報錯!!!!
靜態方法
靜態方法是共享的,屬於類,而不是某個對象
靜態方法只能訪問靜態數據成員
class Student:
@classmethod
def show(cls,s):
print(cls,s) #輸出類名
Student.show("張三") #輸出:<class '__main__.Student'> 張三
解釋:
方法前的@classmethod表示:此方法爲靜態方法
與普通方法類似,有一個額外的參數cls,cls是class的縮寫,代表類的地址。
因爲靜態方法是共享的,所以通過類名調用靜態方法。
構造方法
創建對象時Python自動調用構造方法
通常在構造方法中做對象的初始化工作,如創建數據成員。
構造方法名固定爲__init__。
class Student:
def __init__(self):
self.name=""
self.age=0
s=Student()
析構方法
回收對象時Python自動調用析構方法
通常在析構方法中做對象的收尾工作,如關閉打開的文件。
class Student:
def __del__(self):
print("對象已被刪除")
s=Student()
對象已被刪除
解釋:
析構方法名固定爲__del__。
程序運行完畢時會回收所有變量和內存空間,所以會刪除對象s。
除此之外,代碼塊運行完畢也會回收臨時變量,如函數中的臨時變量。
__call__方法
__call__方法:對象名可以當作方法名使用,調用__call__方法的函數體和形參
class Student:
def __call__(self):
print("調用__call__")
s=Student()
s() #輸出:調用__call__
解釋:
可以看到__call__方法的調用方式不是s.__call__(),而是通過對象名調用s()
強制轉換
通過定義強制轉換的方法,可以將對象轉換成任意數據類型。
class Student:
def __int__(self):
return 18
s=Student()
a=int(s) #a的值爲18
解釋:
若要轉換爲int,需要定義__int__方法
強制轉換的語法爲int(obj)
若要轉換爲其他類型,如float,需要定義__float__方法。
應用-得到學生的成績
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
def __int__(self):
return self.age
s=Student("張三",20)
a=int(s) #得到學生s的年齡
應用-得到學生的信息
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
def __str__(self):
return "我是一名學生,姓名:%s,年齡:%d"%(self.name,self.age)
s=Student("張三",20)
print(s) #直接輸出,就會調用str(obj)
結果:
我是一名學生,姓名:張三,年齡:20
解釋:
類似的還有float等數據類型,也都可以做強制轉換
若要轉換爲其他類型,如float,需要定義__float__方法。
運算符重載
對象之間可以進行加減乘除等操作,如:
s1=Student()
s2=Student()
s3=s1-s2
那s3的值到底是多少?到底是s1和s2身高差?還是平均分差?還是其他屬性的差值?
這需要我們自己來定義,進行運算符的重載。
示例-重載符號+:
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
def __add__(self,other): #運算符重載
return self.age + other.age
s1=Student("張三",20)
s2=Student("李四",18)
print(s1+s2) #輸出38 (20+18=38)
解釋:
重載符號+的方法名固定爲:add。
參數固定爲self和other,分別表示左操作數和右操作數。
通過return得到結果。
常用的符號與方法名:
符號 | 方法名 | 描述 |
---|---|---|
+ | __add__ | 加 |
- | __sub__ | 減 |
* | __mul__ | 乘 |
/ | __truediv__ | 除 |
// | __floordiv__ | 整除 |
% | __mod__ | 取模 |
** | __pow__ | 冪 |
下標運算符重載
通過下標運算符,可通過obj[n]這種形式訪問對象中的數據
示例-通過下標查詢:
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
def __getitem__(self, item): # 使用obj[n]時調用
if type(item) == slice: #切片
print(item.start) #開始下標
print(item.stop) #結束下標
print(item.step) #步長
return item
s1=Student("張三",20)
print(s1[1:8:7])
結果:
1
8
7
slice(1, 8, 7)
示例-通過下標修改:
class Student:
def __init__(self):
self.score={"語文":98,"數學":96}
def __setitem__(self, key, value):
self.score[key]=value
s1=Student()
s1["語文"]=100
print(s1.score)
結果:
{'語文': 100, '數學': 96}
示例-通過下標刪除:
class Student:
def __init__(self):
self.score={"語文":98,"數學":96}
def __delitem__(self, key):
del self.score[key]
s1=Student()
del s1["語文"]
print(s1.score)
結果:
{'數學': 96}
屬性
通過定義屬性,可以把函數變成數據成員
示例:
class Student:
@property
def name(self): #調用方式print(obj.name)
return "張三"
@name.setter
def name(self, value): #調用方式obj.name="Fei"
print(value)
@name.deleter
def name(self): #調用方式del obj.name
print("del name")
s=Student()
print(s.name) #輸出:張三
s.name="李四" #輸出:李四
del s.name #輸出:del name
解釋:
使用@property將函數變成數據成員
name.setter:name()的set函數
name.deleter:name()的del函數
屬性就像數據成員一樣使用,實際上是函數
繼承
當我們想爲某個類添加新的方法時,無需重新定義所有方法,使用繼承即可。
例如:普通學生可以得到成績信息,藝術生還能得到藝術成績。
好處:
代碼重用,方便維護。
通過繼承,可以得到所有父類的方法,不必再複製一份代碼。
基類&派生類
已存在的類稱爲基類(父類),新建的類稱爲派生類(子類)。
注意點:
子類不能直接訪問父類的私有成員
示例:
class A:
def show(self):
print("我是函數show()")
class B(A): #繼承了A
pass
b=B()
b.show() #因爲B繼承了A,所以b擁有函數show()
解釋:
C繼承了A和B,所以擁有A和B的所有方法
子類不能直接調用父類的私有成員
多重繼承
子類可以再被繼承。
如:A被B繼承,B又被C繼承。
class A:
pass
class B(A): #繼承了A
pass
class C(B): #繼承了B
pass
c=C()
函數重寫
繼承父類的函數之後,可以設置同名函數,這是新函數會把舊函數覆蓋。
class A:
def show(self):
print("我是A")
class B(A):
def show(self): #重寫了show函數
print("我是B")
b=B()
b.show() #輸出:我是B