python教程12、面向物件進階
上一篇《面向物件基礎》文章介紹了面向物件基本知識:
- 面向物件是一種程式設計方式,此程式設計方式的實現是基於對 類 和 物件 的使用
- 類 是一個模板,模板中包裝了多個“函式”供使用(可以講多函式中公用的變數封裝到物件中)
- 物件,根據模板建立的例項(即:物件),例項用於呼叫被包裝在類中的函式
- 面向物件三大特性:封裝、繼承和多型
本篇將詳細介紹Python 類的成員、成員修飾符、類的特殊成員。
一、類的成員
類的成員可以分為三大類:屬性、方法和包裝
(很重要的一句話,例項可以訪問例項屬性和方法包括類中的所有屬性和方法,但是類不可以訪問例項中的屬性和方法,例項中的屬性和方法需要例項化後才能呼叫和訪問,但類依然不可以訪問和呼叫方法)
注:所有成員中,只有例項屬性的內容儲存物件中,即:根據此類建立了多少物件,在記憶體中就有多少個例項屬性。而其他的成員,則都是儲存在類中,即:無論物件的多少,在記憶體中只建立一份。
一、屬性
屬性包括:例項屬性和類屬性,他們在定義和使用中有所區別,而最本質的區別是記憶體中儲存的位置不同
- 例項屬性屬於物件
- 類屬性屬於類
屬性的定義和使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class
Province:
# 類屬性
country =
'中國'
def
__init__(
self
, name):
# 例項屬性
self
.name
=
name
# 例項屬性需要類例項化,然後通過物件訪問
obj
=
Province(
'河北省'
)
print
obj.name
# 直接訪問類屬性,訪問類屬性則不需要例項化
Province.country
|
由上述程式碼可以看出【例項屬性需要通過物件來訪問】【類屬性通過類訪問】,在使用上可以看出例項屬性和類屬性的歸屬是不同的。其在內容的儲存方式類似如下圖:
由上圖可是:
- 類屬性在記憶體中只儲存一份
- 例項屬性在每個物件中都要儲存一份
應用場景: 通過類建立物件時,如果每個物件都具有相同的屬性,那麼就使用類屬性
二、方法
方法包括:普通方法、靜態方法和類方法,三種方法在記憶體中都歸屬於類,區別在於呼叫方式不同。
- 普通方法:由物件呼叫;至少一個self引數;執行普通方法時,自動將呼叫該方法的物件賦值給self;self是例項的變數名;(類不能呼叫例項的方法)
- 靜態方法:由類呼叫;預設無引數,可以任意引數;(物件也能呼叫)
- 類方法: 由類呼叫; 至少一個cls引數;執行類方法時,自動將呼叫該方法的類複製給cls;cls其實是類名,類方法其實是靜態方法的一個變種;(物件也能呼叫)
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 |
class
Province:
country
=
"中國"
def
__init__(
self
,name):
self
.name
=
name
def
show(
self
):
"""普通方法,由物件呼叫執行(方法屬於類),例項化才能呼叫"""
print
(
self
.name)
@staticmethod
def
f1(arg1,arg2):
"""靜態方法,由類呼叫執行,可以沒有引數,或者任意引數"""
print
(arg1,arg2)
@classmethod
def
f2(
cls
):
"""類方法至少要有cls一個引數,cls就是類名,python自動會傳,就像self"""
print
(
cls
)
obj
=
Province(
'python'
)
# 類例項化成物件
print
(obj.name)
# 通過物件訪問例項屬性
obj.show()
# 通過物件執行例項方法
Province.f1(
1111
,
2222
)
# 靜態方法,通過類呼叫
Province.f2()
# 類方法,通過類呼叫
obj.f1(
'python'
,
'linux'
)
# 物件也能呼叫靜態方法
obj.f2()
# 物件也能呼叫類方法
|
三、包裝
如果你已經瞭解Python類中的方法,那麼包裝就非常簡單了,因為Python中的包裝其實是例項方法的變種。包裝是將方法包裝成屬性,屬性通過物件呼叫,包裝後的方法也是通過一樣的方式呼叫,並且無需()
對於包裝,有以下三個知識點:
- 包裝的基本使用
- 包裝的兩種定義方式
1、包裝的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# ############### 定義 ###############
class
Foo:
def
func(
self
):
pass
# 設定包裝
@property
def
prop(
self
):
print
(
"將方法包裝成屬性訪問即包裝"
)
return
123
# ############### 呼叫 ###############
foo_obj
=
Foo()
foo_obj.func()
ret
=
foo_obj.prop
#呼叫屬性並接收返回值
print
(ret)
|
由包裝的定義和呼叫要注意一下幾點:
- 定義時,在例項方法的基礎上新增 @property 裝飾器;
- 定義時,僅有一個self引數
- 呼叫時,無需括號
方法:foo_obj.func()
屬性:foo_obj.prop
注意:包裝存在意義是:訪問屬性時可以製造出和訪問欄位完全相同的假象
包裝由方法變種而來,如果Python中沒有包裝,方法完全可以代替其功能。
例項:對於主機列表頁面,每次請求不可能把資料庫中的所有內容都顯示到頁面上,而是通過分頁的功能區域性顯示,所以在向資料庫中請求資料時就要顯示的指定獲取從第m條到第n條的所有資料(即:limit m,n),這個分頁的功能包括:
- 根據使用者請求的當前頁和總資料條數計算出 m 和 n
- 根據m 和 n 去資料庫中請求資料
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
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
|
2、包裝的兩種定義方式
包裝的定義有兩種方式:
- 裝飾器 即:在方法上應用裝飾器
- 類屬性 即:在類中定義值為property物件的類屬性
(1)裝飾器方式:在類的例項方法上應用@property裝飾器
我們知道Python中的類有經典類和新式類,新式類的屬性比經典類的屬性豐富。( 如果類繼承object,那麼該類是新式類 )
經典類,具有一種@property裝飾器(如上一步例項)
經典類中的屬性只有一種訪問方式,其對應被 @property 修飾的方法
1 2 3 4 5 6 7 8 9 |
# ############### 定義 ###############
class
Goods:
@property
def
price(
self
):
return
"python"
# ############### 呼叫 ###############
obj
=
Goods()
result
=
obj.price
# 自動執行 @property 修飾的 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 26 27 |
class
Pager:
def
__init__(
self
,all_count):
self
.all_count
=
all_count
@property
def
all_pager(
self
):
a1,a2
=
divmod
(
self
.all_count,
10
)
if
a2
=
=
0
:
return
a1
else
:
return
a1
+
1
@all_pager
.setter
def
all_pager(
self
,value):
print
(value)
@all_pager
.deleter
def
all_pager(
self
):
print
(
'del all_page'
)
p
=
Pager(
101
)
ret
=
p.all_pager
print
(ret)
p.all_pager
=
115
del
p.all_pager
|
(2)類屬性方式:建立值為property物件的類方法(經典類與新式類無差別)
property的構造方法中有個四個引數:
- 第一個引數是方法名,呼叫
物件.方法
時自動觸發執行方法 - 第二個引數是方法名,呼叫
物件.方法 = XXX
時自動觸發執行方法 - 第三個引數是方法名,呼叫
del 物件.方法
時自動觸發執行方法 - 第四個引數是字串,呼叫
物件.方法.__doc__
,此引數是該屬性的描述資訊
可以根據他們幾個方法的訪問特點,分別將三個方法定義為對同一個屬性:獲取、修改、刪除
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
Pager:
def
__init__(
self
,all_count):
self
.all_count
=
all_count
def
f1(
self
):
return
123
def
f2(
self
,value):
print
(value)
def
f3(
self
):
print
(
"del p.foo"
)
foo
=
property
(fget
=
f1,fset
=
f2,fdel
=
f3)
p
=
Pager(
101
)
result
=
p.foo
# 自動呼叫第一個引數中定義的方法:f1方法,result用於接收返回值
print
(result)
p.foo
=
"python"
# 自動呼叫第二個引數中定義的方法:f2方法,並將"python"當作引數傳入
del
p.foo
# 自動呼叫第二個引數中定義的方法:f3方法
p.foo.__doc__
# 自動獲取第四個引數中設定的值:description...
|
注意:Python WEB框架 Django 的檢視中 request.POST 就是使用的類屬性的方式建立的方法
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'
|