概述php
面向過程編程最易被初學者接受,其每每用一長段代碼來實現指定功能,開發過程當中最多見的操做就是粘貼複製,即:將以前實現的代碼塊複製到現需功能處,此爲1.0。html
隨着時間的推移,開始使用了函數式編程,加強代碼的重用性和可讀性,此爲2.0。java
而從如今開始,咱們即未來學習編程方式的升級版——面向對象編程(Object Oriented Programming, OOP, 面向對象程序設計),華麗麗的3.0。python
PS:Java和C#來講只支持面向對象編程,而python比較靈活即支持面向對象編程也支持函數式編程。程序員
建立類和對象面試
面向對象編程是一種編程方式,此編程方式須要使用 「類」 和 「對象」 來實現,因此,面向對象編程其實就是對 「類」 和 「對象」 的使用。數據庫
ps:類中的函數第一個參數必須是self(詳細見:類的三大特性之封裝)
類中定義的函數叫作 「方法」。編程
1
2
3
4
5
6
7
8
9
10
11
12
|
# 建立類
class
foo:
def
bar(
self
):
print
"bar"
def
hello(
self
,name):
print
"I am %s"
%
name
# 根據類foo建立對象obj
obj
=
foo()
obj.bar()
# 執行bar方法
obj.hello(
'korala'
)
# 執行hello方法
|
運行結果:c#
1
2
|
bar
I am korala
|
看了上面的例子是否是有點困惑啊?使用函數式編程和麪向對象編程方式來執行一個「方法」時函數要比面向對象簡便。ruby
看了上述的對比函數的確要比面向對象簡便,然而事情的真相是。。。不一樣的場景中其適合的編程方式也是不同滴。
總結:函數式的應用場景 --> 各個函數之間是獨立且無共用的數據。
至於面向對象該如何應用呢?別急,接着往下看。
面向對象的三大特性
想知道面向對象應用在哪些場景,先來了解它的三大特性:
1、 封裝
封裝,顧名思義就是將內容封裝到某個地方,之後再去調用被封裝在某處的內容。
因此,在使用面向對象的封裝特性時,須要:
第一步:將內容封裝到某處
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 建立類
class
foo:
def
__int__(
self
, name, age):
# 稱爲構造方法,根據類建立對象時自動執行
self
.name
=
name
self
.age
=
age
# 根據類foo建立對象
# 自動執行foo類的 __init__ 方法
obj1
=
foo(
'wxy'
,
20
)
# 將wxy和20分別封裝到 obj1 self 的name和age屬性中
# 根據類foo建立對象
# 自動執行foo類的 __init__ 方法
obj2
=
foo(
'korala'
,
18
)
# 將korala和18分別封裝到 obj2 self 的name和age屬性中
|
self 是一個形式參數,當執行 obj1 = Foo('wxy',20) 時,self 等於 obj1;當執行 obj2 = Foo('korala', 18 ) 時,self 等於 obj2。
因此,內容其實被封裝到了對象 obj1 和 obj2 中,每一個對象中都有 name 和 age 屬性,在內存裏相似於下圖來保存。
第二步:從某處調用被封裝的內容
調用被封裝的內容時,有兩種狀況:
一、經過對象直接調用被封裝的內容
上圖展現了對象 obj1 和 obj2 在內存中保存的方式,根據保存格式能夠如此調用被封裝的內容:對象.屬性名
1
2
3
4
5
6
7
8
9
10
11
12
|
class
foo:
def
__int__(
self
, name, age):
self
.name
=
name
self
.age
=
age
obj1
=
foo(
'wxy'
,
20
)
print
obj1.name
# 直接調用obj1對象的name屬性
print
obj1.age
# 直接調用obj1對象的age屬性
obj2
=
foo(
'korala'
,
18
)
print
obj2.name
# 直接調用obj2對象的name屬性
print
obj2.age
# 直接調用obj2對象的age屬性
|
二、經過self間接調用被封裝的內容
執行類中的方法時,須要經過self間接調用被封裝的內容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class
foo:
def
__int__(
self
, name, age):
self
.name
=
name
self
.age
=
age
def
detail(
self
):
print
self
.name
print
self
.age
obj1
=
foo(
'wxy'
,
20
)
obj1.detail()
# python默認會將obj1傳給self參數,即:obj1.detail(obj1),故此時方法內部的 self.name 是 wxy; self.age 是 20
obj2
=
foo(
'korala'
,
18
)
obj2.detail()
# python默認會將obj2傳給self參數,即:obj2.detail(obj2),故此時方法內部的 self.name 是 korala ; self.age 是 18
|
綜上所述,對於面向對象的封裝來講,其實就是使用構造方法將內容封裝到 對象 中,而後經過對象直接或者self間接獲取被封裝的內容。
練習一:在終端輸出以下信息
小明,10歲,男,上山去砍柴
小明,10歲,男,開車去東北
小明,10歲,男,最愛大保健
老李,90歲,男,上山去砍柴
老李,90歲,男,開車去東北
老李,90歲,男,最愛大保健
# 函數式編程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def
kanchai(name, age, gender):
print
"%s,%s歲,%s,上山去砍柴"
%
(name, age, gender)
def
qudongbei(name, age, gender):
print
"%s,%s歲,%s,開車去東北"
%
(name, age, gender)
def
dabaojian(name, age, gender):
print
"%s,%s歲,%s,最愛大保健"
%
(name, age, gender)
kanchai(
'小明'
,
10
,
'男'
)
qudongbei(
'小明'
,
10
,
'男'
)
dabaojian(
'小明'
,
10
,
'男'
)
kanchai(
'老李'
,
90
,
'男'
)
qudongbei(
'老李'
,
90
,
'男'
)
dabaojian(
'老李'
,
90
,
'男'
)
|
# 面向對象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class
Foo:
def
__init__(
self
, name, age ,gender):
self
.name
=
name
self
.age
=
age
self
.gender
=
gender
def
kanchai(
self
):
print
"%s,%s歲,%s,上山去砍柴"
%
(
self
.name,
self
.age,
self
.gender)
def
qudongbei(
self
):
print
"%s,%s歲,%s,開車去東北"
%
(
self
.name,
self
.age,
self
.gender)
def
dabaojian(
self
):
print
"%s,%s歲,%s,最愛大保健"
%
(
self
.name,
self
.age,
self
.gender)
xiaoming
=
Foo(
'小明'
,
10
,
'男'
)
xiaoming.kanchai()
xiaoming.qudongbei()
xiaoming.dabaojian()
laoli
=
Foo(
'老李'
,
90
,
'男'
)
laoli.kanchai()
laoli.qudongbei()
laoli.dabaojian()
|
上述對比能夠看出,若是使用函數式編程,須要在每次執行函數時傳入相同的參數,若是參數多的話,就須要n次粘貼複製了;而對於面向對象只須要在建立對象時,將全部須要的參數封裝到當前對象中,以後再次使用時,經過self間接去當前對象中取值便可。
練習二:遊戲人生程序
一、建立三個遊戲人物,分別是:
蒼井井,女,18,初始戰鬥力1000
東尼木木,男,20,初始戰鬥力1800
波多多,女,19,初始戰鬥力2500
二、遊戲場景,分別:
草叢戰鬥,消耗200戰鬥力
自我修煉,增加100戰鬥力
多人遊戲,消耗500戰鬥力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
# ##################### 定義實現功能的類 #####################
class
Person:
def
__init__(
self
, na, gen, age, fig):
self
.name
=
na
self
.gender
=
gen
self
.age
=
age
self
.fight
=
fig
def
grassland(
self
):
"""註釋:草叢戰鬥,消耗200戰鬥力"""
self
.fight
=
self
.fight
-
200
def
practice(
self
):
"""註釋:自我修煉,增加100戰鬥力"""
self
.fight
=
self
.fight
+
200
def
incest(
self
):
"""註釋:多人遊戲,消耗500戰鬥力"""
self
.fight
=
self
.fight
-
500
def
detail(
self
):
"""註釋:當前對象的詳細狀況"""
temp
=
"姓名:%s ; 性別:%s ; 年齡:%s ; 戰鬥力:%s"
%
(
self
.name,
self
.gender,
self
.age,
self
.fight)
print
temp
# ##################### 開始遊戲 #####################
cang
=
Person(
'蒼井井'
,
'女'
,
18
,
1000
)
# 建立蒼井井角色
dong
=
Person(
'東尼木木'
,
'男'
,
20
,
1800
)
# 建立東尼木木角色
bo
=
Person(
'波多多'
,
'女'
,
19
,
2500
)
# 建立波多多角色
cang.incest()
#蒼井空參加一次多人遊戲
dong.practice()
#東尼木木自我修煉了一次
bo.grassland()
#波多多參加一次草叢戰鬥
#輸出當前全部人的詳細狀況
cang.detail()
dong.detail()
bo.detail()
cang.incest()
#蒼井空又參加一次多人遊戲
dong.incest()
#東尼木木也參加了一個多人遊戲
bo.practice()
#波多多自我修煉了一次
#輸出當前全部人的詳細狀況
cang.detail()
dong.detail()
bo.detail()
|
小結:
封裝:一、將數據進行封裝,便於使用時取出數據;
二、根據一個模板建立使用對象(相同屬性);
執行過程:
類對象指針 ——> 經過指針找到類,類中找方法,方法執行(對象中隱含着類中的指針 ==> 關聯)
2、繼承
繼承,面向對象中的繼承和現實生活中的繼承相同,即:子能夠繼承父的內容。
例如:
貓能夠:喵喵叫、吃、喝、拉、撒
狗能夠:汪汪叫、吃、喝、拉、撒
若是咱們要分別爲貓和狗建立一個類,那麼就須要爲 貓 和 狗 實現他們全部的功能,以下所示:
# 僞代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class
貓:
def
喵喵叫(
self
):
print
'喵喵叫'
def
吃(
self
):
# do something
def
喝(
self
):
# do something
def
拉(
self
):
# do something
def
撒(
self
):
# do something
class
狗:
def
汪汪叫(
self
):
print
'喵喵叫'
def
吃(
self
):
# do something
def
喝(
self
):
# do something
def
拉(
self
):
# do something
def
撒(
self
):
# do something
|
上述代碼不難看出,吃、喝、拉、撒是貓和狗都具備的功能,而咱們卻分別的貓和狗的類中編寫了兩次。若是使用 繼承 的思想,以下實現:
動物:吃、喝、拉、撒
貓:喵喵叫(貓繼承動物的功能)
狗:汪汪叫(狗繼承動物的功能)
# 僞代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class
動物:
def
吃(
self
):
# do something
def
喝(
self
):
# do something
def
拉(
self
):
# do something
def
撒(
self
):
# do something
# 在類後面括號中寫入另一個類名,表示當前類繼承另一個類
class
貓(動物):
def
喵喵叫(
self
):
print
'喵喵叫'
# 在類後面括號中寫入另一個類名,表示當前類繼承另一個類
class
狗(動物):
def
汪汪叫(
self
):
print
'喵喵叫'
|
# 代碼實例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
class
Animal:
def
eat(
self
):
print
"%s 吃 "
%
self
.name
def
drink(
self
):
print
"%s 喝 "
%
self
.name
def
shit(
self
):
print
"%s 拉 "
%
self
.name
def
pee(
self
):
print
"%s 撒 "
%
self
.name
class
Cat(Animal):
def
__init__(
self
, name):
self
.name
=
name
self
.breed
=
'貓'
def
cry(
self
):
print
'喵喵叫'
class
Dog(Animal):
def
__init__(
self
, name):
self
.name
=
name
self
.breed
=
'狗'
def
cry(
self
):
print
'汪汪叫'
# ######### 執行 #########
c1
=
Cat(
'小白家的小黑貓'
)
c1.eat()
c2
=
Cat(
'小黑的小白貓'
)
c2.drink()
d1
=
Dog(
'胖子家的小瘦狗'
)
d1.eat()
|
因此,對於面向對象的繼承來講,其實就是將多個類共有的方法提取到父類中,子類僅需繼承父類而沒必要一一實現每一個方法。
PS:除了子類和父類的稱謂,你可能看到過 派生類 和 基類 ,他們與子類和父類只是叫法不一樣而已。
學習了繼承的寫法以後,咱們用代碼來是上述阿貓阿狗的功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
class
Animal:
def
eat(
self
):
print
"%s 吃 "
%
self
.name
def
drink(
self
):
print
"%s 喝 "
%
self
.name
def
shit(
self
):
print
"%s 拉 "
%
self
.name
def
pee(
self
):
print
"%s 撒 "
%
self
.name
class
Cat(Animal):
def
__init__(
self
, name):
self
.name
=
name
self
.breed =
'貓'
def
cry(
self
):
print
'喵喵叫'
class
Dog(Animal):
def
__init__(
self
, name):
self
.name
=
name
self
.breed =
'狗'
def
cry(
self
):
print
'汪汪叫'
# ######### 執行 #########
c1
=
Cat(
'小白家的小黑貓'
)
c1.eat()
c2
=
Cat(
'小黑的小白貓'
)
c2.drink()
d1
=
Dog(
'胖子家的小瘦狗'
)
d1.eat()
|
那麼問題又來了,多繼承呢?
一、Python的類能夠繼承多個類,Java和C#中則只能繼承一個類
二、Python的類若是繼承了多個類,那麼其尋找方法的方式有兩種,分別是:深度優先 和 廣度優先
經典類和新式類,從字面上能夠看出一個老一個新,新的必然包含了跟多的功能,也是以後推薦的寫法,從寫法上區分的話,若是 當前類或者父類繼承了object類,那麼該類即是新式類,不然即是經典類。
# 經典類多繼承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class
D:
def
bar(
self
):
print
'D.bar'
class
C(D):
def
bar(
self
):
print
'C.bar'
class
B(D):
def
bar(
self
):
print
'B.bar'
class
A(B, C):
def
bar(
self
):
print
'A.bar'
a
=
A()
# 執行bar方法時
# 首先去A類中查找,若是A類中沒有,則繼續去B類中找,若是B類中麼有,則繼續去D類中找,若是D類中麼有,則繼續去C類中找,若是仍是未找到,則報錯
# 因此,查找順序:A --> B --> D --> C
# 在上述查找bar方法的過程當中,一旦找到,則尋找過程當即中斷,便不會再繼續找了
a.bar()
|
# 新式類多繼承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class
D(
object
):
def
bar(
self
):
print
'D.bar'
class
C(D):
def
bar(
self
):
print
'C.bar'
class
B(D):
def
bar(
self
):
print
'B.bar'
class
A(B, C):
def
bar(
self
):
print
'A.bar'
a
=
A()
# 執行bar方法時
# 首先去A類中查找,若是A類中沒有,則繼續去B類中找,若是B類中麼有,則繼續去C類中找,若是C類中麼有,則繼續去D類中找,若是仍是未找到,則報錯
# 因此,查找順序:A --> B --> C --> D
# 在上述查找bar方法的過程當中,一旦找到,則尋找過程當即中斷,便不會再繼續找了
a.bar()
|
經典類:首先去A類中查找,若是A類中沒有,則繼續去B類中找,若是B類中麼有,則繼續去D類中找,若是D類中麼有,則繼續去C類中找,若是仍是未找到,則報錯。
新式類:首先去A類中查找,若是A類中沒有,則繼續去B類中找,若是B類中麼有,則繼續去C類中找,若是C類中麼有,則繼續去D類中找,若是仍是未找到,則報錯。
注意:在上述查找過程當中,一旦找到,則尋找過程當即中斷,便不會再繼續找了。
小結:
繼承:只跟類有關係;
父類、子類
基類、派生類
函數式 ==> 函數式面向對象(低級)經典類 ==> 函數式面向對象(添加新功能)新式類(直接或間接繼承)
java,c#:只能繼承一個類,不能繼承多個類
python,ruby,php:多繼承支持——>
廣度(橫向)優先(新式類):離誰越近就先找誰
深度(縱向)優先(經典類)
一個類D繼承了多個類A,B,C,對於新式類來講,對於類中的同一功能,尋找順序爲:C,B,A
深度優先則順序爲:A,B,C
3、多態
Pyhon不支持多態而且也用不到多態,多態的概念是應用於Java和C#這一類強類型語言中,而Python崇尚「鴨子類型」。
# python僞代碼實現java或c#的多態
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class
F1:
pass
class
S1(F1):
def
show(
self
):
print
'S1.show'
class
S2(F1):
def
show(
self
):
print
'S2.show'
# 因爲在Java或C#中定義函數參數時,必須指定參數的類型
# 爲了讓Func函數既能夠執行S1對象的show方法,又能夠執行S2對象的show方法,因此,定義了一個S1和S2類的父類
# 而實際傳入的參數是:S1對象和S2對象
def
Func(F1 obj):
"""Func函數須要接收一個F1類型或者F1子類的類型"""
print
obj.show()
s1_obj
=
S1()
Func(s1_obj)
# 在Func函數中傳入S1類的對象 s1_obj,執行 S1 的show方法,結果:S1.show
s2_obj
=
S2()
Func(s2_obj)
# 在Func函數中傳入Ss類的對象 ss_obj,執行 Ss 的show方法,結果:S2.show
|
# python 「鴨子類型」
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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)
|
基礎知識總結:
問答專區
問題一:什麼樣的代碼纔是面向對象?
答:從簡單來講,若是程序中的全部功能都是用 類 和 對象 來實現,那麼就是面向對象編程了。
問題二:函數式編程 和 面向對象 如何選擇?分別在什麼狀況下使用?
答:須知:對於 C# 和 Java 程序員來講不存在這個問題,由於該兩門語言只支持面向對象編程(不支持函數式編程)。而對於 Python 和 PHP 等語言卻同時支持兩種編程方式,且函數式編程能完成的操做,面向對象均可以實現;而面向對象的能完成的操做,函數式編程不行(函數式編程沒法實現面向對象的封裝功能)。
因此,通常在Python開發中,所有使用面向對象 或 面向對象和函數式混合使用。
面向對象的應用場景:
1. 多函數需使用共同的值,如:數據庫的增、刪、改、查操做都須要鏈接數據庫字符串、主機名、用戶名和密碼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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
|
2. 須要建立多個事物,每一個事物屬性個數相同,可是值的需求
如:張3、李4、楊五,他們都有姓名、年齡、血型,但其都是不相同。即:屬性個數相同,但值不相同。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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 執行 【方法一】 時,過程以下:
1. 根據當前對象中的 類對象指針 找到類中的方法
2. 將對象 obj1 看成參數傳給 方法的第一個參數 self
PS:全部成員中,只有普通字段的內容保存對象中,即:根據此類建立了多少對象,在內存中就有多少個普通字段。而其餘的成員,則都是保存在類中,即:不管對象的多少,在內存中只建立一份。
1、字段
字段包括:普通字段和靜態字段,他們在定義和使用中有所區別,而最本質的區別是內存中保存的位置不一樣,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 字段的定義和使用
class
Province:
# 靜態字段
country
=
'中國'
def
__init__(
self
, name):
# 普通字段
self
.name
=
name
# 直接訪問普通字段
obj
=
Province(
'河北省'
)
print
obj.name
# 直接訪問靜態字段
Province.country
|
由上述代碼能夠看出【普通字段須要經過對象來訪問】【靜態字段經過類訪問】,在使用上能夠看出普通字段和靜態字段的歸屬是不一樣的。其在內容的存儲方式相似以下圖:
由上圖可知:
應用場景: 經過類建立對象時,若是每一個對象都具備相同的字段,那麼就使用靜態字段
PS:普通字段——保存在對象中
靜態字段——保存在類中
2、方法
方法包括:普通方法、靜態方法和類方法,三種方法在內存中都歸屬於類,區別在於調用方式不一樣。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
# 方法的定義和使用
class
Foo:
def
__init__(
self
, name):
self
.name
=
name
def
ord_func(
self
):
""" 定義普通方法,至少有一個self參數 """
# print self.name
print
'普通方法'
@classmethod
def
class_func(
cls
):
""" 定義類方法,至少有一個cls參數 """
print
'類方法'
@staticmethod
def
static_func():
""" 定義靜態方法 ,無默認參數"""
print
'靜態方法'
# 調用普通方法
f
=
Foo()
f.ord_func()
# 調用類方法
Foo.class_func()
# 調用靜態方法
Foo.static_func()
|
相同點:對於全部的方法而言,均屬於類(非對象)中,因此,在內存中也只保存一份。
不一樣點:方法調用者不一樣、調用方法時自動傳入的參數不一樣。
PS:普通方法——對象觸發,至少一個self參數
類方法——類調用,只能一個cls參數 ==> 無需建立對象
靜態方法——類調用,無默認參數 ==> 類+靜態方法=函數(節省內存),不用單首創建內存
3、屬性
若是你已經瞭解Python類中的方法,那麼屬性就很是簡單了,由於Python中的屬性實際上是普通方法的變種。
對於屬性,有如下三個知識點:
一、 屬性的基本使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 屬性的定義和使用
# ############### 定義 ###############
class
Foo:
def
func(
self
):
pass
# 定義屬性
@property
def
prop(
self
):
pass
# ############### 調用 ###############
foo_obj
=
Foo()
foo_obj.func()
foo_obj.prop
#調用屬性
|
由屬性的定義和調用要注意一下幾點:
注意:屬性存在乎義是:訪問屬性時能夠製造出和訪問字段徹底相同的假象。
屬性由方法變種而來,若是Python中沒有屬性,方法徹底能夠代替其功能。
實例:對於主機列表頁面,每次請求不可能把數據庫中的全部內容都顯示到頁面上,而是經過分頁的功能局部顯示,因此在向數據庫中請求數據時就要顯示的指定獲取從第m條到第n條的全部數據(即:limit m,n),這個分頁的功能包括:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# ############### 定義 ###############
class
Pager:
def
__init__(
self
, current_page):
# 用戶當前請求的頁碼(第一頁、第二頁...)
self
.current_page
=
current_page
# 每頁默認顯示10條數據
self
.per_items
=
10
@property
def
start(
self
):
val
=
(
self
.current_page
-
1
)
*
self
.per_items
return
val
@property
def
end(
self
):
val
=
self
.current_page
*
self
.per_items
return
val
# ############### 調用 ###############
p
=
Pager(
1
)
p.start 就是起始值,即:m
p.end 就是結束值,即:n
|
從上述可見,Python的屬性的功能是:屬性內部進行一系列的邏輯計算,最終將計算結果返回。
2、屬性的兩種定義方式
屬性的定義有兩種方式:
裝飾器方式:在類的普通方法上應用@property裝飾器
咱們知道Python中的類有經典類和新式類,新式類的屬性比經典類的屬性豐富。( 若是類繼object,那麼該類是新式類 )
經典類,具備一種@property裝飾器(如上一步實例)
1
2
3
4
5
6
7
8
|
# ############### 定義 ###############
class
Goods:
@property
def
price(
self
):
return
"wupeiqi"
# ############### 調用 ###############
obj
=
Goods()
result
=
obj.price
# 自動執行 @property 修飾的 price 方法,並獲取方法的返回值
|
新式類,具備三種@property裝飾器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# ############### 定義 ###############
class
Goods(
object
):
@property
def
price(
self
):
print
'@property'
@price
.setter
def
price(
self
, value):
print
'@price.setter'
@price
.deleter
def
price(
self
):
print
'@price.deleter'
# ############### 調用 ###############
obj
=
Goods()
obj.price
# 自動執行 @property 修飾的 price 方法,並獲取方法的返回值
obj.price
=
123
# 自動執行 @price.setter 修飾的 price 方法,並將 123 賦值給方法的參數
del
obj.price
# 自動執行 @price.deleter 修飾的 price 方法
|
注:經典類中的屬性只有一種訪問方式,其對應被 @property 修飾的方法
新式類中的屬性有三種訪問方式,並分別對應了三個被@property、@方法名.setter、@方法名.deleter修飾的方法
因爲新式類中具備三種訪問方式,咱們能夠根據他們幾個屬性的訪問特色,分別將三個方法定義爲對同一個屬性:獲取、修改、刪除
# 實例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class
Goods(
object
):
def
__init__(
self
):
# 原價
self
.original_price
=
100
# 折扣
self
.discount
=
0.8
@property
def
price(
self
):
# 實際價格 = 原價 * 折扣
new_price
=
self
.original_price
*
self
.discount
return
new_price
@price
.setter
def
price(
self
, value):
self
.original_price
=
value
@price
.deltter
def
price(
self
, value):
del
self
.original_price
obj
=
Goods()
obj.price
# 獲取商品價格
obj.price
=
200
# 修改商品原價
del
obj.price
# 刪除商品原價
|
靜態字段方式,建立值爲property對象的靜態字段
當使用靜態字段的方式建立屬性時,經典類和新式類無區別
1
2
3
4
5
6
7
8
9
|
class
Foo:
def
get_bar(
self
):
return
'wupeiqi'
BAR
=
property
(get_bar)
obj
=
Foo()
reuslt
=
obj.BAR
# 自動調用get_bar方法,並獲取方法的返回值
print
reuslt
|
property的構造方法中有個四個參數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class
Foo:
def
get_bar(
self
):
return
'wxy'
# *必須兩個參數
def
set_bar(
self
, value):
return
return
'set value'
+
value
def
del_bar(
self
):
return
'wxy'
BAR =
property
(get_bar, set_bar, del_bar,
'description...'
)
obj
=
Foo()
obj.BAR
# 自動調用第一個參數中定義的方法:get_bar
obj.BAR
=
"korala"
# 自動調用第二個參數中定義的方法:set_bar方法,並將「korala」看成參數傳入
del
Foo.BAR
# 自動調用第三個參數中定義的方法:del_bar方法
obj.BAE.__doc__
# 自動獲取第四個參數中設置的值:description...
|
因爲靜態字段方式建立屬性具備三種訪問方式,咱們能夠根據他們幾個屬性的訪問特色,分別將三個方法定義爲對同一個屬性:獲取、修改、刪除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class
Goods(
object
):
def
__init__(
self
):
# 原價
self
.original_price
=
100
# 折扣
self
.discount
=
0.8
def
get_price(
self
):
# 實際價格 = 原價 * 折扣
new_price
=
self
.original_price
*
self
.discount
return
new_price
def
set_price(
self
, value):
self
.original_price
=
value
def
del_price(
self
, value):
del
self
.original_price
PRICE
=
property
(get_price, set_price, del_price,
'價格屬性描述...'
)
obj
=
Goods()
obj.PRICE
# 獲取商品價格
obj.PRICE
=
200
# 修改商品原價
del
obj.PRICE
# 刪除商品原價
|
注意:Python WEB框架 Django 的視圖中 request.POST 就是使用的靜態字段的方式建立的屬性。
# Django源碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
class
WSGIRequest(http.HttpRequest):
def
__init__(
self
, environ):
script_name
=
get_script_name(environ)
path_info
=
get_path_info(environ)
if
not
path_info:
# Sometimes PATH_INFO exists, but is empty (e.g. accessing
# the SCRIPT_NAME URL without a trailing slash). We really need to
# operate as if they'd requested '/'. Not amazingly nice to force
# the path like this, but should be harmless.
path_info
=
'/'
self
.environ
=
environ
self
.path_info
=
path_info
self
.path
=
'%s/%s'
%
(script_name.rstrip(
'/'
), path_info.lstrip(
'/'
))
self
.META
=
environ
self
.META[
'PATH_INFO'
]
=
path_info
self
.META[
'SCRIPT_NAME'
]
=
script_name
self
.method
=
environ[
'REQUEST_METHOD'
].upper()
_, content_params
=
cgi.parse_header(environ.get(
'CONTENT_TYPE'
, ''))
if
'charset'
in
content_params:
try
:
codecs.lookup(content_params[
'charset'
])
except
LookupError:
pass
else
:
self
.encoding
=
content_params[
'charset'
]
self
._post_parse_error
=
False
try
:
content_length
=
int
(environ.get(
'CONTENT_LENGTH'
))
except
(ValueError, TypeError):
content_length
=
0
self
._stream
=
LimitedStream(
self
.environ[
'wsgi.input'
], content_length)
self
._read_started
=
False
self
.resolver_match
=
None
def
_get_scheme(
self
):
return
self
.environ.get(
'wsgi.url_scheme'
)
def
_get_request(
self
):
warnings.warn(
'`request.REQUEST` is deprecated, use `request.GET` or '
'`request.POST` instead.'
, RemovedInDjango19Warning,
2
)
if
not
hasattr
(
self
,
'_request'
):
self
._request
=
datastructures.MergeDict(
self
.POST,
self
.GET)
return
self
._request
@cached_property
def
GET(
self
):
# The WSGI spec says 'QUERY_STRING' may be absent.
raw_query_string
=
get_bytes_from_wsgi(
self
.environ,
'QUERY_STRING'
, '')
return
http.QueryDict(raw_query_string, encoding
=
self
._encoding)
# ############### 看這裏看這裏 ###############
def
_get_post(
self
):
if
not
hasattr
(
self
,
'_post'
):
self
._load_post_and_files()
return
self
._post
# ############### 看這裏看這裏 ###############
def
_set_post(
self
, post):
self
._post
=
post
@cached_property
def
COOKIES(
self
):
raw_cookie
=
get_str_from_wsgi(
self
.environ,
'HTTP_COOKIE'
, '')
return
http.parse_cookie(raw_cookie)
def
_get_files(
self
):
if
not
hasattr
(
self
,
'_files'
):
self
._load_post_and_files()
return
self
._files
# ############### 看這裏看這裏 ###############
POST
=
property
(_get_post, _set_post)
FILES
=
property
(_get_files)
REQUEST
=
property
(_get_request)
|
因此,定義屬性共有兩種方式,分別是【裝飾器】和【靜態字段】,而【裝飾器】方式針對經典類和新式類又有所不一樣。
小結:
屬性:將方法僞形成字段
定義方式:
裝飾器 即:在方法上應用裝飾器 @property
靜態字段 即:在類中定義值爲property對象的靜態字段
新式類屬性能夠設置讀寫刪;
類成員的修飾符
類的全部成員在上一步驟中已經作了詳細的介紹,對於每個類的成員而言都有兩種形式:
私有成員和公有成員的定義不一樣:私有成員命名時,前兩個字符是下劃線。(特殊成員除外,例如:__init__、__call__、__dict__等)
1
2
3
4
|
class
C:
def
__init__(
self
):
self
.name
=
'公有字段'
self
.__foo
=
"私有字段"
|
靜態字段
# 公有靜態字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class
C:
name
=
"公有靜態字段"
def
func(
self
):
print
C.name
class
D(C):
def
show(
self
):
print
C.name
C.name
# 類訪問
obj
=
C()
obj.func()
# 類內部能夠訪問
obj_son
=
D()
obj_son.show()
# 派生類中能夠訪問
|
# 私有靜態字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class
C:
__name
=
"私有靜態字段"
def
func(
self
):
print
C.__name
class
D(C):
def
show(
self
):
print
C.__name
C.__name
# 類訪問 ==> 錯誤
obj
=
C()
obj.func()
# 類內部能夠訪問 ==> 正確
obj_son
=
D()
obj_son.show()
# 派生類中能夠訪問 ==> 錯誤
|
普通字段
ps:若是想要強制訪問私有字段,能夠經過 【對象._類名__私有字段明 】訪問(如:obj._C__foo),不建議強制訪問私有成員。
# 公有字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class
C:
def
__init__(
self
):
self
.foo
=
"公有字段"
def
func(
self
):
print
self
.foo
# 類內部訪問
class
D(C):
def
show(
self
):
print
self
.foo # 派生類中訪問
obj
=
C()
obj.foo
# 經過對象訪問
obj.func()
# 類內部訪問
obj_son
=
D();
obj_son.show()
# 派生類中訪問
|
# 私有字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class
C:
def
__init__(
self
):
self
.__foo
=
"私有字段"
def
func(
self
):
print
self
.foo
# 類內部訪問
class
D(C):
def
show(
self
):
print
self
.foo # 派生類中訪問
obj
=
C()
obj.__foo
# 經過對象訪問 ==> 錯誤
obj.func()
# 類內部訪問 ==> 正確
obj_son
=
D();
obj_son.show()
# 派生類中訪問 ==> 錯誤
|
方法、屬性的訪問於上述方式類似,即:私有成員只能在類內部使用
ps:非要訪問私有屬性的話,能夠經過 對象._類__屬性名
類的特殊成員
上文介紹了Python的類成員以及成員修飾符,從而瞭解到類中有字段、方法和屬性三大類成員,而且成員名前若是有兩個下劃線,則表示該成員是私有成員,私有成員只能由類內部調用。不管人或事物每每都有不按套路出牌的狀況,Python的類成員也是如此,存在着一些具備特殊含義的成員,詳情以下:
1. __doc__
表示類的描述信息。
1
2
3
4
5
6
7
|
class
Foo:
""" 描述類信息,這是用於看片的神奇 """
def
func(
self
):
pass
print
Foo.__doc__
#輸出:類的描述信息
|
2. __module__ 和 __class__
__module__ 表示當前操做的對象在那個模塊
__class__ 表示當前操做的對象的類是什麼
# lib/aa.py
# index.py
1
2
3
4
5
|
from
lib.aa
import
C
obj
=
C()
print
obj.__module__
# 輸出 lib.aa,即:輸出模塊
print
obj.__class__
# 輸出 lib.aa.C,即:輸出類
|
3. __init__
構造方法,經過類建立對象時,自動觸發執行。
1
2
3
4
5
6
|
class
Foo:
def
__init__(
self
, name):
self
.name
=
name
self
.age
=
18
obj
=
Foo(
'wupeiqi'
)
# 自動執行類中的 __init__ 方法
|
4. __del__
析構方法,當對象在內存中被釋放時,自動觸發執行。
注:此方法通常無須定義,由於Python是一門高級語言,程序員在使用時無需關心內存的分配和釋放,由於此工做都是交給Python解釋器來執行,因此,析構函數的調用是由解釋器在進行垃圾回收時自動觸發執行的。
1
2
3
|
class
Foo:
def
__del__(
self
):
pass
|
5. __call__
對象後面加括號,觸發執行。
注:構造方法的執行是由建立對象觸發的,即:對象 = 類名() ;而對於 __call__ 方法的執行是由對象後加括號觸發的,即:對象() 或者 類()()
1
2
3
4
5
6
7
8
9
|
class
Foo:
def
__init__(
self
):
pass
def
__call__(
self
,
*
args,
*
*
kwargs):
print
'__call__'
obj
=
Foo()
# 執行 __init__
obj()
# 執行 __call__
|
6. __dict__
類或對象中的全部成員
上文中咱們知道:類的普通字段屬於對象;類中的靜態字段和方法等屬於類,即:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class
Province:
country
=
'China'
def
__init__(
self
, name, count):
self
.name
=
name
self
.count
=
count
def
func(
self
,
*
args,
*
*
kwargs):
print
'func'
# 獲取類的成員,即:靜態字段、方法、
print
Province.__dict__
# 輸出:{'country': 'China', '__module__': '__main__', 'func': <function func at 0x10be30f50>, '__init__': <function __init__ at 0x10be30ed8>, '__doc__': None}
obj1
=
Province(
'HeBei'
,
10000
)
print
obj1.__dict__
# 獲取 對象obj1 的成員
# 輸出:{'count': 10000, 'name': 'HeBei'}
obj2
=
Province(
'HeNan'
,
3888
)
print
obj2.__dict__
# 獲取 對象obj1 的成員
# 輸出:{'count': 3888, 'name': 'HeNan'}
|
7. __str__
若是一個類中定義了__str__方法,那麼在打印 對象 時,默認輸出該方法的返回值。
1
2
3
4
5
6
7
|
class
Foo:
def
__str__(
self
):
return
'korala'
obj
=
Foo()
print
obj
# 輸出korala
|
8、__getitem__、__setitem__、__delitem__
用於索引操做,如字典。以上分別表示獲取、設置、刪除數據。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class
Foo(
object
):
def
__getitem__(
self
, key):
print
'__getitem__'
,key
def
__setitem__(
self
, key, value):
print
'__setitem__'
,key,value
def
__delitem__(
self
, key):
print
'__delitem__'
,key
obj
=
Foo()
result
=
obj[
'k1'
]
# 自動觸發執行 __getitem__
obj[
'k2'
]
=
'korala'
# 自動觸發執行 __setitem__
del
obj[
'k1'
]
# 自動觸發執行 __delitem__
|
9、__getslice__、__setslice__、__delslice__
該三個方法用於分片操做,如:列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class
Foo(
object
):
def
__getslice__(
self
, i, j):
print
'__getslice__'
,i,j
def
__setslice__(
self
, i, j, sequence):
print
'__setslice__'
,i,j
def
__delslice__(
self
, i, j):
print
'__delslice__'
,i,j
obj
=
Foo()
obj[
-
1
:
1
]
# 自動觸發執行 __getslice__
obj[
0
:
1
]
=
[
11
,
22
,
33
,
44
]
# 自動觸發執行 __setslice__
del
obj[
0
:
2
]
# 自動觸發執行 __delslice__
|
10. __iter__
用於迭代器,之因此列表、字典、元組能夠進行for循環,是由於類型內部定義了 __iter__。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
# 第一步
class
Foo(
object
):
pass
obj
=
Foo()
for
i
in
obj:
print
i
# 報錯:TypeError: 'Foo' object is not iterable
# 第二步
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class
Foo(
object
):
def
__iter__(
self
):
pass
obj
=
Foo()
for
i
in
obj:
print
i
# 報錯:TypeError: iter() returned non-iterator of type 'NoneType'
# 第三步
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class
Foo(
object
):
def
__init__(
self
, sq):
self
.sq
=
sq
def
__iter__(
self
):
return
iter
(
self
.sq)
obj
=
Foo([
11
,
22
,
33
,
44
])
for
i
in
obj:
print
i
|
以上步驟能夠看出,for循環迭代的實際上是 iter([11,22,33,44]) ,因此執行流程能夠變動爲:
1
2
3
4
5
6
|
#!/usr/bin/env python
# -*- coding:utf-8 -*-
obj
=
iter
([
11
,
22
,
33
,
44
])
for
i
in
obj:
print
i
|
11. __new__ 和 __metaclass__
閱讀如下代碼:
1
2
3
4
|
class
Foo(
object
):
def
__init__(
self
):
pass
obj
=
Foo()
# obj是經過Foo類實例化的對象
|
上述代碼中,obj 是經過 Foo 類實例化的對象,其實,不只 obj 是一個對象,Foo類自己也是一個對象,由於在Python中一切事物都是對象。
若是按照一切事物都是對象的理論:obj對象是經過執行Foo類的構造方法建立,那麼Foo類對象應該也是經過執行某個類的 構造方法 建立。
1
2
|
print
type
(obj)
# 輸出:<class '__main__.Foo'> 表示,obj 對象由Foo類建立
print
type
(Foo)
# 輸出:<type 'type'> 表示,Foo類對象由 type 類建立
|
因此,obj對象是Foo類的一個實例,Foo類對象是 type 類的一個實例,即:Foo類對象 是經過type類的構造方法建立。
那麼,建立類就能夠有兩種方式:
a). 普通方式
1
2
3
|
class
Foo(
object
):
def
func(
self
):
print
'hello korala'
|
b).特殊方式(type類的構造函數)
1
2
3
4
5
6
7
|
ef func(
self
):
print
'hello korala'
Foo
=
type
(
'Foo'
,(
object
,), {
'func'
: func})
#type第一個參數:類名
#type第二個參數:當前類的基類
#type第三個參數:類的成員<span style="font-family: 宋體; font-size: 16px; color: #ff0000;"><strong> </strong></span>
|
==》 類 是由 type 類實例化產生
那麼問題來了,類默認是由 type 類實例化產生,type類中如何實現的建立類?類又是如何建立對象?
答:類中有一個屬性 __metaclass__,其用來表示該類由 誰 來實例化建立,因此,咱們能夠爲 __metaclass__ 設置一個type類的派生類,從而查看 類 建立的過程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class
MyType(
type
):
def
__init__(
self
, what, bases
=
None
,
dict
=
None
):
super
(MyType,
self
).__init__(what, bases,
dict
)
def
__call__(
self
,
*
args,
*
*
kwargs):
obj
=
self
.__new__(
self
,
*
args,
*
*
kwargs)
self
.__init__(obj)
class
Foo(
object
):
__metaclass__
=
MyType
def
__init__(
self
, name):
self
.name
=
name
def
__new__(
cls
,
*
args,
*
*
kwargs):
return
object
.__new__(
cls
,
*
args,
*
*
kwargs)
# 第一階段:解釋器從上到下執行代碼建立Foo類
# 第二階段:經過Foo類建立obj對象
obj
=
Foo()
|
總結:一、面向對象的三大特性 封裝、繼承、多態(用不到)二、python ——> 封裝 兩種狀況:(1)多個方法共用一組變量,變量封裝到對象中; (2)遊戲 模板三、繼承 (1)基類、派生類 (2)多繼承 新式類、經典類 廣度優先(規則)、深度優先 ==> 面試四、類,對象,內存 ==> 關係圖五、類成員 字段:普通(對象)、靜態(類) 方法:普通(對象觸發)至少一個self,self=當前對象 類 ==> 類觸發 只有一個cls,cls=當前類 靜態 ==> 類觸發 任意參數 屬性:方法的變種,變成訪問時跟字段類似 定義方式:@property data = property(方法名) # obj.data = 執行方法,並獲取方法的返回值 新式類中的屬性: @property 修改 @方法名.setter 設置 @方法名.deleter 刪除六、類成員修飾符 公有 私有 以"__"開頭,只能內部訪問 非要訪問,對象._類名__成員七、對象後面加括號執行__call__方法,類後面加括號執行__init__方法八、__dict__ 類或對象中的全部成員對象由類建立,一切事物都是對象 ==> 類也是對象對象.__dict__ 查看對象裏有什麼東西類.__dict__ 查看類裏有什麼東西九、__str__輸出時會輸出返回值對象轉成字符串,類裏找方法,並獲取返回值