天天看點

Python高手之路【十一】python基礎之面向對象

建立類和對象

面向對象程式設計是一種程式設計方式,此程式設計方式的落地需要使用 “類” 和 “對象” 來實作,是以,面向對象程式設計其實就是對 “類” 和 “對象” 的使用。

類就是一個模闆,模闆裡可以包含多個函數,函數裡實作一些功能

對象則是根據模闆建立的執行個體,通過執行個體對象可以執行類中的函數

Python高手之路【十一】python基礎之面向對象

class是關鍵字,表示類建立對象,類名稱後加括号即可

注:類中的函數第一個參數必須是self(詳細見:類的三大特性之封裝)  

  類中定義的函數叫做 “方法”

#建立類
class Foo:
    def Bar(self):
        print("Bar")
    def Hello(self,name):
        print("I love %s" %name)
#根據Foo類建立obj對象
obj = Foo()
obj.Bar()   #執行Bar方法
obj.Hello('python')     #執行Hello方法
      

 self參數相當于php面向對象中的$this,誰調用它就指向誰

面向對象的三大特性

面向對象的三大特性是指:封裝、繼承和多态。

一、封裝

封裝,顧名思義就是将内容封裝到某個地方,以後再去調用被封裝在某處的内容。

是以,在使用面向對象的封裝特性時,需要:

  • 将内容封裝到某處
  • 從某處調用被封裝的内容

第一步:将内容封裝到某處

class Foo:
    def __init__(self,name,age):##__init__稱為構造方法,根據類建立對象時自動執行
        self.name = name
        self.age = age
obj1 = Foo('poe',21)##poe and 21分别封裝到obj1 self的name and age屬性中
print(obj1.name,obj1.age)
obj2 = Foo('jet',22)##jet and 22分别封裝到obj2 self的name and age屬性中
print(obj2.name,obj2.age)
      

self 是一個形式參數,當執行 obj1 = Foo('wupeiqi', 18 ) 時,self 等于 obj1

                              當執行 obj2 = Foo('alex', 78 ) 時,self 等于 obj2

是以,内容其實被封裝到了對象 obj1 和 obj2 中,每個對象中都有 name 和 age 屬性,在記憶體裡類似于下圖來儲存。

Python高手之路【十一】python基礎之面向對象

第二步:從某處調用被封裝的内容

調用被封裝的内容時,有兩種情況:

  • 通過對象直接調用
  • 通過self間接調用

1、通過對象直接調用被封裝的内容

上圖展示了對象 obj1 和 obj2 在記憶體中儲存的方式,根據儲存格式可以如此調用被封裝的内容:對象.屬性名

class Foo:
    def __init__(self,name,age):##__init__稱為構造方法,根據類建立對象時自動執行
        self.name = name
        self.age = age
obj1 = Foo('poe',21)##poe and 21分别封裝到obj1 self的name and age屬性中
print(obj1.name,obj1.age)# 直接調用obj1對象的name和age屬性
obj2 = Foo('jet',22)##jet and 22分别封裝到obj2 self的name and age屬性中
print(obj2.name,obj2.age)# 直接調用obj2對象的name和age屬性
      

2、通過self間接調用被封裝的内容

執行類中的方法時,需要通過self間接調用被封裝的内容

class Foo:
    def __init__(self,name,age):##__init__稱為構造方法,根據類建立對象時自動執行
        self.name = name
        self.age = age
    def detail(self):
        print(self.name)
        print(self.age)

obj1 = Foo("poe",21)
obj1.detail()# Python預設會将obj1傳給self參數,即:obj1.detail(obj1),是以,此時方法内部的 self = obj1,即:self.name 是 poe ;self.age 是 21
obj2 = Foo("jet",22)
obj2.detail()# Python預設會将obj1傳給self參數,即:obj1.detail(obj1),是以,此時方法内部的 self = obj1,即:self.name 是 jet ;self.age 是 22
      

綜上所述,對于面向對象的封裝來說,其實就是使用構造方法将内容封裝到 對象 中,然後通過對象直接或者self間接擷取被封裝的内容。

二、繼承

繼承,面向對象中的繼承和現實生活中的繼承相同,即:子可以繼承父的内容。

class Animal:
    def eat(self):
        print("%s eat" %self.name)
    def drink(self):
        print("%s drink" %self.name)

class Cat(Animal):
    def __init__(self,name):
        self.name = name
        self.breed = 'cat'
    def cry(self):
        print("cry")
