抽象是隱藏多余細節的藝術。在面向對象的概念中,抽象的直接表現形式通常為類。雖然Python是解釋性語言,但是它是面向對象的,從設計之初就已經是一門面向對象的語言。Python基本上提供了面向對象編程語言的所有元素,如果你已經至少掌握了一門面向對象語言,那么利用Python進行面向對象程序設計將會相當容易。下面就來了解一下如何在Python中進行對象編程。
一. 如何定義一個類
在進行python面向對象編程之前,先來了解幾個術語:類,類對象,實例對象,屬性,函數和方法。
類是對現實世界中一些事物的封裝,定義一個類可以采用下面的方式來定義:
- class className:
- block
- class people:
- name = 'jack' #定義了一個屬性
- #定義了一個方法
- def printName(self):
- print self.name
people類定義完成之后就產生了一個全局的類對象,可以通過類對象來訪問類中的屬性和方法了。當通過people.name(至於為什么可以直接這樣訪問屬性后面再解釋,這里只要理解類對象這個概念就行了)來訪問時,people.name中的people稱為類對象,這點和C++中的有所不同。當然還可以進行實例化操作,p=people( ),這樣就產生了一個people的實例對象,此時也可以通過實例對象p來訪問屬性或者方法了(p.name).
理解了類、類對象和實例對象的區別之后,我們來了解一下Python中屬性、方法和函數的區別。
在上面代碼中注釋的很清楚了,name是一個屬性,printName( )是一個方法,與某個對象進行綁定的函數稱作為方法。一般在類里面定義的函數與類對象或者實例對象綁定了,所以稱作為方法;而在類外定義的函數一般沒有同對象進行綁定,就稱為函數。
二. 屬性
在類中我們可以定義一些屬性,比如:
- class people:
- name = 'jack'
- age = 12
- p = people()
- print p.name,p.age
- class people:
- __name = 'jack'
- __age = 12
- p = people()
- print p.__name,p.__age
- Traceback (most recent call last):
- File "C:/PycharmProjects/FirstProject/oop.py", line 6, in <module>
- print p.__name,p.__age
- AttributeError: people instance has no attribute '__name
提示找不到該屬性,因為私有屬性是不能夠在類外通過對象名來進行訪問的。在Python中沒有像C++中public和private這些關鍵字來區別公有屬性和私有屬性,它是以屬性命名方式來區分,如果在屬性名前面加了2個下划線'__',則表明該屬性是私有屬性,否則為公有屬性(方法也是一樣,方法名前面加了2個下划線的話表示該方法是私有的,否則為公有的)。
三. 方法
在類中可以根據需要定義一些方法,定義方法采用def關鍵字,在類中定義的方法至少會有一個參數,,一般以名為'self'的變量作為該參數(用其他名稱也可以),而且需要作為第一個參數。下面看個例子:
- class people:
- __name = 'jack'
- __age = 12
- def getName(self):
- return self.__name
- def getAge(self):
- return self.__age
- p = people()
- print p.getName(),p.getAge()
如果對self不好理解的話,可以把它當做C++中類里面的this指針一樣理解,就是對象自身的意思,在用某個對象調用該方法時,就將該對象作為第一個參數傳遞給self。
四. 類中內置的方法
在Python中有一些內置的方法,這些方法命名都有比較特殊的地方(其方法名以2個下划線開始然后以2個下划線結束)。類中最常用的就是構造方法和析構方法。
構造方法__init__(self,....):在生成對象時調用,可以用來進行一些初始化操作,不需要顯示去調用,系統會默認去執行。構造方法支持重載,如果用戶自己沒有重新定義構造方法,系統就自動執行默認的構造方法。
析構方法__del__(self):在釋放對象時調用,支持重載,可以在里面進行一些釋放資源的操作,不需要顯示調用。
還有其他的一些內置方法,比如 __cmp__( ), __len( )__等。下面是常用的內置方法:
內置方法 | 說明 |
__init__(self,...) | 初始化對象,在創建新對象時調用 |
__del__(self) | 釋放對象,在對象被刪除之前調用 |
__new__(cls,*args,**kwd) | 實例的生成操作 |
__str__(self) | 在使用print語句時被調用 |
__getitem__(self,key) | 獲取序列的索引key對應的值,等價於seq[key] |
__len__(self) | 在調用內聯函數len()時被調用 |
__cmp__(stc,dst) | 比較兩個對象src和dst |
__getattr__(s,name) | 獲取屬性的值 |
__setattr__(s,name,value) | 設置屬性的值 |
__delattr__(s,name) | 刪除name屬性 |
__getattribute__() | __getattribute__()功能與__getattr__()類似 |
__gt__(self,other) | 判斷self對象是否大於other對象 |
__lt__(slef,other) | 判斷self對象是否小於other對象 |
__ge__(slef,other) | 判斷self對象是否大於或者等於other對象 |
__le__(slef,other) | 判斷self對象是否小於或者等於other對象 |
__eq__(slef,other) | 判斷self對象是否等於other對象 |
__call__(self,*args) | 把實例對象作為函數調用 |
- # Filename: class_init.py
- class Person:
- def __init__(self, name):
- self.name = name
- def sayHi(self):
- print 'Hello, my name is', self.name
- p = Person('Swaroop')
- p.sayHi()
- 輸出:
- Hello, my name is Swaroop
- # -*- coding: UTF-8 -*-
- class Singleton(object):
- __instance = None # 定義實例
- def __init__(self):
- pass
- def __new__(cls, *args, **kwd): # 在__init__之前調用
- if Singleton.__instance is None: # 生成唯一實例
- Singleton.__instance = object.__new__(cls, *args, **kwd)
- return Singleton.__instance
- # -*- coding: UTF-8 -*-
- class Fruit(object):
- def __init__(self, color="red", price=0):
- self.__color = color
- self.__price = price
- def __getattribute__(self, item): # <span style="font-family:宋體;font-size:12px;">獲取屬性的方法</span>
- return object.__getattribute__(self, item)
- def __setattr__(self, key, value):
- self.__dict__[key] = value
- if __name__ == "__main__":
- fruit = Fruit("blue", 10)
- print fruit.__dict__.get("_Fruit__color") # <span style="font-family:宋體;font-size:12px;">獲取color屬性</span>
- fruit.__dict__["_Fruit__price"] = 5
- print fruit.__dict__.get("_Fruit__price") # <span style="font-family:宋體;font-size:12px;">獲取price屬性</span>
Python不允許實例化的類訪問私有數據,但你可以使用object._className__attrName訪問這些私有屬性。
__getitem__():如果類把某個屬性定義為序列,可以使用__getitem__()輸出序列屬性中的某個元素.假設水果店中銷售多鍾水果,可以通過__getitem__()方法獲取水果店中的沒種水果。代碼例子:
- # -*- coding: UTF-8 -*-
- class FruitShop:
- def __getitem__(self, i): # 獲取水果店的水果
- return self.fruits[i]
- if __name__ == "__main__":
- shop = FruitShop()
- shop.fruits = ["apple", "banana"]
- print shop[1]
- for item in shop: # 輸出水果店的水果
- print item,
- banana
- apple banana
- # -*- coding: UTF-8 -*-
- class Fruit:
- '''''Fruit類''' #為Fruit類定義了文檔字符串
- def __str__(self): # 定義對象的字符串表示
- return self.__doc__
- if __name__ == "__main__":
- fruit = Fruit()
- print str(fruit) # 調用內置函數str()觸發__str__()方法,輸出結果為:Fruit類
- print fruit #直接輸出對象fruit,返回__str__()方法的值,輸出結果為:Fruit類
- # -*- coding: UTF-8 -*-
- class Fruit:
- class Growth: # 內部類
- def __call__(self):
- print "grow ..."
- grow = Growth() # 調用Growth(),此時將類Growth作為函數返回,即為外部類Fruit定義方法grow(),grow()將執行__call__()內的代碼
- if __name__ == '__main__':
- fruit = Fruit()
- fruit.grow() # 輸出結果:grow ...
- Fruit.grow() # 輸出結果:grow ...
五. 類屬性、實例屬性、類方法、實例方法以及靜態方法
在了解了類基本的東西之后,下面看一下python中這幾個概念的區別。
先來談一下類屬性和實例屬性
在前面的例子中我們接觸到的就是類屬性,顧名思義,類屬性就是類對象所擁有的屬性,它被所有類對象的實例對象所共有,在內存中只存在一個副本,這個和C++中類的靜態成員變量有點類似。對於公有的類屬性,在類外可以通過類對象和實例對象訪問。
- class people:
- name = 'jack' #公有的類屬性
- __age = 12 #私有的類屬性
- p = people()
- print p.name #正確
- print people.name #正確
- print p.__age #錯誤,不能在類外通過實例對象訪問私有的類屬性
- print people.__age #錯誤,不能在類外通過類對象訪問私有的類屬性
- class people:
- name = 'jack'
- p = people()
- p.age =12
- print p.name #正確
- print p.age #正確
- print people.name #正確
- print people.age #錯誤
- class people:
- name = 'jack'
- #__init__()是內置的構造方法,在實例化對象時自動調用
- def __init__(self,age):
- self.age = age
- p = people(12)
- print p.name #正確
- print p.age #正確
- print people.name #正確
- print people.age #錯誤
- class people:
- country = 'china'
- print people.country
- p = people()
- print p.country
- p.country = 'japan'
- print p.country #實例屬性會屏蔽掉同名的類屬性
- print people.country
- del p.country #刪除實例屬性
- print p.country
下面來看一下類方法、實例方法和靜態方法的區別。
類方法:是類對象所擁有的方法,需要用修飾器"@classmethod"來標識其為類方法,對於類方法,第一個參數必須是類對象,一般以"cls"作為第一個參數(當然可以用其他名稱的變量作為其第一個參數,但是大部分人都習慣以'cls'作為第一個參數的名字,就最好用'cls'了),能夠通過實例對象和類對象去訪問。
- class people:
- country = 'china'
- #類方法,用classmethod來進行修飾
- @classmethod
- def getCountry(cls):
- return cls.country
- p = people()
- print p.getCountry() #可以用過實例對象引用
- print people.getCountry() #可以通過類對象引用
類方法還有一個用途就是可以對類屬性進行修改:
- class people:
- country = 'china'
- #類方法,用classmethod來進行修飾
- @classmethod
- def getCountry(cls):
- return cls.country
- @classmethod
- def setCountry(cls,country):
- cls.country = country
- p = people()
- print p.getCountry() #可以用過實例對象引用
- print people.getCountry() #可以通過類對象引用
- p.setCountry('japan')
- print p.getCountry()
- print people.getCountry()
運行結果:
- china
- china
- japan
- japan
結果顯示在用類方法對類屬性修改之后,通過類對象和實例對象訪問都發生了改變。
實例方法:在類中最常定義的成員方法,它至少有一個參數並且必須以實例對象作為其第一個參數,一般以名為'self'的變量作為第一個參數(當然可以以其他名稱的變量作為第一個參數)。在類外實例方法只能通過實例對象去調用,不能通過其他方式去調用。
- class people:
- country = 'china'
- #實例方法
- def getCountry(self):
- return self.country
- p = people()
- print p.getCountry() #正確,可以用過實例對象引用
- print people.getCountry() #錯誤,不能通過類對象引用實例方法
- class people:
- country = 'china'
- @staticmethod
- #靜態方法
- def getCountry():
- return people.country
- print people.getCountry()
對於類屬性和實例屬性,如果在類方法中引用某個屬性,該屬性必定是類屬性,而如果在實例方法中引用某個屬性(不作更改),並且存在同名的類屬性,此時若實例對象有該名稱的實例屬性,則實例屬性會屏蔽類屬性,即引用的是實例屬性,若實例對象沒有該名稱的實例屬性,則引用的是類屬性;如果在實例方法更改某個屬性,並且存在同名的類屬性,此時若實例對象有該名稱的實例屬性,則修改的是實例屬性,若實例對象沒有該名稱的實例屬性,則會創建一個同名稱的實例屬性。想要修改類屬性,如果在類外,可以通過類對象修改,如果在類里面,只有在類方法中進行修改。
從類方法和實例方法以及靜態方法的定義形式就可以看出來,類方法的第一個參數是類對象cls,那么通過cls引用的必定是類對象的屬性和方法;而實例方法的第一個參數是實例對象self,那么通過self引用的可能是類屬性、也有可能是實例屬性(這個需要具體分析),不過在存在相同名稱的類屬性和實例屬性的情況下,實例屬性優先級更高。靜態方法中不需要額外定義參數,因此在靜態方法中引用類屬性的話,必須通過類對象來引用。
六. 繼承和多重繼承
上面談到了類的基本定義和使用方法,這只體現了面向對象編程的三大特點之一:封裝。下面就來了解一下另外兩大特征:繼承和多態。
在Python中,如果需要的話,可以讓一個類去繼承一個類,被繼承的類稱為父類或者超類、也可以稱作基類,繼承的類稱為子類。並且Python支持多繼承,能夠讓一個子類有多個父類。
Python中類的繼承定義基本形式如下:
- #父類
- class superClassName:
- block
- #子類
- class subClassName(superClassName):
- block
- # -*- coding: UTF-8 -*-
- class UniversityMember:
- def __init__(self,name,age):
- self.name = name
- self.age = age
- def getName(self):
- return self.name
- def getAge(self):
- return self.age
- class Student(UniversityMember):
- def __init__(self,name,age,sno,mark):
- UniversityMember.__init__(self,name,age) #注意要顯示調用父類構造方法,並傳遞參數self
- self.sno = sno
- self.mark = mark
- def getSno(self):
- return self.sno
- def getMark(self):
- return self.mark
- class Teacher(UniversityMember):
- def __init__(self,name,age,tno,salary):
- UniversityMember.__init__(self,name,age)
- self.tno = tno
- self.salary = salary
- def getTno(self):
- return self.tno
- def getSalary(self):
- return self.salary
在大學中的每個成員都有姓名和年齡,而學生有學號和分數這2個屬性,老師有教工號和工資這2個屬性,從上面的代碼中可以看到:
1)在Python中,如果父類和子類都重新定義了構造方法__init( )__,在進行子類實例化的時候,子類的構造方法不會自動調用父類的構造方法,必須在子類中顯示調用。
2)如果需要在子類中調用父類的方法,需要以”父類名.方法“這種方式調用,以這種方式調用的時候,注意要傳遞self參數過去。
對於繼承關系,子類繼承了父類所有的公有屬性和方法,可以在子類中通過父類名來調用,而對於私有的屬性和方法,子類是不進行繼承的,因此在子類中是無法通過父類名來訪問的。
Python支持多重繼承。對於多重繼承,比如
class SubClass(SuperClass1,SuperClass2)
此時有一個問題就是如果SubClass沒有重新定義構造方法,它會自動調用哪個父類的構造方法?這里記住一點:以第一個父類為中心。如果SubClass重新定義了構造方法,需要顯示去調用父類的構造方法,此時調用哪個父類的構造方法由你自己決定;若SubClass沒有重新定義構造方法,則只會執行第一個父類的構造方法。並且若SuperClass1和SuperClass2中有同名的方法,通過子類的實例化對象去調用該方法時調用的是第一個父類中的方法。
七. 多態
多態即多種形態,在運行時確定其狀態,在編譯階段無法確定其類型,這就是多態。Python中的多態和Java以及C++中的多態有點不同,Python中的變量是弱類型的,在定義時不用指明其類型,它會根據需要在運行時確定變量的類型(個人覺得這也是多態的一種體現),並且Python本身是一種解釋性語言,不進行預編譯,因此它就只在運行時確定其狀態,故也有人說Python是一種多態語言。在Python中很多地方都可以體現多態的特性,比如內置函數len(object),len函數不僅可以計算字符串的長度,還可以計算列表、元組等對象中的數據個數,這里在運行時通過參數類型確定其具體的計算過程,正是多態的一種體現。這有點類似於函數重載(一個編譯單元中有多個同名函數,但參數不同),相當於為每種類型都定義了一個len函數。這是典型的多態表現。有些朋友提出Python不支持多態,我是完全不贊同的。
本質上,多態意味着可以對不同的對象使用同樣的操作,但它們可能會以多種形態呈現出結果。len(object)函數就體現了這一點。在C++、Java、C#這種編譯型語言中,由於有編譯過程,因此就鮮明地分成了運行時多態和編譯時多態。運行時多態是指允許父類指針或名稱來引用子類對象,或對象方法,而實際調用的方法為對象的類類型方法,這就是所謂的動態綁定。編譯時多態有模板或范型、方法重載(overload)、方法重寫(override)等。而Python是動態語言,動態地確定類型信息恰恰體現了多態的特征。在Python中,任何不知道對象到底是什么類型,但又需要對象做點什么的時候,都會用到多態。
能夠直接說明多態的兩段示例代碼如下:1、方法多態
- # -*- coding: UTF-8 -*-
- _metaclass_=type # 確定使用新式類
- class calculator:
- def count(self,args):
- return 1
- calc=calculator() #自定義類型
- from random import choice
- obj=choice(['hello,world',[1,2,3],calc]) #obj是隨機返回的 類型不確定
- print type(obj)
- print obj.count('a') #方法多態
對於一個臨時對象obj,它通過Python的隨機函數取出來,不知道具體類型(是字符串、元組還是自定義類型),都可以調用count方法進行計算,至於count由誰(哪種類型)去做怎么去實現我們並不關心。
有一種稱為”鴨子類型(duck typing)“的東西,講的也是多態:
- _metaclass_=type # 確定使用新式類
- class Duck:
- def quack(self):
- print "Quaaaaaack!"
- def feathers(self):
- print "The duck has white and gray feathers."
- class Person:
- def quack(self):
- print "The person imitates a duck."
- def feathers(self):
- print "The person takes a feather from the ground and shows it."
- def in_the_forest(duck):
- duck.quack()
- duck.feathers()
- def game():
- donald = Duck()
- john = Person()
- in_the_forest(donald)
- in_the_forest(john)
- game()
2、運算符多態
- def add(x,y):
- return x+y
- print add(1,2) #輸出3
- print add("hello,","world") #輸出hello,world
- print add(1,"abc") #拋出異常 TypeError: unsupported operand type(s) for +: 'int' and 'str'
Python同樣支持運算符重載,實例如下:
- class Vector:
- def __init__(self, a, b):
- self.a = a
- self.b = b
- def __str__(self):
- return 'Vector (%d, %d)' % (self.a, self.b)
- def __add__(self,other):
- return Vector(self.a + other.a, self.b + other.b)
- v1 = Vector(2,10)
- v2 = Vector(5,-2)
- print v1 + v2
參考文獻:
http://www.cnblogs.com/dolphin0520/archive/2013/03/29/2986924.html
http://www.cnblogs.com/jeffwongishandsome/archive/2012/10/06/2713258.html