1. 程式人生 > >初學python,感受和C的不同

初學python,感受和C的不同

從開始看Python到現在也有半個多月了,前後看了Python核心程式設計和Dive into Python兩本書。話說半個月看兩本,是個人都知道有多囫圇吞棗,這也是因為我暫時沒有需求拿這個做大型開發,主要是平時的小程式test用一用。所以我的策略是,整體瀏覽,用到時候現查。話說這核心程式設計第一版太古老了,老在講2.2之前的東西,我看的翻譯電子版,翻譯得也不好,很晦澀。看完這個後還有點雲裡霧裡,看網上人家說DIP好,啄木鳥還有免費電子文件,就找來看這個。怎麼說呢,講的比核心程式設計好,但不適合第一次看的初學者。我之所以覺得講得好,是因為看核心程式設計,有些概念還有些模糊,看了這本書就明白不少了。要是初學者上來就看這本,保證不好理解。

下面就是在學習的過程中,在翻閱資料的過程中,總結的一些C和python比較明顯的不同之處,有大方向的,也有細節的。肯定沒有總結完,比如動態函式,lambda這些,我都懶得往上寫了。實際上,作為兩種完全不同的語言,下面這些差異只是冰山一角而已。權當拋磚引玉吧,至少應該對和我有相同研究興趣,正在考慮是否學習另一門語言的朋友有點幫助。此文也算是DIP的學習筆記吧。順帶說一句,要是有朋友瞭解,可以幫忙推薦一下實戰性強的Python教材,語言這東西,不多練手,光比劃,是不可能學好的。

學習目的

我的以後的研究方向是嵌入式,顯然,C語言是我的主要語言。我不是一個語言愛好者,我以前覺得,對於做研究而不是應用的人來說,瞭解多門語言,不如精通一門語言。之所以去看python,主要還是因為python更有利於快速開發一些程式,也是因為現在認識到,研究和應用是不能分離的。個人以為,要想在計算機工程的競爭中立足,必須懂C語言。因為真正要做高效能程式設計, 不可能將機器的體系架構拋到腦後讓Python虛擬機器(或Java虛擬機器等)幫你搞定所有底層。越來越多的CPU core,越來越恐怖的記憶體效能瓶頸,對於上層開發人員來說,無所謂,但是對高效能程式開發人員來說,這些是無法透明的。很多應用,還是自己掌控比較有效。這些場合中,彙編和C還是不可替代的。但是,光知道C是不夠的,掌握一門面向物件語言,相對更高層的語言,不僅對以後的個人發展有利,也會對自己的技術認識產生幫助。

如果要問對我來說誰更重要,我覺得還是C更重要。C的學習曲線更陡,貌似簡單,實際上到處都是陷阱,看上去比較簡單低效的程式,也不是學1,2個月就能搞定的。談到優化的深層次和難度嘛,需要的功底是按年算的。但是一旦你C語言的基礎打好了,對計算機的理解,對其他語言的理解都是大有裨益的。比如,如果你有C基礎,可以說,學過1天python,就能寫的出來一些不短的程式。後面的優化也不是什麼大不了的演算法,都是非常基本的語句換來換去。當然這裡不是說 Python不好,實際上,上層應用,Python比C方便的不是一個層次。

很多人覺得,既然懂C了,那麼進一步掌握C++應該是水到渠成,但C++不是C的超集,而我又不喜歡C++的繁瑣和巨大,所以才決定看一看Python。我很喜歡Python的優雅與快捷。

語言型別

和C不一樣,Python是一種動態型別語言,又是強型別語言。這個分類怎麼理解呢?大概是可以按照下列說明來分類的:

靜態型別語言

一種在編譯期間就確定資料型別的語言。大多數靜態型別語言是通過要求在使用任一變數之前宣告其資料型別來保證這一點的。Java和 C 是靜態型別語言。

動態型別語言

一種在執行期間才去確定資料型別的語言,與靜態型別相反。Python 是動態型別的,因為它們確定一個變數的型別是在您第一次給它賦值的時候。

強型別語言

一種總是強制型別定義的語言。Java 和 Python 是強制型別定義的。您有一個整數,如果不明確地進行轉換 ,不能將把它當成一個字串。

弱型別語言

一種型別可以被忽略的語言,與強型別相反。VBScript 是弱型別的。在 VBScript 中,您可以將字串 ’12′ 和整數 3 進行連線得到字串’123′,然後可以把它看成整數 123 ,所有這些都不需要任何的顯示轉換。

物件機制