class Dog(Animal):
    def __init__(self,name):
        self.name = name
        self.breed = 'dog'
    def cry(self):
        print("cry")

c1 = Cat('little cat')
c1.eat()
c2 = Cat('big cat')
c2.eat()

d1 = Dog('little black')
d1.eat()
      

是以,對于面向對象的繼承來說,其實就是将多個類共有的方法提取到父類中,子類僅需繼承父類而不必一一實作每個方法。

注:除了子類和父類的稱謂,你可能看到過 派生類 和 基類 ,他們與子類和父類隻是叫法不同而已。

那麼問題又來了,多繼承呢?

  • 是否可以繼承多個類
  • 如果繼承的多個類每個類中都定了相同的函數,那麼那一個會被使用呢?

1、Python的類可以繼承多個類,Java和C#中則隻能繼承一個類

2、Python的類如果繼承了多個類,那麼其尋找方法的方式如下圖:

Python高手之路【十一】python基礎之面向對象

假設G類在繼承時C類在前,F類在後,如:G(C,F)

黑色的箭頭表示繼承關系:B類繼承A類,C類繼承B類,D類繼承A類,F類繼承D類,G類繼承C類與F類

橘黃色箭頭表示查找順序,上圖中的查找順序為:G->C->B->F->D->A

如果A類沒有的繼承關系,那麼查找順序如下圖:

Python高手之路【十一】python基礎之面向對象
class Animal:
    def eat(self):
        print("%s eat" %self.name)
    def drink(self):
        print("%s drink" %self.name)
    def piao(self):
        print("Animal piao")

class Uncle:
    def piao(self):
        print('Uncle piao')
    def du(self):
        print('du')

class Cat(Uncle,Animal):#繼承了Uncle類和Animal類
    def __init__(self,name):
        self.name = name
        self.breed = 'cat'
    def cry(self):
        print("cry")

c1 = Cat('little cat')
c1.piao()
c2 = Cat('big cat')
c2.piao()
      

上面的Cat類繼承了Unlcle類和Animal類,兩個類中又都有piao()方法,那麼在調用該方法的時候,是按什麼順序執行的呢?執行哪個類裡面的piao()方法呢?

1:如果Cat類自身中有piao()方法,那就在調用piao()方法時先執行自身類中的piao()方法

2:如果Cat類自身中沒有piao()方法,像上面的代碼,就按繼承時的順序執行,class Cat(Uncle,Animal),這兩個類哪個寫在前面,就優先執行哪個類中的piao()方法

三、多态 

 Pyhon不支援Java和C#這一類強類型語言中多态的寫法,但是原生多态,其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)
      

擴充:

  重載:函數名相同,參數個數不同(python不支援)

  重寫:派生類中重新實作基類中的方法

  接口:python中沒有接口這一說

類和對象在記憶體中是如何儲存? 

類以及類中的方法在記憶體中隻有一份,而根據類建立的每一個對象都在記憶體中需要存一份,大緻如下圖:

Python高手之路【十一】python基礎之面向對象

如上圖所示,根據類建立對象時,對象中除了封裝 name 和 age 的值之外,還會儲存一個類對象指針,該值指向目前對象的類。

當通過 obj1 執行 【方法一】 時,過程如下:

  1. 根據目前對象中的 類對象指針 找到類中的方法
  2. 将對象 obj1 當作參數傳給 方法的第一個參數 self 

執行基類構造方法:

class A:
    def __init__(self):
        print('A contruct')
class B(A):
    def __init__(self):
        print('B contruct')
        super(B,self).__init__()#推薦使用此方法
        #A.__init__(self)#此方法也可以執行基類的構造方法
obj = B()
      

利用反射操作對象成員

class Foo:
    def __init__(self,name):
        self.name = name
    def show(self):
        print('show')

obj = Foo('poe')
#反射:類,隻能找到類裡的成員
#反射:對象,既可以找對象,也可以找類的成員
print(hasattr(Foo,'show'))
print(hasattr(obj,'name'))
      
##commons.py檔案
class Foo:
    def __init__(self,name):
        self.name = name
        gender = 'male'
    def show(self):
        print('show')


##index.py檔案

#導入子產品
m = __import__('commons',fromlist = True)
#找到子產品中的類
class_name = getattr(m,'Foo')
#根據類建立對象
obj = class_name("bruce")
#利用對象去找name的值
val = getattr(obj,'name')
print(val)
      

一個類中存在普通字段,靜态字段,普通方法,靜态方法,類方法那麼在通路這些成員的時候優先選擇自己的成員自己去通路:

