最近由於工作的需要開始開發一些Python的東西,由於之前一直在使用Javascript,所以會不自覺的使用一些Javascript的概念,語法什麼的,經常掉到坑裡。我覺得對於從Javascript轉到Python,有必要總結一下它們之間的差異。
基本概念
Python和Javascript都是指令碼語言,所以它們有很多共同的特性,都需要直譯器來執行,都是動態型別,都支援自動記憶體管理,都可以呼叫eval()來執行指令碼等等指令碼語言所共有的特性。
然而它們也有很大的區別,Javascript這設計之初是一種客戶端的指令碼語言,主要應用於瀏覽器,它的語法主要借鑑了C,而Python由於其“優雅”,“明確”,“簡單”的設計而廣受歡迎,被應用於教育,科學計算,web開發等不同的場景中。
程式設計正規化
Python和Javascript都支援多種不同的程式設計正規化,在物件導向的程式設計上面,它們有很大的區別。Javascript的物件導向是基於原型(prototype)的, 物件的繼承是由原型(也是物件)建立出來的,由原型物件建立出來的物件繼承了原型鏈上的方法。而Python則是中規中矩的基於類(class)的繼承,並天然的支援多型(polymophine)。
OO in Pyhton
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Employee: 'Common base class for all employees' empCount = 0 ##類成員 def __init__(self, name, salary): self.name = name self.salary = salary Employee.empCount += 1 def displayCount(self): print "Total Employee %d" % Employee.empCount def displayEmployee(self): print "Name : ", self.name, ", Salary: ", self.salary ## 建立例項 ea = Employee("a",1000) eb = Employee("b",2000) |
OO in Javascript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var empCount = 0; //建構函式 function Employee(name, salary){ this.name = name; this.salary = salary; this.empCount += 1; } Employee.prototype.displayCount = function(){ console.log("Total Employee " + empCount ); } Employee.prototype.displayEmployee = function(){ console.log("Name " + this.name + ", Salary " + this.salary ); } //建立例項 var ea = new Employee("a",1000); var eb = new Employee("b",2000); |
因為是基於物件的繼承,在Javascript中,我們沒有辦法使用類成員empCount,只好宣告瞭一個全域性變數,當然實際開發中我們會用更合適的scope。注意Javascript建立物件需要使用new關鍵字,而Python不需要。
除了原生的基於原型的繼承,還有很多利用閉包或者原型來模擬類繼承的Javascript OO工具,因為不是語言本身的屬性,我們就不討論了。
執行緒模型
在Javascript的世界中是沒有多執行緒的概念的,併發使用過使用事件驅動的方式來進行的, 所有的JavaScript程式都執行在一個執行緒中。在HTML5中引入web worker可以併發的處理任務,但沒有改變Javascript單執行緒的限制。
Python通過thread包支援多執行緒。
不可改變型別 (immutable type)
在Python中,有的資料型別是不可改變的,也就意味著這種型別的資料不能被修改,所有的修改都會返回新的物件。而在Javascript中所有的資料型別都是可以改變的。Python引入不可改變型別我認為是為了支援執行緒安全,而因為Javascript是單執行緒模型,所以沒有必要引入不可改變型別。
當然在Javascript可以定義一個物件的屬性為只讀。
1 2 3 |
var obj = {};Object.defineProperty(obj, "prop", { value: "test", writable: false}); |
在ECMAScript5的支援中,也可以呼叫Object的freeze方法來是物件變得不可修改。
1 |
Object.freeze(obj) |
資料型別
Javascript的資料型別比較簡單,有object、string、boolean、number、null和undefined,總共六種
Python中一切均為物件,像module、function、class等等都是。
Python有五個內建的簡單資料型別bool、int、long、float和complex,另外還有容器型別,程式碼型別,內部型別等等。
布林
Javascript有true和false。Python有True和False。它們除了大小寫沒有什麼區別。
字串
Javascript採用UTF16編碼。
Python使用ASCII碼。需要呼叫encode、decode來進行編碼轉換。使用u作為字首可以指定字串使用Unicode編碼。
數值
Javascript中所有的數值型別都是實現為64位浮點數。支援NaN(Not a number),正負無窮大(+/-Infiity)。
Python擁有諸多的數值型別,其中的複數型別非常方便,所以在Python在科研和教育領域很受歡迎。這應該也是其中一個原因吧。Python中沒有定義NaN,除零操作會引發異常。
列表
Javascript內建了array型別(array也是object)
Python的列表(List)和Javascript的Array比較接近,而元組(Tuple)可以理解為不可改變的列表。
除了求長度在Python中是使用內建方法len外,基本上Javascript和Python都提供了類似的方法來操作列表。Python中對列表下標的操作非常靈活也非常方便,這是Javascript所沒有的。例如l[5:-1],l[:6]等等。
字典、雜湊表、物件
Javascript中大量的使用{}來建立物件,這些物件和字典沒有什麼區別,可以使用[]或者.來訪問物件的成員。可以動態的新增,修改和刪除成員。可以認為物件就是Javascript的字典或者雜湊表。物件的key必須是字串。
Python內建了雜湊表(dictS),和Javascript不同的是,dictS可以有各種型別的key值。
空值
Javascript定義了兩種空值。 undefined表示變數沒有被初始化,null表示變數已經初始化但是值為空。
Python中不存在未初始化的值,如果一個變數值為空,Python使用None來表示。
Javascript中變數的宣告和初始化
1 2 3 4 5 |
v1; v2 = null; var v3; var v4 = null; var v5 = 'something'; |
在如上的程式碼中v1是全域性變數,未初始化,值為undefined;v2是全域性變數,初始化為空值;v3為區域性未初始化變數,v4是區域性初始化為空值的變數;v5是區域性已初始化為一個字元處的變數。
Python中變數的宣告和初始化
1 2 |
v1 = None v2 = 'someting' |
Python中的變數宣告和初始化就簡單了許多。當在Python中訪問一個不存在的變數時,會丟擲NameError的異常。當訪問物件或者字典的值不存在的時候,會丟擲AttributeError或者KeyError。因此判斷一個值是否存在在Javascript和Python中需要不一樣的方式。
Javascript中檢查某變數的存在性:
1 2 3 4 5 6 7 |
if (!v ) { // do something if v does not exist or is null or is false } if (v === undefined) { // do something if v does not initialized } |
注意使用!v來檢查v是否初始化是有歧義的因為有許多種情況!v都會返回true
Python中檢查某變數的存在性:
1 2 3 4 |
try: v except NameError ## do something if v does not exist |
在Python中也可以通過檢查變數是不是存在於區域性locals()或者全域性globals()來判斷是否存在該變數。
型別檢查
Javascript可以通過typeof來獲得某個變數的型別:
typeof in Javascript 的例子:
1 2 3 4 5 6 7 8 |
typeof 3 // "number" typeof "abc" // "string" typeof {} // "object" typeof true // "boolean" typeof undefined // "undefined" typeof function(){} // "function" typeof [] // "object" typeof null // "object" |
要非常小心的使用typeof,從上面的例子你可以看到,typeof null居然是object。因為javscript的弱型別特性,想要獲得更實際的型別,還需要結合使用instanceof,constructor等概念。具體請參考這篇文章
Python提供內建方法type來獲得資料的型別。
1 2 3 4 5 6 7 8 |
>>> type([]) is list True >>> type({}) is dict True >>> type('') is str True >>> type(0) is int True |
同時也可以通過isinstance()來判斷類的型別
1 2 3 4 5 6 7 8 |
class A: pass class B(A): pass isinstance(A(), A) # returns True type(A()) == A # returns True isinstance(B(), A) # returns True type(B()) == A # returns False |
但是注意Python的class style發生過一次變化,不是每個版本的Python執行上述程式碼的行為都一樣,在old style中,所有的例項的type都是‘instance’,所以用type方法來檢查也不是一個好的方法。這一點和Javascript很類似。
自動型別轉換
當操作不同型別一起進行運算的時候,Javascript總是儘可能的進行自動的型別轉換,這很方便,當然也很容易出錯。尤其是在進行數值和字串操作的時候,一不小心就會出錯。我以前經常會計算SVG中的各種數值屬性,諸如x,y座標之類的,當你一不小心把一個字串加到數值上的時候,Javascript會自動轉換出一個數值,往往是NaN,這樣SVG就完全畫不出來啦,因為自動轉化是合法的,找到出錯的地方也非常困難。
Python在這一點上就非常的謹慎,一般不會在不同的型別之間做自動的轉換。
語法
風格
Python使用縮排來決定邏輯行的結束非常具有創造性,這也許是Python最獨特的屬性了,當然也有人對此頗具微詞,尤其是需要修改重構程式碼的時候,修改縮排往往會引起不小的麻煩。
Javascript雖然名字裡有Java,它的風格也有那麼一點像Java,可是它和Java就好比雷峰塔和雷鋒一樣,真的沒有半毛錢的關係。到時語法上和C比較類似。這裡必須要提到的是coffeescript作為構建與Javascript之上的一種語言,採用了類似Python的語法風格,也是用縮排來決定邏輯行。
Python風格
1 2 3 |
def func(list): for i in range(0,len(list)): print list[i] |
Javascript風格
1 2 3 4 5 |
function funcs(list) { for(var i=0, len = list.length(); i < len; i++) { console.log(list[i]); } } |
從以上的兩個程式碼的例子可以看出,Python確實非常簡潔。
作用範圍和包管理
Javascript的作用域是由方法function來定義的,也就是說同一個方法內部擁有相同的作用域。這個嚴重區別與C語言使用{}來定義的作用域。Closure是Javascript最有用的一個特性。
Python的作用域是由module,function,class來定義的。
Python的import可以很好的管理依賴和作用域,而Javascript沒有原生的包管理機制,需要藉助AMD來非同步的載入依賴的js檔案,requirejs是一個常用的工具。
賦值邏輯操作符
Javascript使用=賦值,擁有判斷相等(==)和全等(===)兩種相等的判斷。其它的邏輯運算子有&& 和||,和C語言類似。
Python中沒有全等,或和與使用的時and 和 or,更接近自然語言。Python中沒有三元運算子 A :B ?C,通常的寫法是
1 |
(A and B) or C |
因為這樣寫有一定的缺陷,也可以寫作
1 |
B if A else C |
Python對賦值操作的一個重要的改進是不允許賦值操作返回賦值的結果,這樣做的好處是避免出現在應該使用相等判斷的時候錯誤的使用了賦值操作。因為這兩個操作符實在太像了,而且從自然語言上來說它們也沒有區別。
++運算子
Python不支援++運算子,沒錯你再也不需要根據++符號在變數的左右位置來思考到底是先加一再賦值呢還是先賦值再加一。
連續賦值
利用元組(tuple),Python可以一次性的給多個變數賦值
1 |
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7) |
函式引數
Python的函式引數支援命名引數和可選引數(提供預設值),使用起來很方便,Javascript不支援可選引數和預設值(可以通過對arguments的解析來支援)
1 2 |
def info(object, spacing=10, collapse=1): ... ... |
其它
立即呼叫函式表示式 (IIFE)
Javascript的一個方便的特性是可以立即呼叫一個剛剛宣告的匿名函式。也有人稱之為自呼叫匿名函式。
下面的程式碼是一個module模式的例子,使用閉包來儲存狀態實現良好的封裝。這樣的程式碼可以用在無需重用的場合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var counter = (function(){ var i = 0; return { get: function(){ return i; }, set: function( val ){ i = val; }, increment: function() { return ++i; } }; }()); |
Python沒有相應的支援。
生成器和迭代器(Generators & Iterator)
在我接觸到的Python程式碼中,大量的使用這樣的生成器的模式。
Python生成器的例子
1 2 3 4 5 6 7 8 |
# a generator that yields items instead of returning a list def firstn(n): num = 0 while num < n: yield num num += 1 sum_of_first_n = sum(firstn(1000000)) |
Javascript1.7中引入了一些列的新特性,其中就包括生成器和迭代器。然而大部分的瀏覽器除了Mozilla(Mozilla基本上是在自己玩,下一代的Javascript標準應該是ECMAScript5)都不支援這些特性
Javascript1.7 迭代器和生成器的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function fib() { var i = 0, j = 1; while (true) { yield i; var t = i; i = j; j += t; } }; var g = fib(); for (var i = 0; i < 10; i++) { console.log(g.next()); } |
列表(字典、集合)對映表示式 (List、Dict、Set Comprehension)
Python的對映表示式可以非常方便的幫助使用者構造列表、字典、集合等內建資料型別。
下面是列表對映表示式使用的例子:
1 2 3 4 5 6 |
>>> [x + 3 for x in range(4)] [3, 4, 5, 6] >>> {x + 3 for x in range(4)} {3, 4, 5, 6} >>> {x: x + 3 for x in range(4)} {0: 3, 1: 4, 2: 5, 3: 6} |
Javascript1.7開始也引入了Array Comprehension
1 2 |
var numbers = [1, 2, 3, 4]; var doubled = [i * 2 for (i of numbers)]; |
Lamda表示式 (Lamda Expression )
Lamda表示式是一種匿名函式,基於著名的λ演算。許多語言諸如C#,Java都提供了對lamda的支援。Pyhton就是其中之一。Javascript沒有提供原生的Lamda支援。但是有第三方的Lamda包。
1 |
g = lambda x : x*3 |
裝飾器(Decorators)
Decorator是一種設計模式,大部分語言都可以支援這樣的模式,Python提供了原生的對該模式的支援,算是一種對程式設計師的便利把。
Decorator的用法如下。
1 2 3 |
@classmethod def foo (arg1, arg2): .... |
更多decorator的內容,請參考https://wiki.python.org/moin/PythonDecorators
本人對Javascript和Python的認識有限,歡迎大家提出寶貴意見。