最近因為工作的需求開始開發一些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 Javascript 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可以定義一個物件的屬性為唯讀。
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: v except 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 list True >>> type({}) is dict True >>> type('') is str True >>> type(0) is int True
同时也可以通过isinstance()来判断类的类型
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风格
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 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 迭代器和生成器的例子。
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的用法如下。
@classmethod def foo (arg1, arg2): ....