通過類去通路的有:靜态字段,靜态方法,類方法

通過對象去通路的有:普通字段,普通方法

但請注意:這麼做隻是為了代碼的規範,這四個成員利用對象都是可以通路到的

class Province:
    #靜态字段,儲存類中,記憶體中隻有一個
    country = "China"
    def __init__(self,name):
        #普通字段,儲存對象中!有幾個對象,就有幾個字段
        self.name = name
    #普通方法,儲存在類中     
    def show(self):
        print('show')
    #靜态方法,靜态方法中不需要self參數
    @staticmethod
    def xo(arg1,arg2):
        return arg1+arg2
    #類方法,類方法也不需要self參數,但要有一個cls參數(class)
    @classmethod
    def xxoo(cls):
        print("xxoo",cls)

hunan = Province('hunan')
hunan.show()
print(hunan.xo(3,4))
print(Province.country)
print(Province.xo(1,2))
Province.xxoo()#調用類方法,cls參數會自動獲得目前類的類名
      

 類的特性:

class Province:
    def start(self):
        temp = "%s is hero" %self.name
        return temp
    #特性,将方法僞造成一種字段
    @property
    def end(self):
        temp = "%s is hero" %self.name
        return temp

obj = Province('poe')
print(obj.start())
print(obj.end)#通路特性時,後面不需要加括号,是以也就無法傳遞參數
      

 類的特性預設情況下是無法像普通字段那樣在類外進行重新指派,如果需要對特性進行重新指派,需要用到一個裝飾器:

class Province:
    #靜态字段,儲存類中,記憶體中隻有一個
    country = "China"
    def __init__(self,name):
        #普通字段,儲存對象中!有幾個對象,就有幾個字段
        self.name = name
    #普通方法,儲存在類中     
    def show(self):
        print('show')
    #靜态方法,靜态方法中不需要self參數
    @staticmethod
    def xo(arg1,arg2):
        return arg1+arg2
    #類方法,類方法也不需要self參數,但要有一個cls參數(class)
    @classmethod
    def xxoo(cls):
        print("xxoo",cls)

    def start(self):
        temp = "%s is hero" %self.name
        return temp
    #特性,将方法僞造成一種字段
    @property
    def end(self):
        temp = "%s is hero" %self.name
        return temp
    @end.setter
    def end(self,value):
        print(value)
        self.name = value

obj = Province('poe')
p = obj.end
print(p)
#設定特性
obj.end = "bruce"
print(obj.end)
################################################
poe is hero
bruce
bruce is hero
      

 快速判斷成員由類執行還是對象執行:

有self的,對象調用,無self的類調用

成員修飾符

對于每一個類的成員而言都有兩種形式:

  • 公有成員,在任何地方都能通路
  • 私有成員,隻有在類的内部才能通路

私有成員和公有成員的定義不同:私有成員命名時,前兩個字元是下劃線。(特殊成員除外,例如:__init__、__call__、__dict__等)

class A:
    def __init__(self):
        self.name = 'public'
        self.__nick = 'private'

obj = A()
print(obj.name)
print(obj.__nick)
##################################################
public
Traceback (most recent call last):
  File "index.py", line 11, in <module>
    print(obj.__nick)
AttributeError: 'A' object has no attribute '__nick'
      

如何通路類中的靜态成員:兩種方法

法一:在類中添加一個方法

class A:
    def __init__(self):
        self.name = 'public'
        self.__nick = 'private'

    def fetch(self):
        print(self.__nick)

obj = A()
obj.fetch()
      

法二:使用python的特定文法(此方法不推薦使用)

class A:
    def __init__(self):
        self.name = 'public'
        self.__nick = 'private'

obj = A()
print(obj._A__nick)#A前面一個下劃線
      

類的特殊成員

1:__init__:構造方法

構造方法,通過類建立對象時,自動觸發執行。

class Foo:

    def __init__(self, name):
        self.name = name
        self.age = 18


obj = Foo('poe') # 自動執行類中的 __init__ 方法      

2:__del__:析構方法

析構方法,當對象在記憶體中被釋放時,自動觸發執行。

注:此方法一般無須定義,因為Python是一門進階語言,程式員在使用時無需關心記憶體的配置設定和釋放,因為此工作都是交給Python解釋器來執行,是以,析構函數的調用是由解釋器在進行垃圾回收時自動觸發執行的。

class Foo:

    def __del__(self):
        pass      

3:__call__方法用于執行個體自身的調用