具體怎麼來理解這個“動態確定變數型別”,就要從Python的Object物件機制說起了。Objects(以下稱物件)是Python對於資料的抽象,Python中所有的資料,都是由物件或者物件之間的關係表示的,函式是物件,字串是物件,每個東西都是物件的概念。每一個物件都有三種屬性:實體,型別和值。理解實體是理解物件中很重要的一步,實體一旦被建立,那麼就一直不會改變,也不會被顯式摧毀,同時通常意義來講,決定物件所支援的操作方式的型別(type,包括number,string,tuple及其他)也不會改變,改變的只可能是它的值。如果要找一個具體點的說明,實體就相當於物件在記憶體中的地址,是本質存在。而型別和值都只是實體的外在呈現。然後Python提供一些介面讓使用者和物件互動,比如id()函式用來獲得物件實體的整形表示(實際在這裡就是地址),type()函式獲取其型別。

這個object機制,就是c所不具備的,主要體現在下面幾點:

1 剛才說了,c是一個靜態型別語言,我們可以定義int a, char b等等,但必須是在原始碼裡面事先規定。比如我們可以在Python裡面任意一處直接規定a = “lk”,這樣,a的型別就是string,這是在其賦值的時候才決定的,我們無須在程式碼中明確寫出。而在C裡面,我們必須顯式規定char *a = “lk”,也就是人工事先規定好a的型別

2 由於在C中,沒有物件這個概念,只有“資料的表示”,比如說,如果有兩個int變數a和b,我們想比較大小,可以用a == b來判斷,但是如果是兩個字串變數a和b,我們就不得不用strcmp來比較了,因為此時,a和b本質上是指向字串的指標,如果直接還是用==比較,那比較的實際是指標中儲存的值——地址。

在Java中呢,我們通過使用 str1 == str2 可以確定兩個字串變數是否指向同一塊實體記憶體位置,這叫做“物件同一性”。在 Java 中要比較兩個字串值,你要使用 str1.equals(str2)。

然後在Python中,和前兩者都不一樣,由於物件的引入,我們可以用“is”這個運算子來比較兩個物件的實體,和具體物件的type就沒有關係了,比如你的物件是tuple也好,string也好,甚至class也好,都可以用”is”來比較,本質上就是“物件同一性”的比較,和Java中的==類似,和 C中的pointer比較類似。Python中也有==比較,這個就是值比較了。

3 由於物件機制的引入,讓Python的使用非常靈活,比如我們可以用自省方法來檢視記憶體中以物件形式存在的其它模組和函式,獲取它們的資訊,並對它們進行操作。用這種方法,你可以定義沒有名稱的函式,不按函式宣告的引數順序呼叫函式,甚至引用事先並不知道名稱的函式。 這些操作在C中都是不可想象的。

4 還有一個很有意思的細節,就是型別對物件行為的影響是各方面的,比如說,a = 1; b = 1這個語句中,在Python裡面引發的,可能是a,b同時指向一個值為1的物件,也可能是分別指向兩個值為1的物件。而例如這個語句,c = []; d = [],那麼c和d是肯定指向不同的,新建立的空list的。沒完,如果是”c = d = []“這個語句呢?此時,c和d又指向了相同的list物件了。這些區別,都是在c中沒有的。

最後,我們來說說為什麼python慢。主要原因就是function call overhead比較大。因為所有東西現在都是物件了,contruct 和destroy 花費也大。連1 + 1 都是 function call,像’12′+’45′ 這樣的要 create a third string object, then calls the string obj’s __add。可想而知,速度如何能快起來?

列表和陣列

分析Python中的list和C中的陣列總是很有趣的。相信可能一些朋友和一樣,初學列表的時候,都是把它當作是陣列來學的。最初對於list和陣列區別的定性,主要是集中在兩點。首先,list可以包含很多不同的資料型別,比如

["this", 1, "is", "an", "array"]

這個List,如果放在C中,其實是一個字串陣列,相當於二維的了。

其次呢,list有很多方法,其本身就是一個物件,這個和C的單純陣列是不同的。對於List的操作很多樣,因為有方法也有過載的運算子。也帶來一些問題,比如下面這個例子:

加入我們要產生一個多維列表,用下面這個語句

A = [[None] * 2] * 3

結果,A的值會是

[[None, None], [None, None], [None, None]]

初一看沒問題,典型的二維陣列形式的列表。好,現在我們想修改第一個None的值,用語句

A[0][0] = 5

現在我們再來看看A的值:

[[5, None], [5, None], [5, None]]

發現問題沒有?這是因為用 * 來複制時,只是建立了對這個物件的引用,而不是真正的建立了它。 *3 建立了一個包含三個引用的列表,這三個引用都指向同一個長度為2的列表。其中一個行的改變會顯示在所有行中,這當然不是你想要的。解決方法當然有,我們這樣來建立

A = [None]*3
for i in range(3):
A[i] = [None] * 2

這樣建立了一個包含三個不同的長度為2的列表。

所以,還是一直強調的,越複雜的東西,越靈活,也越容易出錯。

程式碼優化

