Python——對象

面向對象技術簡介

  • 類(Class): 用來描述具有相同的屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。對象是類的實例。
  • 方法:類中定義的函數。
  • 類變量:類變量在整個實例化的對象中是公用的。類變量定義在類中且在函數體之外。類變量通常不作爲實例變量使用。
  • 數據成員:類變量或者實例變量用於處理類及其實例對象的相關的數據。
  • 方法重寫:如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆蓋(override),也稱爲方法的重寫。
  • 局部變量:定義在方法中的變量,只作用於當前實例的類。
  • 實例變量:在類的聲明中,屬性是用變量來表示的。這種變量就稱爲實例變量,是在類聲明的內部但是在類的其他成員方法之外聲明的。
  • 繼承:即一個派生類(derived class)繼承基類(base class)的字段和方法。繼承也允許把一個派生類的對象作爲一個基類對象對待。例如,有這樣一個設計:一個Dog類型的對象派生自Animal類,這是模擬"是一個(is-a)"關係(例圖,Dog是一個Animal)。
  • 實例化:創建一個類的實例,類的具體對象。
  • 對象:通過類定義的數據結構實例。對象包括兩個數據成員(類變量和實例變量)和方法。
    和其它編程語言相比,Python 在儘可能不增加新的語法和語義的情況下加入了類機制。
    Python中的類提供了面向對象編程的所有基本功能:類的繼承機制允許多個基類,派生類可以覆蓋基類中的任何方法,方法中可以調用基類中的同名方法。
    對象可以包含任意數量和類型的數據。

類對象

類對象支持兩種操作:屬性引用和實例化。
屬性引用使用和 Python 中所有的屬性引用一樣的標準語法:obj.name。
類對象創建後,類命名空間中所有的命名都是有效屬性名。所以如果類定義是這樣:

實例(Python 3.0+)
#!/usr/bin/python3
 
class MyClass:
    """一個簡單的類實例"""
    i = 12345
    def f(self):
        return 'hello world'

#實例化類
x = MyClass()
 
# 訪問類的屬性和方法
print("MyClass 類的屬性 i 爲:", x.i)
print("MyClass 類的方法 f 輸出爲:", x.f())
以上創建了一個新的類實例並將該對象賦給局部變量 x,x 爲空的對象。

執行以上程序輸出結果爲:

MyClass 類的屬性 i 爲: 12345
MyClass 類的方法 f 輸出爲: hello world

self代表類的實例,而非類

類的方法與普通的函數只有一個特別的區別——它們必須有一個額外的第一個參數名稱, 按照慣例它的名稱是 self。

class Test:
    def prt(self):
        print(self)
        print(self.__class__)
 
t = Test()
t.prt()
以上實例執行結果爲:

<__main__.Test instance at 0x100771878>
__main__.Test

從執行結果可以很明顯的看出,self 代表的是類的實例,代表當前對象的地址,而 self.__class__ 則指向類。
self 不是 python 關鍵字,我們把他換成 runoob 也是可以正常執行的:

class Test:
    def prt(runoob):
        print(runoob)
        print(runoob.__class__)
 
t = Test()
t.prt()
以上實例執行結果爲:

<__main__.Test instance at 0x100771878>
__main__.Test

類的方法

在類的內部,使用 def 關鍵字來定義一個方法,與一般函數定義不同,類方法必須包含參數 self, 且爲第一個參數,self 代表的是類的實例。
**在python中定義私有變量只需在變量前加上兩個下劃線,eg:__name,但是可以通過實例._類名__變量名,同理私有方法。

實例(Python 3.0+)
#!/usr/bin/python3
 