注:構造方法的執行是由建立對象觸發的,即:對象 = 類名() ;而對于 __call__ 方法的執行是由對象後加括号觸發的,即:對象() 或者 類()()

class Foo:
    def __init__(self):
        print('init')
    # __call__方法用于執行個體自身的調用
    def __call__(self,*args,**kwargs):
        print('call')
        return 1
# obj = Foo()
# obj()
r = Foo()()
print(r)
##################################################
init
call
1
      

4:__getitem__ , __setitem__ , __delitem__

用于索引操作,如字典。以上分别表示擷取、設定、删除資料

class Foo:

    def __getitem__(self,item):
        print(item)
    def __setitem__(self,key,value):
        print(key,value)
    def __delitem__(self,key):
        print(key)

obj = Foo()
obj['kk']
obj['aa'] = 123
del obj['kk']
obj['kk']
      

5:__dict__查詢類中的成員,對象中的成員

class Foo:
    def __init__(self):
        self.name = 'poe'
    def __call__(self):
        print('call')
    def __getitem__(self,item):
        print(item)
    def __setitem__(self,key,value):
        print(key,value)
    def __delitem__(self,key):
        print(key)

obj = Foo()              #__init__
print(obj.__dict__)
print(Foo.__dict__)
      

6: __iter__

用于疊代器,之是以清單、字典、元組可以進行for循環,是因為類型内部定義了 __iter__ 

class Foo:
    def __iter__(self):
        yield 1
        yield 2
        yield 3
obj = Foo()
for i in obj:
    print(i)
      

7:__new__ 和 __metaclass__

閱讀以下代碼:

class Foo(object):
 
    def __init__(self):
        pass
 
obj = Foo()   # obj是通過Foo類執行個體化的對象
      

上述代碼中,obj 是通過 Foo 類執行個體化的對象,其實,不僅 obj 是一個對象,Foo類本身也是一個對象,因為在Python中一切事物都是對象。

如果按照一切事物都是對象的理論:obj對象是通過執行Foo類的構造方法建立,那麼Foo類對象應該也是通過執行某個類的 構造方法 建立。

print type(obj) # 輸出:<class '__main__.Foo'>     表示,obj 對象由Foo類建立
print type(Foo) # 輸出:<type 'type'>              表示,Foo類對象由 type 類建立
      

是以,obj對象是Foo類的一個執行個體,Foo類對象是 type 類的一個執行個體,即:Foo類對象 是通過type類的構造方法建立。

那麼,建立類就可以有兩種方式:

a). 普通方式

class Foo(object):
 
    def func(self):
        print('hello python')      

b).特殊方式(type類的構造函數)

def func(self):
    print('hello python')

Foo = type('Foo',(object,),{'func':func})
print(Foo,type(Foo))
#type第一個參數:類名
#type第二個參數:目前類的基類
#type第三個參數:類的成員
      

那麼問題來了,類預設是由 type 類執行個體化産生,type類中如何實作的建立類?類又是如何建立對象?

答:類中有一個屬性 __metaclass__,其用來表示該類由 誰 來執行個體化建立,是以,我們可以為 __metaclass__ 設定一個type類的派生類,進而檢視 類 建立的過程。

Python高手之路【十一】python基礎之面向對象

8:__doc__

  表示類的描述資訊

class Foo:
    """ 描述類資訊,這是用于看片的神奇 """

    def func(self):
        pass

print Foo.__doc__
#輸出:類的描述資訊
      

9:__str__

如果一個類中定義了__str__方法,那麼在列印 對象 時,預設輸出該方法的傳回值。

class Foo:

    def __str__(self):
        return('poe')


obj = Foo()
print(obj)
# 輸出:poe      

10:__module__ 和  __class__ 

  __module__ 表示目前操作的對象在那個子產品

  __class__     表示目前操作的對象的類是什麼

# lib/commons.py
class C:
    def __init__(self):
        self.name = 'poe'
      
# index.py
from lib.commons import C

obj = C()
print(obj.__module__)# 輸出 lib.commons,即:輸出子產品
print(obj.__class__)# 輸出 lib.commons.C,即:輸出類
      

異常處理

1、異常基礎

在程式設計過程中為了增加友好性,在程式出現bug時一般不會将錯誤資訊顯示給使用者,而是顯示一個提示的頁面,通俗來說就是不讓使用者看見大黃頁!!!

try:
    pass
except Exception,ex:
    pass
      

需求:将使用者輸入的兩個數字相加

