一 Python簡介 Python是一個可移植的物件導向的指令碼語言。 Python雖然是一個指令碼語言,但也是一個完全物件導向的語言。由於它設計之初把易用性做為非常重要的一個考量標準,所以用起來非常簡潔,優美(文法非常靈活),所以使用Python可以快速地編寫出可運行代碼。與C/C++相比,Python程式的運行速度比較慢,一門語言既然能夠生存下來,就有它自己的原因,Python語言也一樣。當今的電腦處理速度已經很快,對某些任務來說,運行速度並不是首要考慮的因素。比如說為了實現資料庫訪問的自動化,需要做一個資料庫訪問的代碼產生器,這是一個常見的任務,其中涉及編程的許多方面,包括字串處理、資料庫訪問,可能還包括一個GUI系統。這個任務顯然不太適合用C或者C++來編寫,因為使用C/C++來開發雖然可以節省那麼一點已耗用時間,但卻浪費了大量的開發時間。所以,語言沒有好不好之分,只有適合不適合之分。 C++是靜態強型別語言,而Python是動態強型別語言。由於是動態語言,所以變數的類型不是用關鍵字顯式指定,而是在運行時根據賦給它的值動態判斷出來的。 另外,Python也跟C++一樣同時支援結構化編程和物件導向編程兩種範式。 Python的特定總結如下: 1. Python是物件導向的。它支援繼承以及多重繼承、多態、操作符重載等物件導向的概念。 2. Python是可移植的。使用Python語言編寫的程式通常無需修改就可以運行在多個平台上,這點與Java類似。但是Python也提供一些Plantform dependent的模組,使用這些模組的時候必須小心,因為他們是不可移植的。 3. Python是解釋性語言。準確地說,Python先把來源程式編譯成中間代碼(類似於java和c#),然後解釋執行。但要注意的是,編譯過程對程式員來說是透明的(這又不同於java和c#)。 4.Python是一門動態語言。它支援中繼資料編程;支援運行時編譯、執行等現代語言的進階特徵。這些都很難甚至無法在C/C++中實現。Python甚至允許你在啟動並執行時候增加一個對象的成員! 5. 一切都是對象!Python中對象的概念比其他語言豐富得多,比如類是一個對象,因此你可以在啟動並執行時候使用它,而不像C++一樣,類是編譯時間的對象。正因為這種原因,使得Python是高度動態。 6. 自動記憶體管理。像java一樣,你無需管理你的代碼所使用的記憶體。在C和C++的程式中,那是一場惡夢。 7. 其他:Python可以使用c/c++的編寫的組件,也可以把python代碼嵌入到c/c++代碼中執行。二 類定義
首先看下面的例子
class ZZClass:
classNum = 0
def __init__(self):
self.num = 1
ZZClass.classNum += 1
print ("ZZClass _init__ called.")
def __del__(self):
ZZClass.classNum -= 1;
print ("ZZClass __del__ called.")
def Hello(self):
print("hello world!")
self.PrintClassNum(10) #普通函數中可以調用靜態函數
def setNum(self,num):
self.num = num
def getNum(self):
return self.num
@staticmethod
def PrintClassNum(num=100):
print (ZZClass.classNum) #在靜態方法中只能通過類名訪問類變數
#print classNum #在靜態方法中不能直接存取類變數
#print self.num #在靜態方法中不能訪問執行個體變數
print num
@classmethod
def ClassMethod(cls):
#print cls.num #在類方法中不能直接存取執行個體變數
print "class method."
print cls.classNum #在類方法中可以直接存取類變數
myObj = ZZClass()
myObj.Hello()
ZZClass.PrintClassNum(10) #可以通過類名來訪問靜態方法
myObj02 = ZZClass()
myObj02.PrintClassNum() #可以通過對象執行個體來訪問靜態方法
print myObj.classNum #可以通過對象執行個體來訪問類變數
print ZZClass.classNum #可以通過類名來訪問靜態變數
myObj.setNum(10)
myObj02.setNum(20)
print myObj.getNum()
print myObj02.getNum()
myObj02 = 0
print ZZClass.PrintClassNum()
print ZZClass.ClassMethod() #通過類調用類方法
print myObj.ClassMethod() #通過執行個體調用類方法
輸出結果:
ZZClass _init__ called.hello world!110110ZZClass _init__ called.2100221020ZZClass __del__ called.1100Noneclass method.1NoneZZClass __del__ called.
分析:1.與C++相比,Python中定義方法採用def關鍵字,在類中定義的方法至少會有一個參數,一般以名為'self'的變數作為該參數(用其他名稱也可以),而且需要作為第一個參數,用於對對象的變數引用。類似c++的this指標,只不過在C++中this 指標是隱藏的,由編譯器來處理。2.函數調用和變數的訪問使用"."而不是"->"訪問。3._init__類似於建構函式,在產生對象時調用,可以用來進行一些初始化操作,不需要顯示去調用,系統會預設去執行。構造方法支援重載,如果使用者自己沒有重新定義構造方法,系統就自動執行預設的構造方法。 __del__類似於解構函式。在釋放對象時調用,支援重載,可以在裡面進行一些釋放資源的操作,不需要顯示調用。4.執行個體變數和類變數:num是執行個體變數,classNum是類變數。前者類似C++的成員變數,後者類似C++的類的靜態變數。類變數是所有對象共用的,不需要對象,可以直接用“類名.變數名”訪問,與C++一樣,使用對象執行個體也可以訪問類變數。執行個體變數是每個對象一個拷貝,只能通過對象訪問,執行個體變數是不需要在類中顯示定義的。 注意:類變數可以通過類名和執行個體對象兩種方式進行訪問,通過類名修改類變數,該類和所有執行個體所共用的資料將被修改,再次通過類或執行個體訪問得到的將是新的資料,這一點與C++是一致的。但是通過執行個體對象修改類變數,其效果將僅僅作用在該執行個體上,再次通過類或其它執行個體訪問得到的仍然是舊的資料。但這一修改方式將對該類變數執行個體化,其結果是該執行個體將得到一個單獨的該變數拷貝,此後此對象不再與類共用該名稱的變數。(這一點Python有點複雜),看下面的例子:
class classA:
classVar = ''
def __init__(self):
pass
def set_var1(self,x):
classA.classVar= x
def set_var2(self,y):
self.var2 = y
return self.var2
oa = classA()
ob = classA()
oa.set_var1("class variable.")
print oa.classVar #class variable.
print ob.classVar #class variable.
oa.classVar = "changed."
print oa.classVar #changed.
print ob.classVar #class variable.
oa.set_var1("class variable01")
print oa.classVar #changed.
print ob.classVar #class variable01
ob.set_var1("class variable02")
print oa.classVar #changed.
print ob.classVar #class variable02
ob.set_var2("inst variable")
print ob.var2
#print oa.var2 #error! because var2 is a instance variable
如果需要在類外修改類屬性,必須通過類對象去引用然後進行修改。如果通過執行個體對象去引用,會產生一個同名的執行個體屬性,這種方式修改的是執行個體屬性,不會影響到類屬性,並且之後如果通過執行個體對象去引用該名稱的屬性,執行個體屬性會強制屏蔽掉類屬性,即引用的是執行個體屬性,除非刪除了該執行個體屬性。
5.普通函數和靜態函數:Hello是普通函數,PrintClassNum是靜態函數。printClassCount()類似C++的靜態函數。就像靜態函數沒有this指標一樣,它也沒有self變數。靜態函數只能訪問靜態成員變數(類變數)。與C++一樣,可以使用執行個體對象和類名來調用靜態函數,只能使用執行個體變數來調用普通函數。在類外普通函數(執行個體函數)只能通過執行個體對象去調用,不能通過其他方式去調用。
6.類函數和靜態函數: ClassMethod是一個類方法,擁有一個參數(類對象)。與靜態方法使用類似,他們都可以通過類和執行個體進行調用,都無法訪問執行個體成員。類方法可以通過自己的參數cls訪問類成員,而靜態方法則不行,可以通過類名間接訪問。staticmethod無需參數,classmethod需要類變數作為參數傳遞(不是類的執行個體),類函數還有一個用途就是可以對類屬性進行修改。
7.變數訪問屬性:C++的物件導向的文法都比較的規範,有公有、私人和保護的資料類型,而python類是沒有許可權控制的,所有變數都是可以被外部調用,雖然我們可以在變數或者方法的面前加上雙下滑線__,表示私人存取權限,其實在內部實現上,是將私人變數進行了轉化,規則是:_<類名>__<私人變數>。但這個實際上是python的偽私人,只是一種程式員約定俗稱的規定,加了雙底線就表示私人變數,但是如果要在外部調用的話,還是可以調用的。看下面的例子:
class Test:
count = 0
def __init__(self,c):
self.count = c
self.__name="zhangzhe" #私人變數
self.__class__.count = self.__class__.count +1
def __get_name(self): #私人方法
return self.__name
def get_counte(self):
return self.count
a = Test(3)
print a.count #3
#print a.name #AttributeError:Test instance has no attribute 'name'
print Test.count #1
print a.get_counte() #3
print a.__dict__ #{'count': 3, '_Test__name': 'zhangzhe'}
print a._Test__name #zhangzhe
print Test.__dict__ #{'count': 1, '__module__': 'classdemo02', '_Test__get_name': <function __get_name at 0x101e92500>, '__doc__': None, '__init__': <function __init__ at 0x101e92488>, 'get_counte': <function get_counte at 0x101e92578>}
print a._Test__get_name() #zhangzhe
b = Test(-1)
print b.count #1
print Test.count #2
這裡定義的__name是私人屬性,__get_name()是私人方法,直接存取的話,會提示找不到相關的屬性或者方法。可以通過使用 a._Test__name和a._Test__get_name()來訪問私人的變數和方法。
註:在Python中沒有像C++中public和private這些關鍵字來區別公有屬性和私人屬性,它是以屬性命名方式來區分,如果在屬性名稱前面加了2個底線'__',則表明該屬性是私人屬性,否則為公有屬性
小結: 對於類屬性和執行個體屬性,如果在類方法中引用某個屬性,該屬性必定是類屬性,而如果在執行個體方法中引用某個屬性(不作更改),並且存在同名的類屬性,此時若執行個體對象有該名稱的執行個體屬性,則執行個體屬性會屏蔽類屬性,即引用的是執行個體屬性,若執行個體對象沒有該名稱的執行個體屬性,則引用的是類屬性;如果在執行個體方法更改某個屬性,並且存在同名的類屬性,此時若執行個體對象有該名稱的執行個體屬性,則修改的是執行個體屬性,若執行個體對象沒有該名稱的執行個體屬性,則會建立一個同名稱的執行個體屬性。想要修改類屬性,如果在類外,可以通過類對象修改,如果在類裡面,只有在類方法中進行修改。
從類方法和執行個體方法以及靜態方法的定義形式就可以看出來,類方法的第一個參數是類對象cls,那麼通過cls引用的必定是類對象的屬性和方法;而執行個體方法的第一個參數是執行個體對象self,那麼通過self引用的可能是類屬性、也有可能是執行個體屬性(這個需要具體分析),不過在存在相同名稱的類屬性和執行個體屬性的情況下,執行個體屬性優先順序更高。靜態方法中不需要額外定義參數,因此在靜態方法中引用類屬性的話,必須通過類對象來引用。
從上面看來,Python是非常的靈活 的,它的物件導向沒有做到真正的不能訪問,只是一種約定讓大家去遵守,就像大家都用self來代表在類裡的當前對象,你也可以用其他的,只是大家習慣了用self。
三 類中的內建方法
除了上面的init和del方法外,Python類中還有如下的多個內建方法(類似於C++中的運算子多載):
1.__new__():__new__()在__init__()之前被調用,用於產生執行個體對象。利用這個方法和類屬性的特性可以實現設計模式中的單例模式。單例模式是指建立唯一對象嗎,單例模式設計的類只能執行個體化一個對象。例如下面的代碼: __instance = None def __new__(cls, *args, **kwargs): #在init函數前被調用 if ZZClass.__instance is None: #生產唯一執行個體
print("ZZClass __new__ called.")
ZZClass.__instance = object.__new__(cls,*args,**kwargs)
return ZZClass.__instance
2.__getattr__()、__setattr__()和__getattribute__():當讀取對象的某個屬性時,python會自動調用__getattr__()方法。例如,fruit.color將轉換為fruit.__getattr__(color)。當使用賦值語句對屬性進行設定時,python會自動調用__setattr__()方法。__getattribute__()的功能與__getattr__()類似,用於擷取屬性的值。但是__getattribute__()能提供更好的控制,代碼更健壯。注意,python中並不存在__setattribute__()方法。
3. __str__()用於表示對象代表的含義,返回一個字串.實現了__str__()方法後,可以直接使用print語句輸出對象,也可以通過函數str()觸發__str__()的執行。這樣就把對象和字串關聯起來,便於某些程式的實現,可以用這個字串來表示某個類。
例如:def __str__(self): return "ZZClass itself."
4. __call__():在類中實現__call__()方法,可以在對象建立時直接返回__call__()的內容。使用該方法可以類比靜態方法。例如: class InternalClass:
def __call__(self, *args, **kwargs):
print "internal class."
func = InternalClass() # 調用InternalClass(),此時將類InternalClass作為函數返回,
# 即為外部類ZZClass定義方法func(),func()將執行__call__()內的代碼。
myObj.func() #internal class. myObj.func() #internal class.
5.__getitem__():如果類把某個屬性定義為序列,可以使用__getitem__()輸出序列屬性中的某個元素。
class Persons:
def __getitem__(self, item):
return self.persons[item]
allPersons = Persons()
allPersons.persons = ["Alice","Joe"]
print allPersons[1]
補充:強型別語言
一種總是強制類型定義的語言,Java和Python是強制類型定義的,如果你有一個整數,不顯示地進行轉換,不能將其視為一個字串。
弱類型定義語言
一種類型可以被忽略的語言,與強型別定義相反。VBScript是弱類型定義 的。在VBScript中,可以將字串 '12' 和整數 3 進行串連得到字串 '123', 然後可以把它看成整數 123,而不需要顯示轉換。
注意:強弱類型中心詞是‘類型’,而不是變數,一個變數是否能夠綁定到多種類型,跟該語言是否強弱類型無關。
動態聯編 編譯器在編譯階段並不能確切知道將要調用的函數,只有在程式運行時才能確定將要調用的函數,為此要確切知道該調用的函數,要求聯編工作要在程式運行時進行,這種在程式運行時進行聯編工作被稱為動態聯編。 其實它不是 Python 的特性,所有物件導向的語言基本都需要實現。它使得執行一個對象的方法時,使用的是它自己(或它的類)的方法,而不是它的父類的方法。 動態聯編通常的實現方法是把函數的入口地址儲存起放在一個表裡,(例如,c++的虛表),在運行時通過動態尋找表的方式來判斷應該調用哪個函數。函數的執行效率沒有靜態聯編的高,但比其靈活。
靜態聯編 靜態聯編是指聯編工作出現在編譯串連階段,這種聯編又稱早期聯編,它在編譯時間就解決了程式中的操作調用與執行該作業碼間的關係。