#類定義
class people:
    #定義基本屬性
    name = ''
    age = 0
    #定義私有屬性,私有屬性在類外部無法直接進行訪問
    __weight = 0
    #定義構造方法
    def __init__(self,n,a,w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        print("%s 說: 我 %d 歲。" %(self.name,self.age))
 
# 實例化類
p = people('runoob',10,30)
p.speak()
p._people__weight
執行以上程序輸出結果爲:

runoob 說:10 歲。
30

私有變量

默認情況下,Python中的成員函數和成員變量都是公開的(public),在python中沒有類似public,private等關鍵詞來修飾成員函數和成員變量。
在python中定義私有變量只需要在變量名或函數名前加上 ”__“兩個下劃線,那麼這個函數或變量就是私有的了。
在內部,python使用一種 name mangling 技術,將 __membername替換成 _classname__membername,也就是說,類的內部定義中,
所有以雙下劃線開始的名字都被"翻譯"成前面加上單下劃線和類名的形式。
例如:爲了保證不能在class之外訪問私有變量,Python會在類的內部自動的把我們定義的__spam私有變量的名字替換成爲
_classname__spam(注意,classname前面是一個下劃線,spam前是兩個下劃線),因此,用戶在外部訪問__spam的時候就會
提示找不到相應的變量。 python中的私有變量和私有方法仍然是可以訪問的;訪問方法如下:
私有變量:實例._類名__變量名
私有方法:實例._類名__方法名()

其實,Python並沒有真正的私有化支持,但可用下劃線得到僞私有。 儘量避免定義以下劃線開頭的變量!
(1)_xxx "單下劃線 " 開始的成員變量叫做保護變量,意思是隻有類實例和子類實例能訪問到這些變量,
需通過類提供的接口進行訪問;不能用’from module import *'導入
(2)__xxx 類中的私有變量/方法名 (Python的函數也是對象,所以成員方法稱爲成員變量也行得通。),
" 雙下劃線 " 開始的是私有成員,意思是隻有類對象自己能訪問,連子類對象也不能訪問到這個數據。
(3)xxx 系統定義名字,前後均有一個“雙下劃線” 代表python裏特殊方法專用的標識,如 init()代表類的構造函數。

小漏洞:派生類和基類取相同的名字就可以使用基類的私有變量

[python] view plain copy
 
class A():  
    def __init__(self):  
        self.__name='python' #翻譯成self._A__name='python'  
      
class B(A):  
    def func(self):  
        print self.__name #翻譯成print self._B__name  
  
instance=B()  
#instance.func()#報錯:AttributeError: B instance has no attribute '_B__name'  
print instance.__dict__  
print instance._A__name  
運行結果:
{'_A__name': 'python'}
python

類繼承

調用父類構造方法

BaseClassName(示例中的基類名)必須與派生類定義在一個作用域內。除了類,還可以用表達式,基類定義在另一個模塊中時這一點非常有用:

class DerivedClassName(modname.BaseClassName):
實例(Python 3.0+)
#!/usr/bin/python3
 
#類定義
class people:
    #定義基本屬性
    name = ''
    age = 0
    #定義私有屬性,私有屬性在類外部無法直接進行訪問
    __weight = 0
    #定義構造方法
    def __init__(self,n,a,w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        print("%s 說: 我 %d 歲。" %(self.name,self.age))
 
#單繼承示例
class student(people):
    grade = ''
    def __init__(self,n,a,w,g):
        #調用父類的構函,如果不調用會覆蓋原來父類的方法,則不能使用父類的屬性,self是子類的實例對象
        people.__init__(self,n,a,w)
        self.grade = g
    #覆寫父類的方法
    def speak(self):
        print("%s 說: 我 %d 歲了,我在讀 %d 年級"%(self.name,self.age,self.grade))
s = student('ken',10,60,3)
s.speak()
執行以上程序輸出結果爲:

ken 說:10 歲了,我在讀 3 年級

嘗試不繼承父類的__init__函數,可以如下面的寫法


#!/usr/bin/python3
 
#類定義
class people:
    #定義基本屬性
    name = ''
    age = 0
    #定義私有屬性,私有屬性在類外部無法直接進行訪問
    __weight = 0
    #定義構造方法
    def __init__(self,n,a,w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        print("%s 說: 我 %d 歲。" %(self.name,self.age))
 
#單繼承示例
class student(people):
    def __init__(self,n,a,w,g):
        
        #調用父類的構函,如果不調用會覆蓋原來父類的方法,則不能使用父類的屬性,self是子類的實例對象
        self.grade = g
    #覆寫父類的方法
    def speak(self):
        print("%s 說: 我 %d 歲了,我在讀 %d 年級"%(self.name,self.age,self.grade))
 
 
 
s = student('ken',10,60,3)
people.__init__(s,'ken',10,60)
s.speak()
執行以上程序輸出結果爲:

ken 說:10 歲了,我在讀 3 年級

圓括號中基類的順序,若是基類中有相同的方法名,而在子類使用時未指定,python從左至右搜索 即方法在子類中未找到時,從左到右查找基類中是否包含方法。

使用super函數

#!/usr/bin/python3
 
#類定義
class people:
    #定義基本屬性
    name = ''
    age = 0
    #定義私有屬性,私有屬性在類外部無法直接進行訪問
    __weight = 0
    #定義構造方法
    def __init__(self,n,a,w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        print("%s 說: 我 %d 歲。" %(self.name,self.age))
 
#單繼承示例
class student(people):
    def __init__(self,n,a,w,g):
        #使用super()函數自動繼承父類的構造方法
        super().__init__(n,a,w)
        self.grade = g
    #覆寫父類的方法
    def speak(self):
        print("%s 說: 我 %d 歲了,我在讀 %d 年級"%(self.name,self.age,self.grade))
s = student('ken',10,60,3)
s.speak()
執行以上程序輸出結果爲:

ken 說:10 歲了,我在讀 3 年級

MIX-in編程機制

類,類對象,實例對象

類對象的屬性是靜態的和方法都是靜態的。

class C:
    count = 0  #類對象的屬性
    def __init__(self,num):
        self.num = num  # 實例對象屬性
a =C(1)
b=C(2)
print(id(C.count))
print(id(a.count))
print(id(b.count))
b.count +=10  # 定義一個新的實例對象屬性覆蓋了原來的類對象屬性
print(id(C.count))
print(id(a.count))
print(id(b.count))
# print(id(C.num))  這個會報錯,因爲這是實例對象的屬性,類對象無法訪問
print(id(a.num))
print(id(b.num))

結果:

140724793946928
140724793946928
140724793946928
140724793946928
140724793946928
140724793947248
140724793946960
140724793946992

綁定

python嚴格要求方法需要有實例才能被調用,這種限制其實就是綁定。

class BB:
    def printBB():
        print("BB")
BB.printBB()
bb = BB()
bb.printBB()
class BB:
    def printBB():
        print("BB")
BB.printBB()
bb = BB()
bb.printBB()
BB
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-166-ad50d8768f26> in <module>
      4 BB.printBB()
      5 bb = BB()
----> 6 bb.printBB()

TypeError: printBB() takes 0 positional arguments but 1 was given

這個例子告訴我們,bb.printBB()實際執行的是bb.printBB(bb)。所以方法中需要加入self這個綁定實例對象。

>>>class CC:     #類
    def setxy(self,x,y):
       self.x = x
       self.y = y
    def printxy(self):
       print(self.x,self.y)
>>> dd= CC()   #實例對象,類對象
>>>dd.__dict__  #查看實例對象所擁有的屬性
{}
>>>CC.__dict__  #查看類對象所擁有的屬性
mappingproxy({'setxy':<function CC.setxy at 0x00000000031F9B70>, 'printxy': <functionCC.printxy at 0x00000000031F9BF8>, '__module__': '__main__', '__weakref__':<attribute '__weakref__' of 'CC' objects>, '__dict__': <attribute '__dict__'of 'CC' objects>, '__doc__': None})
>>>dd.setxy (4,5)  #實例對象中傳入x,y
>>>dd.__dict__  #實例對象就有屬性了,這兩個屬性緊屬於實例對象的,類對象中是沒有的
{'y': 5, 'x': 4}     #類對象中是沒有實例對象傳入的,這歸功與綁定這個功能,self

爲什麼實例對象調用方法後類對象中沒有實例對象的屬性?
實例對象調用方法時,dd.setxy(dd,4,5) 實際上是這樣的,也就是(self.x = x;self.y = y)dd.x=4,dd.y=5,那麼4,5存放在實例對象的空間,故這兩個屬性只屬於實例對象的。(實例對象調用類方法時,先把自己傳給self,self.x也就是dd.x.)
類對象與實例對象差別:

把類對象CC刪除後,del CC,再實例化就會報錯,但是已經實例化對象dd仍然可以調用類對象中的方法:

>>> delCC
>>>dd.setxy (3,4)
>>>dd.__dict__
{'y': 4, 'x': 3}
>>> dd =CC()
Traceback (most recent call last):
 File"<pyshell#45>", line 1, in <module>
  dd =CC()
NameError: name 'CC' is not defined
>>>dd.printxy ()
3 4

爲什麼已經實例化對象dd仍然可以調用類對象中的方法?
類中定義的屬性是靜態變量,方法也一樣,就算類對象被刪除了,屬性和方法一樣存放在內存中,故實例對象仍然可以從內存中調用類的方法和屬性,除非程序退出。所以創建一個類後最好先實例化再使用類對象中的方法,不要直接利用類對象調用方法。
self.x self相當於實例對象的名字,.x就是實例的空間了

類使用的注意點

屬性名覆蓋了方法,無法調用方法。

class C:
    def x(self):
        print("x-man!")
c =C()
c.x =1
c.x()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-145-67aee95725e2> in <module>
      4 c =C()
      5 c.x =1
----> 6 c.x()

TypeError: 'int' object is not callable
  • 所有命名時,屬性名名詞,方法名動詞。
  • 不要試圖在一個類中定義所有能夠想到的屬性和方法,應該利用繼承和組合機制來進行擴展。

Property內置函數

property() 函數的作用是在新式類中返回屬性值。
語法
以下是 property() 方法的語法:
class property([fget[, fset[, fdel[, doc]]]])
參數
fget – 獲取屬性值的函數
fset – 設置屬性值的函數
fdel – 刪除屬性值函數
doc – 屬性描述信息
返回值
返回新式類屬性。

定義一個可控屬性值 x
class C(object):
    def __init__(self):
        self._x = None
 
    def getx(self):
        return self._x
 
    def setx(self, value):
        self._x = value
 
    def delx(self):
        del self._x
 
    x = property(getx, setx, delx, "I'm the 'x' property.")
a = C()
a.x = 1
print(a.x)
結果:
1

如果 c 是 C 的實例化, c.x 將觸發 getter,c.x = value 將觸發 setter , del c.x 觸發 deleter。
另一個例子:

class Rectangle:
    # 定義構造方法
    def __init__(self, width, height):
        self.width = width
        self.height = height
    # 定義setsize()函數
    def setsize (self , size):
        print(size)
        self.width, self.height = size
    # 定義getsize()函數
    def getsize (self):
        return self.width, self.height
     # 定義getsize()函數
    def delsize (self):
        self.width, self.height = 0, 0 
    # 使用property定義屬性
    size = property(getsize, setsize, delsize, '用於描述矩形大小的屬性')
rect = Rectangle(4, 3)
# 訪問rect的size屬性
print(rect.size) # (4, 3)
# 對rect的size屬性賦值
rect.size = 9, 7
# 訪問rect的width、height實例變量
print(rect.width) # 9
print(rect.height) # 7
# 刪除rect的size屬性
del rect.size
# 訪問rect的width、height實例變量
print(rect.width) # 0
print(rect.height) # 0
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章