while True:
    num1 = raw_input('num1:')
    num2 = raw_input('num2:')
    try:
        num1 = int(num1)
        num2 = int(num2)
        result = num1 + num2
    except Exception, e:
        print '出現異常,資訊如下:'
        print e
      

2、異常種類

python中的異常種類非常多,每個異常專門用于處理某一項異常!!!

Python高手之路【十一】python基礎之面向對象
Python高手之路【十一】python基礎之面向對象
AttributeError 試圖通路一個對象沒有的屬性,比如foo.x,但是foo沒有屬性x
IOError 輸入/輸出異常;基本上是無法打開檔案
ImportError 無法引入子產品或包;基本上是路徑問題或名稱錯誤
IndentationError 文法錯誤(的子類) ;代碼沒有正确對齊
IndexError 下标索引超出序列邊界,比如當x隻有三個元素,卻試圖通路x[5]
KeyError 試圖通路字典裡不存在的鍵
KeyboardInterrupt Ctrl+C被按下
NameError 使用一個還未被賦予對象的變量
SyntaxError Python代碼非法,代碼不能編譯(個人認為這是文法錯誤,寫錯了)
TypeError 傳入對象類型與要求的不符合
UnboundLocalError 試圖通路一個還未被設定的局部變量,基本上是由于另有一個同名的全局變量,
導緻你以為正在通路它
ValueError 傳入一個調用者不期望的值,即使值的類型是正确的      

常用異常

Python高手之路【十一】python基礎之面向對象
Python高手之路【十一】python基礎之面向對象
ArithmeticError
AssertionError
AttributeError
BaseException
BufferError
BytesWarning
DeprecationWarning
EnvironmentError
EOFError
Exception
FloatingPointError
FutureWarning
GeneratorExit
ImportError
ImportWarning
IndentationError
IndexError
IOError
KeyboardInterrupt
KeyError
LookupError
MemoryError
NameError
NotImplementedError
OSError
OverflowError
PendingDeprecationWarning
ReferenceError
RuntimeError
RuntimeWarning
StandardError
StopIteration
SyntaxError
SyntaxWarning
SystemError
SystemExit
TabError
TypeError
UnboundLocalError
UnicodeDecodeError
UnicodeEncodeError
UnicodeError
UnicodeTranslateError
UnicodeWarning
UserWarning
ValueError
Warning
ZeroDivisionError

更多異常      

更多異常

Python高手之路【十一】python基礎之面向對象
Python高手之路【十一】python基礎之面向對象
dic = ["Jet", 'Jacky']
try:
    dic[10]
except IndexError, e:
    print e      

執行個體:IndexError

Python高手之路【十一】python基礎之面向對象
Python高手之路【十一】python基礎之面向對象
s1 = 'hello'
try:
    int(s1)
except ValueError, e:
    print e      

ValueError

對于上述執行個體,異常類隻能用來處理指定的異常情況,如果非指定異常則無法處理。

# 未捕獲到異常,程式直接報錯
 
s1 = 'hello'
try:
    int(s1)
except IndexError,e:
    print e
      

是以,寫程式時需要考慮到try代碼塊中可能出現的任意異常,可以這樣寫:

s1 = 'hello'
try:
    int(s1)
except IndexError,e:
    print e
except KeyError,e:
    print e
except ValueError,e:
    print e
      

萬能異常 在python的異常中,有一個萬能異常:Exception,他可以捕獲任意異常,即:

s1 = 'hello'
try:
    int(s1)
except Exception,e:
    print e
      

接下來你可能要問了,既然有這個萬能異常,其他異常是不是就可以忽略了!

答:當然不是,對于特殊處理或提醒的異常需要先定義,最後定義Exception來確定程式正常運作。

s1 = 'hello'
try:
    int(s1)
except KeyError,e:
    print '鍵錯誤'
except IndexError,e:
    print '索引錯誤'
except Exception, e:
    print '錯誤'
      

3、異常其他結構

try:
    # 主代碼塊
    pass
except KeyError,e:
    # 異常時,執行該塊
    pass
else:
    # 主代碼塊執行完,執行該塊
    pass
finally:
    # 無論異常與否,最終執行該塊
    pass
      

4、主動觸發異常

try:
    raise Exception('錯誤了。。。')
except Exception,e:
    print e
      

5、自定義異常

class MyException(Exception):
 
    def __init__(self, msg):
        self.message = msg
 
    def __str__(self):
        return self.message
 
try:
    raise MyException('我的異常')
except MyException,e:
    print e
      

6、斷言

# assert 條件
 
assert 1 == 1
 
assert 1 == 2