最近由于工作的需要開始開發一些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包支持多線程。
在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中大量的使用{}來創建對象,這些對象和字典沒有什么區別,可以使用[]或者.來訪問對象的成員??梢詣討B的添加,修改和刪除成員??梢哉J為對象就是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 ): ... ... |
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沒有相應的支持。
在我接觸到的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()); } |
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表達式是一種匿名函數,基于著名的λ演算。許多語言諸如C#,Java都提供了對lamda的支持。Pyhton就是其中之一。Javascript沒有提供原生的Lamda支持。但是有第三方的Lamda包。
1 | g = lambda x : x * 3 |
Decorator是一種設計模式,大部分語言都可以支持這樣的模式,Python提供了原生的對該模式的支持,算是一種對程序員的便利把。
Decorator的用法如下。
1 2 3 | @classmethod def foo (arg1, arg2): .... |
更多decorator的內容,請參考https://wiki.python.org/moin/PythonDecorators
本人對Javascript和Python的認識有限,歡迎大家提出寶貴意見。
新聞熱點
疑難解答