C是一個很簡單的語言,當我們考慮優化的時候,通常想得也很簡單,比如系統級呼叫越少越好(緩衝區機制),消除迴圈的低效率和不必要的系統引用,等等,其實主要都是基於系統和硬體細節考慮的。而Python就完全不一樣了,當然上面說的這些優化形式,對於Python仍然是實用的,但由於 Python的語法形式千差萬別,庫和模組多種多樣,所以對於語言本身而言,就有很多值得注意的優化要點,舉幾個例子吧。

比如我們有一個list L1,想要構建一個新的list L2,L2包括L1的頭4個元素。按照最直接的想法,程式碼應該是

L2 = []
for i in range[3]:
L2.append(L1[i])

而更加優化和優美的版本是

L2 = L1[:3]

再比如,如果s1..s7是大字串(10K+),那麼join([s1,s2,s3,s4,s5,s6,s7])就會比 s1+s2+s3+s4+s5+s6+s7快得多,因為後者會計算很多次子表示式,而join()則在一次過程中完成所有的複製。還有,對於字串操作,對字串物件使用replace()方法。僅當在沒有固定字串模式時才使用正則表示式。

所以說,以優化為評判標準,如果說C是短小精悍,Python就是博大精深。

include和import

在C語言中的include非常簡單,因為形式單一,意義明確,當你需要用到外部函式等資源時,就用include。而Python中有一個相似的機制,就是import。乍一看,這兩個傢伙挺像的,不都是我們要用外部資源(最常見的就是函式或者模組(Python))時就用這個來指明麼?其實不然,兩者的處理機制本質區別在於,C中的include是用於告訴前處理器,這個include指定的檔案的內容,你都給我當作在本地原始檔中出現過。而 import呢,不是簡單的將後面的內容*直接*插入到本地裡面去,這玩意更加靈活。事實上,幾乎所有類似的機制,Python都比C靈活。這裡不是說C不好,C很簡練,我其實更喜歡C。

簡單說說這個靈活性。import在python中有三種形式,import X, from X import *( or a,b,c……), X = __import__(‘x’)。最常用的是第二種,因為比較方便,不像第一種那樣老是用X.module來呼叫模組。from X import *只是import那些public的module(一般都是不以__命名的模組),也可以指定a,b,c來import。

什麼時候用哪一種形式呢?應該說,在大多數的模組文件裡,都會明確告訴你應該用哪種形式。如果需要用到很多物件,那麼from X import *可能更合適一些,但是,就目前來看,大多數第三方Python庫都不推薦使用from modulename import * 這種格式。這樣做會使引入者的namespace混亂。很多人甚至對於那些專門設計用於這種模式的模組(包括Tkinter, threading和matplot)都不採用這種方式。而如果你僅僅需要某個物件類a,那麼用from X import a比用import X.a更好,因為以後你呼叫a的函式直接用a.function()既可以了,不用加X。

如果你連自己希望import的模組都不知道怎麼辦?請注意,此時Python的優勢就體現出來了,我們可以用 __import__(module)來呼叫module,其中這個module是字串,這樣,可以在執行時再決定,你到底要呼叫什麼module。舉個例子:

def classFromModule (module, Name):
mod = __import__ (module)
return getattr (mod, Name)

這裡,定義了一個函式classFromModule,你可以在程式碼的任何時候呼叫它,

o = classFromModule (ModuleOfTheClass, NameOfTheAttribute)()

只需要傳入字串形式的你希望import的模組ModuleOfTheClass和其中屬性的名字NameOfTheAttribute(當然可以是資料也可以是方法),就能呼叫了,這個名字字串不用事先指定,而是根據當時執行的情況來判斷。

順帶說一句,Python中import的順序也有預設規定,這個和C中的include有點類似,因為我們一般都是先include系統檔案,再 include自己的標頭檔案(而且還有<>和“”的區別)。Python中呢,一般應該按照以下順序import模組:

1. 標準庫模組 — 如 sys, os, getopt 等

2. 第三方模組

3. 本地實現的模組。

全域性變數

這裡談全域性變數呢,倒不是說Python和c的全域性變數概念不同,他們的概念是相同的。只是在使用機制上,是有一些差異的。舉個例子:

– module.py –
globalvar = 1

def func():
print globalvar
# This makes someglobal readonly,
# any attempt to write to someglobal
# would create a new local variable.

def func2():
global globalvar
globalvar = 2
# this allows you to manipulate the global
# variable

在 func這個函式中,globalvar是隻讀的。如果你使用了globalvar = xxx這種賦值語句,Python會重新創造一個新的本地物件並將新值賦給它,原來的物件值不變。而在func2函式中,由於我們事先申明瞭 globalvar是global的,那麼此時的更改就直接在全域性變數上生效。

很明顯這和c中的使用機制是不一樣的,在c中,我們只要在函式外的全域性區域申明瞭變數,就可以在函式中直接對其操作,不用還申明一個global。

Published 2008-06-22

Filed in c,code,python and tagged cpython