最近由於工作的需要開始開發一些Python的東西,由於之前一直在使用Javascript,所以會不自覺的使用一些Javascript的概念,文法什麼的,經常掉到坑裡。我覺得對於從Javascript轉到Python,有必要總結一下它們之間的差異。
基本概念
Python和Javascript都是指令碼語言,所以它們有很多共同的特性,都需要解譯器來運行,都是動態類型,都支援自動記憶體管理,都可以調用eval()來執行指令碼等等指令碼語言所共有的特性。
然而它們也有很大的區別,Javascript這設計之初是一種用戶端的指令碼語言,主要應用於瀏覽器,它的文法主要借鑒了C,而Python由於其“優雅”,“明確”,“簡單”的設計而廣受歡迎,被應用於教育,科學計算,web開發等不同的情境中。
編程範式
Python和Javascript都支援多種不同的編程範式,在物件導向的編程上面,它們有很大的區別。Javascript的物件導向是基於原型(prototype)的, 對象的繼承是由原型(也是對象)建立出來的,由原型對象建立出來的對象繼承了原型鏈上的方法。而Python則是中規中矩的基於類(class)的繼承,並天然的支援多態(polymophine)。
OO in Pyhton
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 Javascriptvar 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可以定義一個對象的屬性為唯讀。
var obj = {};Object.defineProperty(obj, "prop", { value: "test", writable: false});
在ECMAScript5的支援中,也可以調用Object的freeze方法來是對象變得不可修改。
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中變數的聲明和初始化
v1;v2 = null;var v3;var v4 = null;var v5 = 'something';
在如上的代碼中v1是全域變數,未初始化,值為undefined;v2是全域變數,初始化為空白值;v3為局部未初始設定變數,v4是局部初始化為空白值的變數;v5是局部已初始化為一個字元處的變數。
Python中變數的聲明和初始化
v1 = None
v2 = 'someting'
Python中的變數聲明和初始化就簡單了許多。當在Python中訪問一個不存在的變數時,會拋出NameError的異常。當訪問對象或者字典的值不存在的時候,會拋出AttributeError或者KeyError。因此判斷一個值是否存在在Javascript和Python中需要不一樣的方式。
Javascript中檢查某變數的存在性:
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中檢查某變數的存在性:
try: vexcept NameError ## do something if v does not exist
在Python中也可以通過檢查變數是不是存在於局部locals()或者全域globals()來判斷是否存在該變數。
類型檢查
Javascript可以通過typeof來獲得某個變數的類型:
typeof in Javascript 的例子:
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來獲得資料的類型。
>>> type([]) is listTrue>>> type({}) is dictTrue>>> type('') is strTrue>>> type(0) is intTrue
同時也可以通過isinstance()來判斷類的類型
class A: passclass B(A): passisinstance(A(), A) # returns Truetype(A()) == A # returns Trueisinstance(B(), A) # returns Truetype(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風格
def func(list): for i in range(0,len(list)): print list[i]Javascript風格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,通常的寫法是
(A and B) or C
因為這樣寫有一定的缺陷,也可以寫作
B if A else C
Python對賦值操作的一個重要的改進是不允許賦值操作返回賦值的結果,這樣做的好處是避免出現在應該使用相等判斷的時候錯誤的使用了賦值操作。因為這兩個操作符實在太像了,而且從自然語言上來說它們也沒有區別。
++運算子
Python不支援++運算子,沒錯你再也不需要根據++符號在變數的左右位置來思考到底是先加一再賦值呢還是先賦值再加一。
連續賦值
利用元組(tuple),Python可以一次性的給多個變數賦值
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
函數參數
Python的函數參數支援具名引數和選擇性參數(提供預設值),使用起來很方便,Javascript不支援選擇性參數和預設值(可以通過對arguments的解析來支援)
def info(object, spacing=10, collapse=1):
... ...
其它
立即調用函數運算式 (IIFE)
Javascript的一個方便的特性是可以立即調用一個剛剛聲明的匿名函數。也有人稱之為自調用匿名函數。
下面的代碼是一個module模式的例子,使用閉包來儲存狀態實現良好的封裝。這樣的代碼可以用在無需重用的場合。
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產生器的例子
# a generator that yields items instead of returning a listdef 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 迭代器和產生器的例子。
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的映射運算式可以非常方便的協助使用者構造列表、字典、集合等內建資料類型。
下面是列表映射運算式使用的例子:
>>> [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
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包。
g = lambda x : x*3
裝飾器(Decorators)
Decorator是一種設計模式,大部分語言都可以支援這樣的模式,Python提供了原生的對該模式的支援,算是一種對程式員的便利把。
Decorator的用法如下。
@classmethoddef foo (arg1, arg2): ....