一、簡介
面向對象編程是一種編程方式,使用 “類” 和 “對象” 來實現,所以,面向對象編程其實就是對 “類” 和 “對象” 的使用。類就是一個模板,模板里可以包含多個方法(函數),方法里實現各種各樣的功能,對象則是根據模板創建的實例,通過實例,對象可以執行類中的方法,每個對象都擁有相同的方法,但各自的數據可能不同。
二、類、對象和方法
在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
