Python的類
類的定義
類的最基本的作用就是封裝,類只負責去定義,去刻畫一些東西。
類的首字母要大寫,變量的名字要小寫;
變量的兩個單詞間使用下劃線來進行連接,如:student_number,但是類不用下劃線來連接:StudentHomework;
例子:
class Student():
name = ''
age = 0
# 類中的方法的參數必須加self
def print_file(self):
print('name:' + self.name)
print('age:' + str(self.age))
# 要想使用這個類,就需要先對類進行實例化
student = Student()
# 調用類下面的方法
student.print_file()
注:不要在一個模塊下面又去定義類,又去完成類的實例化和類下面方法的調用。模塊如果寫類的話,那這個模塊就只寫類,而把對類的實例化和使用放在另外一個模塊裏去,再在另外一個模塊中通過from … import … 引入來對類進行實例化
函數與方法的區別
方法是一個設計層面上的稱謂,函數是程序運行、過程式的稱謂;
對於定義在類中的,稱作方法比較合適,因爲類是面向對象最基本的概念、是面向設計的。而定義在模塊裏的方法,稱作函數比較合適;
變量在模塊中,定義爲變量;而變量定義在類中,一般稱爲數據成員,數據成員是爲了體現出類的封裝性,每一個變量都被類視爲一個數據,這樣的數據用來描述類的特性;
類與對象
類和對象到底是什麼,它們之間又有一個什麼樣的關係?
類和對象是通過實例化關聯到一起的,類是現實世界或思維世界中的實體在計算機中的反映,它將數據(數據成員)以及這些數據上的操作(方法)封裝到一起,即類是用來刻畫某些事物基本特性的。
做面向對象的原因也就是爲了在計算機的世界中,把現實世界中的一些事物映射到計算機中。
我們用數據成員來刻畫事物的一些特徵,用方法來刻畫事物的一些行爲。比如學生類中,學生的name和age是特徵,寫作業,上課就是行爲。
類設計的好壞與否就是行爲和特徵你怎樣定義,行爲要對應設計的主體。
類是一個抽象的概念,是一類事物的總稱,並不是具體。而表示具體的東西,就是對象,類只是告訴我們這一類東西中有什麼特徵,但是特徵具體取什麼值,需要通過實例化來完成。當類被實例化後,它就變爲了一個具體的對象。類可以理解爲一個模板,通過這個模板,可以創造出不同的對象。
構造函數
實例化就是用類的模板來創建一個新的對象,那麼怎麼讓模板實例化後的對象不相同呢?類比一下函數,我們會對函數定義不同的形參,然後傳遞不同的實參到函數的內部,由此讓函數返回不同的結果。
那麼對類來說,我們也需要向它傳入不同的參數,以期實例化出的對象不相同,那麼我們是否可以直接在實例化時就傳入呢:
Student = Student(name = 'wjp',age = 18)
這樣做顯然是不行的,那麼我們需要在類中定義一個’特殊’的函數:
class Student():
name = ''
age = 0
def __init__(self,name,age):
# 構造函數
# 初始化對象的屬性
self.name = name
self.age = age
def do_homework(self):
print('homework')
student1 = Student('石敢當',18)
print(student1.name)
當使用類來實例化一個對象時,py會自動的調用構造函數,同時我們也可以顯式的去調用構造函數。顯示的調用構造函數返回的是none。
構造函數的作用就是讓你的類模板實例化不同的對象,__init__的形參中定義了哪些參數,那麼在類實例化中就要傳入哪些參數。
區分模塊變量與類中的變量
類比下模塊中全局變量和局部變量,局部變量的作用域在內部,所以局部變量的值不會改變全局變量。
但如果你對於類中的理解,等同於局部變量,那就不對了。 不能把模塊中的全局變量和模塊中函數的局部變量等同於類中類變量和實例變量。
類變量和實例變量
類變量就是和類相關的變量,實例變量是和對象相關聯在一起的,而對象是由類這個模板創建出來的。
定義在方法之外,類中的變量,叫做類變量,而實例變量則是通過self.實例變量名來保存變量的特徵值。
student1 = Student('石敢當',18)
student2 = Student('喜小樂',19)
# 實例變量
print(student1.name)
print(student2.name)
# 類變量
print(Student.name)
那麼定義類變量有什麼意義呢?我們從面向對象的角度來思考問題,面向對象是計算機對現實世界中各種關係的一個映射。類是抽象的,對象是具體的,所以name和age兩個變量定義爲類變量是不合適的,應該定義爲實例變量和對象關聯起來。
(一定要從現實世界的角度去考慮,爲什麼要有類,爲什麼要有類變量,爲什麼要有對象。)
舉個例子來說明類變量的作用以及什麼時候要用類變量:
class Student():
sum = 0
面向對象是一個抽象的概念,需要有一個抽象的思考能力,並且在實踐中才能反覆使用,才能深刻理解。
類與對象的變量查找順序
這裏有一個問題,同樣是上面的類,我想輸出實例變量,但是現在輸出的是類變量的原因是什麼呢?
class Student():
name = ''
age = 0
def __init__(self,name,age):
name = name
age = age
student1 = Student('石敢當',18)
# 想打印實例變量
print(student1.name)
# 想打印類變量
print(Student.name)
那麼這個問題是爲什麼呢?我們採用__dict__來打印當前這個對象下的所有相關的變量,得到的是一個空字典,而加了self來賦值的話,對象下的變量字典中才有實例變量。所以說對於輸出的實例變量,實質上是空值的,那爲什麼不輸出none,而輸出類變量呢?
那麼這裏就引出了py尋找相關變量的機制:如果我們嘗試去訪問實例變量的話,那麼py首先會在對象的實例變量列表中去查找有沒有一個name的變量。如果沒有,py並不會返回一個空,它會繼續到類變量裏中去尋找。
(如果類變量中也沒有找到,且存在繼承關係的話,那麼會繼續遍歷到其父類中去尋找。)
self與實例方法
問題一:
class Student():
name = 'wjp'
age = 23
def __init__(self,name,age):
self.name = name
self.age = age
print(age)
print(name)
問題二:
在實例方法中,是否能訪問到類變量?
實例方法在定義時,第一個參數需要定義self,但是實例方法在引用時,py會幫我們自動調用self(不需要再顯式指定)。同樣,在定義時也可以不用self,可以用this來進行替代,可以理解爲py之禪中的顯勝於隱。
self就是當前調用某一個方法的對象,self只和對象有關,和類是沒有關係的。
def __init__(this,name,age):
this.name = name
實例變量和實例方法類比:
實例變量是與對象相關聯的定義的變量,實例方法是和對象實例相關聯的,實例可以調用的方法叫做實例方法。(實例方法最大的一個特點就是其參數需要傳入一個self)
在實例方法中訪問實例變量與類變量
變量間的關係是怎樣的
從面向對象的角度上來講,方法代表了類的一個行爲,而變量代表的是刻畫了一個類的特徵。在絕大多數情況下,方法需要對變量做一定的運算,最終去改變變量的一個狀態。
在實例方法中是否能訪問到類變量?
前置知識回顧
實例方法的主要作用,是用來描述類的行爲,而構造函數的作用是用來初始化類的各種特徵。
在實例方法中,self.name和name是否等價?self.name讀取的是實例變量,而name讀取的是形參的name。
通過__dict__魔法函數方法來查看對象的使用,但是隻能用於類的外部調用中,在外部調用的過程纔可以正確輸出,內部則不可以。如:
class Student():
name = ''
def __init__(self,name1,age):
self.name = name1
self.age = age
print(self.name)
print(self.__dict__)
# 下面這句報錯
print(name)
實例方法中訪問變量的第一種方法
我們複習下在類的外部如何訪問到類變量的方法,在外部訪問時,我們通過類.類變量名來對類變量進行訪問。那麼同樣,內部訪問時也是通過類.類變量名進行訪問。
實例方法中訪問變量的第二種方法
self.class.類變量名
class Student():
sum1 = 0
def __init__(self, name, age):
self.name = name
self.age = age
print(self.__class__.sum1)
故一定要理清楚類變量,實例變量,實例方法它們之間相互調用的一個規律。
類方法
爲什麼會有類方法,以及類方法有什麼樣的作用;
類方法和實例方法的兩點不同:
- 定義實例方法需要參數self,定義類方法需要參數cls;(名字不同其實無所謂)
- 類方法前需要加上@classmethod;
- 實例方法關注的是對象的事物,類方法關注的是事物本身;
class Student:
sum = 0
def __init__(self):
pass
def do_homework(self):
print('homework')
@classmethod
def plus_sum(cls):
cls.sum += 1
print(cls.sum)d
# 類方法的調用
# 既然是類方法,調用時就和類相關,和對象是沒有太大關係的;
Student.plus_sum()
類方法的主要作用是操作一些和類相關的變量,那麼有兩個問題:
- 既然可以在實例方法中操作類變量,那麼還要類方法做什麼?
- 可以用一個類來調用類方法,那麼可否用對象來調用類方法?
1–>就是爲了抽象及對應,類的事讓類方法去處理;
2–>py中可以用對象來調用類方法,其他語言則有限制;
靜態方法
@staticmethod
def add(x,y):
# 靜態方法訪問類變量
print(Student.sum)
# 靜態方法訪問實例變量
print(name)
靜態方法和實例方法、類方法的不同:
- 靜態方法不像實例方法和類方法一樣有一個強制的’關鍵字’:類方法中的cls代表的是類本身,實例方法中的self代表的是對象本身;
- 上方有@staticmethod裝飾器;
靜態方法和麪向對象的關聯性非常弱,一般情況下不推薦使用;
成員可見性
成員理解爲:變量和方法,其可見性就是py對象下面的變量和方法的可見性。
對於成員,我們有外部訪問和內部訪問兩種途徑,實際上使用時,要通過方法對實例變量進行操作,而不是直接修改,因爲在方法內可以進行變量修改。
# 想對score賦值,定義方法
def marking(self,score):
if score < 0:
return '不能夠給別人打負分'
self.score = score
print(self.name + '同學本次考試分數爲:' + str(self.score))
私有方法
對類下面的變量的值的修改要通過方法來完成,而不能直接進行修改。
那麼py怎麼來定義成員的私有或者公開屬性,來防止對其直接修改的操作呢?
py中定義,當成員前有雙下劃線時,就定義了成員是私有的,就不可以在外部進行調用。
如:
# 私有方法
def __marking(self,score):
pass
那爲什麼構造函數__init__的前面有雙下劃線,但是卻不是私有的呢?
這樣前後都有下劃線其實是py的一個自定義函數命名規則,即魔法函數,成員可見性爲公開,如:
def __marking__(self,score):
pass
私有變量
上面我們說了私有方法的定義,而對於私有變量,嚴格意義上來講,py是沒有私有變量的。我們先來看一個例子:
class Student():
def __init__(self):
self.__score = __score
但是在實操中,我們將方法設置爲私有後,私有方法不可訪問,但是把變量設置爲私有後,變量卻可以訪問且賦值,這是什麼原因呢?
如:
class Student():
def __init__(self):
self.__score = 0
def marking(self, score):
self.__score = score
print(self.__score)
student1 = Student('大鵬',23)
student1.marking(100)
student1.__score = 666
print(student1.__score)
student2 = Student('小菜',23)
print(student2.__score)
---
大鵬的成績是100
666
報錯....
在這裏我們直接將私有變量__score進行了修改爲了666,那麼這個私有性何在?爲什麼可以直接修改呢?
事實上,這裏之所以能修改是我們看到的’假象’,根據py動態語言的特性,這裏student.__score實質上是新創建了一個變量__score,接着對其進行print。而py中私有變量的機制裏,本質上是對雙下劃線定義的變量進行了一次新的命名:_類名__私有變量名,所以直接訪問私有變量名時會訪問不到。
類的繼承
繼承性最基本的定義就是爲了避免我們定義重複的方法和重複的變量。
在py中,繼承某類的方式是在其括號內寫上父類,如:
class Human():
pass
class Student(People):
pass
接着在考慮繼承時,需要考慮子類的特性,對Student類來說,其name,age不是特有的特性,因此可以將其放在父類中,py是可以允許多繼承的原則,一個父類是可以有多個子類的:
class Human():
def __init__(self,name,age):
self.name = name
self.age = age
class Student(Human):
def __init(self,school,name,age):
self.school = school
# 掉用父類的初始化方法一:
# 此處要加self:因爲其和實例化時去調用構造函數是不一樣
# 通過對象來調用實例方法時,其self知道這個對象,而通 # 過類來調用時,其實例方法需要指明self;
Human.__init__(self, name, age)
# 方法二:
super(Student, self).__init__(name, age)
def do_homework(self):
pass
student = Student('阿里','大鵬',23)
print(student.name)