1. 程式人生 > >python教程12、面向物件進階

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   # ############### 呼叫 ###############   =  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' )     =  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)     =  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'