一、簡介
面向對象編程是一種編程方式,使用 “類” 和 “對象” 來實現,所以,面向對象編程其實就是對 “類” 和 “對象” 的使用。類就是一個模板,模板里可以包含多個方法(函數),方法里實現各種各樣的功能,對象則是根據模板創建的實例,通過實例,對象可以執行類中的方法,每個對象都擁有相同的方法,但各自的數據可能不同。
二、類、對象和方法
在Python中,定義類是通過class
關鍵字,class
后面緊接着是類名,類名通常是大寫開頭的單詞,緊接着是('要繼承的類名')
,表示該類是從哪個類繼承下來的,可以有多個父類(基類),通常如果沒有合適的繼承類,就使用object
類,這是所有類最終都會繼承的類,也可以不寫。
class F1(object): def __init__(self,name,age): self.name = name self.age = age f1 = F1('python',27) 上面的這個__init__()叫做初始化方法(或構造方法), 在類實例化時,這個方法(雖然它是函數形式,但在類中就不叫函數了,叫方法)會自動執行,
進行一些初始化的動作,所以我們這里寫的__init__(self,name,age)就是要在創建一個角色時給它設置這些屬性。
參數self有什么用呢?
1.在內存中開辟一塊空間指向f1這個變量名
2.實例化F1這個類首先執行其中的__init__()方法,相當於F1.__init__(f1,'python',27),是為了把'python',27這2個值跟剛開辟的f1關聯起來,因為關聯起來后,你就可以直接f1.name, f1.age 這樣來調用啦。所以,為實現這種關聯,在調用__init__方法時,就必須把f1這個變量也傳進去,否則__init__不知道要把那2個參數跟誰關聯,self其實就是實例化對象f1被當作參數傳遞了。
3.所以這個__init__(…)構造方法里的,self.name = name , self.age = age 等等就是要把這幾個值存到f1的內存空間里。
三、面向對象三大特性,封裝、繼承和多態。
1、封裝
面向對象有3大特性,首先我們來說第一個特性,封裝,封裝一般是通過在類中封裝數據,而通過對象或者self獲取。和其他面向對象的語言類似,也是通過構造函數來進行數據封裝。下面來看一下代碼。
class A: def __init__(self,name): # 構造函數,初始化數據, self.name=name # 封裝數據 def f1(self): print(self.name) # 通過self間接獲取封裝的數據 a=A('json') # 相當於A.__init__(a,'json')將'json'封裝到a中的name屬性中 print(a.name) # 直接調用a對象的name屬性 a.f1() # python會把a當作參數傳遞給a.f1(a),所以print(a.name)
還有一種封裝的方式,使用私用的屬性來封裝數據,看一下具體的用法,
class A: name='Jason' __age=18 # 私有類屬性 def __init__(self): self.__like='soccer' # 私有實例屬性 self.hobby='kkkk' def f1(self): print(self.__age) # 私有類屬性,私有實例屬性只能被類中的方法調用 print(self.__like) # A.__age # 外部獲取不到私有類屬性,數據被封裝起來 a=A() # soccer a.f1() # 18 print(a.hobby)
復雜的封裝(一定要搞清楚): 將類封裝進對象中
class c1: def __init__(self,name,obj): self.name = name self.obj = obj class c2: def __init__(self,name,age): self.name = name self.age = age def show(self): print(self.name) class c3: def __init__(self,a1): self.money = 123 self.aaa = a1 c2_obj = c2('aa',12) # 將字符串'aa',數字12封裝到c2_obj.name和c2_obj.age中 c1_obj = c1('python',c2_obj) # 將字符串'python'封裝到c1_obj.name中,將c2_obj中的屬性c2_obj.name,c2_obj.age封裝到c1_obj.obj中 c3_obj = c3(c1_obj) # 將c1_obj中的所有屬性,包括(c2_obj的所所有方法和屬性) print(c3_obj.aaa.obj.name) #c3類中找到c2類中的屬性 ret = c3_obj.aaa.obj.show() #c3類中找到c2類中的方法執行並接收返回值 print(ret)
2、繼承
繼承的本質是將父類中的方法全部復制一份到子類中。Python里面的繼承可以多繼承,通過繼承,可以獲得父類的功能,繼承的時候,如果父類中有重復的方法,優先找自己
通過繼承創建的新類稱為“子類”或“派生類”。被繼承的類稱為“基類”、“父類”或“超類”。
(1)、Python的類可以繼承多個類,Java和C#中則只能繼承一個類
(2)、Python的類如果繼承了多個類,那么其尋找方法的方式有兩種,分別是:深度優先和廣度優先
- 當類是經典類時,多繼承情況下,會按照深度優先方式查找,Python2.x分為經典類和新式類(默認是經典類,繼承了object父類則為新式類)
- 當類是新式類時,多繼承情況下,會按照廣度優先方式查找,Python3.x 統一都是新式類
經典類和新式類,從字面上可以看出一個老一個新,新的必然包含了跟多的功能,也是之后推薦的寫法,從寫法上區分的話,如果 當前類或者父類繼承了object類,那么該類便是新式類,否則便是經典類。
繼承的執行過程例子:
class A: def f(self): print('a') class B: def f(self): print('b') def f1(self): print('bbbb') class C(A,B): def f1(self): print('c') cc=C() cc.f() # 結果為a,在C類中沒有f()這個方法時,繼承時A基類寫在前面,所以優先找A類中的f()方法 cc.f1() # 結果為c,在C類中有f1()方法,則優先執行自己的方法
下面是重點和難點,在其他源碼都是這么干的
class A: def bar(self): print('bar') self.f1() class B(A): def f1(self): print('b') class C(): def f1(self): print('c') class D(B): def f1(self): print('d') class E(C,D): pass d=D() d.bar()
上述繼承的執行過程:
- 1. 對象d是D()的實例,d.bar()首先在D()中找有沒有bar()方法
- 2. 沒有則到D()類繼承的父類B()類中找有沒有bar()方法
- 3. 沒有繼續向上一層找B()類繼承的父類A()類中找有沒有bar()方法
- 4. A()類中有bar()方法則執行了bar()方法
- 5. bar()執行了做了兩件事:(1)、打印print('c') (2)、執行了self.f1()
- 6. 執行了self.f1(),記住self是d對象,回歸到最初 d=D()
- 7. 則執行了D()中的f1()方法,所以結果是bar和d
(3)、除了繼承方法,還可以繼承父類的構造函數
# 繼承構造方法 class A: def __init__(self): self.name='jason' class B(A): def __init__(self): self.age='16' super(B,self).__init__() # A.__init__(self) #另一種繼承構造函數的方法 d=B()
(4)、強制使用父類中的方法(非常有用)使用:super(子類類名,self).父類中的方法
class C1: def f1(self): print('c1.f1') return 123 class C2(C1): def f1(self): # 主動執行父類的f1方法 ret = super(C2,self).f1() print('c2.f1') return ret # C1.f1(self) 第二種方法,主動執行父類的方法,不常用 obj = C2() obj.f1()
三、多態
Pyhon不支持Java和C#這一類強類型語言中多態的寫法,python本身就是支持多態的,所以在Python面向對象里面討論多態並沒有什么意義,其Python崇尚“鴨子類型”。
class F1: pass class S1(F1): def show(self): print('S1.show') class S2(F1): def show(self): print('S2.show') def Func(obj): print(obj.show()) s1_obj = S1() Func(s1_obj) s2_obj = S2() Func(s2_obj)
在java,c#中定義參數是需要強制定義一個參數是什么類型的參數,類似下面的代碼
class A: pass class B(A): pass class C(A): pass # arg參數:必須是A類型或A的子類 def func(A arg): print(arg) # obj = B() # obj = C() obj = A() func(obj)
總結
以上就是本節對於面向對象初級知識的介紹,總結如下:
- 面向對象是一種編程方式,此編程方式的實現是基於對 類 和 對象 的使用
- 類 是一個模板,模板中包裝了多個“函數”供使用
- 對象,根據模板創建的實例(即:對象),實例用於調用被包裝在類中的函數
- 面向對象三大特性:封裝、繼承和多態
問答專區
問題一:什么樣的代碼才是面向對象?
答:從簡單來說,如果程序中的所有功能都是用 類 和 對象 來實現,那么就是面向對象編程了。
問題二:函數式編程 和 面向對象 如何選擇?分別在什么情況下使用?
答:須知:對於 C# 和 Java 程序員來說不存在這個問題,因為該兩門語言只支持面向對象編程(不支持函數式編程)。而對於 Python 和 PHP 等語言卻同時支持兩種編程方式,且函數式編程能完成的操作,面向對象都可以實現;而面向對象的能完成的操作,函數式編程不行(函數式編程無法實現面向對象的封裝功能)。
所以,一般在Python開發中,全部使用面向對象 或 面向對象和函數式混合使用
面向對象的應用場景:
- 多函數需使用共同的值,如:數據庫的增、刪、改、查操作都需要連接數據庫字符串、主機名、用戶名和密碼
class SqlHelper: def __init__(self, host, user, pwd): self.host = host self.user = user self.pwd = pwd def 增(self): # 使用主機名、用戶名、密碼(self.host 、self.user 、self.pwd)打開數據庫連接 # do something # 關閉數據庫連接 def 刪(self): # 使用主機名、用戶名、密碼(self.host 、self.user 、self.pwd)打開數據庫連接 # do something # 關閉數據庫連接 def 改(self): # 使用主機名、用戶名、密碼(self.host 、self.user 、self.pwd)打開數據庫連接 # do something # 關閉數據庫連接 def 查(self): # 使用主機名、用戶名、密碼(self.host 、self.user 、self.pwd)打開數據庫連接 # do something # 關閉數據庫連接# do something
- 需要創建多個事物,每個事物屬性個數相同,但是值的需求
如:張三、李四、楊五,他們都有姓名、年齡、血型,但其都是不相同。即:屬性個數相同,但值不相同
class Person: def __init__(self, name ,age ,blood_type): self.name = name self.age = age self.blood_type = blood_type def detail(self): temp = "i am %s, age %s , blood type %s " % (self.name, self.age, self.blood_type) print temp zhangsan = Person('張三', 18, 'A') lisi = Person('李四', 73, 'AB') yangwu = Person('楊五', 84, 'A')
問題三:類和對象在內存中是如何保存?
答:類以及類中的方法在內存中只有一份,而根據類創建的每一個對象都在內存中需要存一份,大致如下圖:
如上圖所示,根據類創建對象時,對象中除了封裝 name 和 age 的值之外,還會保存一個類對象指針,該值指向當前對象的類。
當通過 obj1 執行 【方法一】 時,過程如下:
- 根據當前對象中的 類對象指針 找到類中的方法
- 將對象 obj1 當作參數傳給 方法的第一個參數 self