當前位置:
首頁 > 知識 > 寫給Java開發者的Python入門

寫給Java開發者的Python入門


Python部落(python.freelycode.com)組織翻譯,禁止轉載,歡迎轉發。


編者註:

這是一篇來自Java社區的文章,作者的觀點是從Java開發者的角度進行講解的。但是....為了部落的榮耀,人生苦短,我用Python!


從哲學的角度來看,Python和Java是全然相反的。它放棄了靜態類型和剛性結構,並以鬆散的沙盒結構取而代之,在這樣的沙盒裡你基本上可以做任何你想做的事情。也許Python這門語言關注的是你能做什麼,而Java則更關注你可以做些什麼。


儘管如此,兩門語言都從C語言中汲取了大量靈感。它們都是使用塊、循環、函數、賦值、中綴算術的命令式語言。二者都大量使用了類、對象、繼承和多態。二者對異常的處理都非常優秀。兩門語言都能自動進行內存管理。它們甚至都需要編譯成位元組碼然後在虛擬機上運行,儘管Python的編譯過程對用戶是隱式的。Python有部分功能也借鑒了Java,例如Python標準庫中的logging和unittest模塊分別受到Java的log4j和JUnit的啟發。


基於以上的技術重疊,我覺得Java開發者理應對Python產生家人一般的感覺。所以我給你們帶來了Python的一些簡單介紹。有機會的話,我會給大家講一下是什麼讓Python和Java有所不同,以及這些不同吸引我的原因。至少,你有可能會把一些有意思的想法帶回到Java的生態系統中。


(如果需要Python教程,Python部落的

視頻教程

是一個不錯的選擇。另外需要注意的是,這是從Python 3的視角來寫的!目前,Python 2 仍然活躍在很多領域中,它和Python 3 有一些語法上的不同。)

語法


那麼,就從hello world 開始吧:




呃,好吧,看起來好像沒什麼啟發性。好吧,接下來是一個函數,用來統計文件中出現頻次最高的十個單詞。這裡我是用了Python標準庫中的Counter類取了個巧,沒辦法它實在太好用了。



Python

使用空格作為分隔。人們對此頗有怨言。我之前甚至將其視為異端。但是十多年後的今天,這看上去竟如此正常以至於再讓我用回花括弧我還得花一段時間來適應。如果你是因為這個而遲遲沒有開始,那麼我不保證能說服你,但我強烈推介你暫時忽略它;在實際工作中,這真的不會帶來太大的問題,反而會減少代碼中的一些干擾。除此之外,Python開發者從來不用在諸如這個花括弧應該放在哪裡之類的問題產生爭論。


除了這些美學上的區別外,其他看起來都很熟悉。我們得到一些數值,做了賦值操作,調用了模塊。Python的import語句使用起來有一點不同,但是很明顯它的作用是「使後面這些東西變得可用」。除了一些標點符號外,Python的for循環和Java的for循環非常像。函數使用def分隔而不是Java中的class,但是它運行的過程並無太大差異:函數可以使用參數調用,然後返回值(即使上述例子中的函數並沒有返回值。)


只有兩個地方有比較明顯的差別。第一個是with代碼塊,這和Java 7 的「try-with-resources」很像—這保證了區塊中的代碼運行結束後文件的正常關閉,即使代碼塊中拋出了異常。另外一個是f」…」語法,這是相當新的一個特性,它允許把表達式直接寫入字元串中。


大概就是這樣了!你已經讀了一些Python代碼。至少,它看上去並不像一門完全來自外星的語言了吧。

動態類型


從上面的列子我們可以看到,Python代碼沒有太多的類型聲明。變數聲明的時候沒有,函數的參數和返回值沒有,對象上也沒有。任何事在任何時間可以是任何類型。我還沒有介紹過類的定義,所以這裡就有一個:




甚至x,y都沒有聲明為屬性;它們的存在是因為構造器中創建了它們。沒有什麼限制我傳入整數,我也可以傳入浮點數,

小數或者分數等。


如果你只用過靜態語言,這聽起來簡直要亂成一團。類型是溫暖的懶惰的以及令人滿意的。類型保證了…好吧,也許不一定能保證代碼能順利工作(儘管有人會不同意),但總是有好處的。當你連正確的類型都沒搞清楚的時候你怎麼可能順利地運行代碼。


稍等片刻- Java也不一定能保證這些呀。畢竟,所有的對象都可以是null,對吧?這樣看來,實際上幾乎從來沒有一個類型正確的對象。


你可能會把動態類型看做對null問題的完全屈服。如果我們必須去處理它,我們也可以試著去擁抱它,然後讓它來為我們工作—通過把所有問題都放在運行過程中解決。類型錯誤在Python中變成很普通的邏輯錯誤,你可以用相同的方式處理。


(另一種相反的方法,請查看Rust,這是一門沒有null值或者異常的語言。我仍然更願意使用Python,儘管我對Rust從不說謊的類型系統讚賞有加。)


在我的magnitude模塊中,self.x的值是整型、浮點型還是其他任何類型的值都不重要。它只要支持**操作,然後返回支持+操作的值就夠了。(Python支持運算符重載,所以它可能是任何類型。)正常的方法調用同樣適用:只要在實踐中能運行,那麼任何類型都是可以接受的。


這意味著Python不需要泛型;一切都是按照泛型工作的。Python也不需要介面;一切都是多態的。沒有向下轉型,沒有向上轉型,在類型系統中也沒有逃生艙口。當它們可以和任何可迭代對象在一起運行正常時,API也不需要必須是列表。


許多常見模式將變得更加簡單。你可以創建一個封裝和代理而不用去修改你的消費者代碼。你可以使用組合來替代集成擴展第三方類,而不用做任何特殊的工作來保證多態。靈活的API不需要將每個類作為介面重複複製;一切都早已經是隱式的介面了。


動態類型哲學


使用靜態類型,無論誰編寫一些代碼來選擇類型,編譯器都要確定其能運行。而使用動態類型,無論誰使用某些代碼來選擇類型,都會在運行過程中嘗試一下。這就是前文講到的兩門語言對立哲學在實際中的體現:類型系統關注的是你可以做什麼,而不是你可能要做什麼。


這樣使用動態類型被稱為「鴨子類型」,這是基於思想「如果它走起路來像一隻鴨子,叫起來也像一隻鴨子,那麼它就是一隻鴨子」。簡單來說,意思就是如果所有你想做的只是像鴨子一樣叫,那麼不用像靜態語言那樣限制你的代碼必須接受一隻鴨子,你可以接受任何給你的東西然後想辦法讓它像鴨子一樣呱呱叫就行。如果它能做到,那麼它就和鴨子一樣好用了。(如果它做不到,你可能會得到一個類型錯誤,但這也不是多大的問題。)


同時要注意的是,Python仍然是強類型的。這個術語可能有點含糊,總體上來說它的意思就是值在運行過程中始終擁有他們的類型。典型的例子就是Python不會允許你把一個字元串和一個數字相加,而像JavaScript這樣的弱類型語言將會隱式地把一個類型轉換成另外一種,,這裡使用的優先順序規則和你想到的可能有所不同。


不像大多數的動態語言,Python在運行之前就可以捕捉錯誤信息。例如,從一個不存在的變數中讀取信息會產生一個異常,包括從字典(類似Java中的Map)中讀取一個不存在的鍵。在JavaScript、Lua和類似的語言中,上述操作會返回null值(對Map中不存在的鍵取值,Java也會返回null!)如果想在鍵不存在時返回默認值,Python的字典類提供了更加明確的方法調用。


這樣的操作當然是有所取捨的,到底值不值得要因人而異、因項目而異。對我來說,至少非常適合用來設計一個更加可靠的系統,在我看到它運行之後;而靜態語言則需要預先進行設計。靜態類型使大量嘗試不同想法變得更加困難。你確實少了一些靜態檢查保證,但在我的實踐中,大多數的類型錯誤都會立刻被捕捉到...因為我寫完代碼的第一件事情就是運行一下。另外一些會在我們的測試中被捕捉到—這個測試你可以用任何語言來寫,當然Python寫起來要相對容易一些。




混合範式




Python

和Java都是命令式和面向對象的;它們都通過執行命令來運行,同時都遵循一切皆對象的理念。




在最近的發行版中,Java新增了一些函數類的特性,我認為這是一個好事。Python也有一定比例的函數式特性,但是二者使用的方法還是有些不同。Python提供了內置的函數如map和reduce,但它們實際上並不是設計用來把一系列小的函數串聯起來的。



相反,Python把挺多東西混合起來了。我不太清楚Python使用的方法的通用名稱。我覺得它應該是把函數鏈的概念拆分成兩部分了:序列運行,使函數本身更加強大。




序列




序列和迭代在Python中扮演了非常重要的角色。序列是最重要的數據結構之一,所以操作序列的工作都很容易獲取。我認為Python對於函數式編程的實現如下:Python首先使得使用命令式代碼來操作序列非常容易,而不是使得結合許多小函數然後應用於序列非常容易。




回到本文開始的地方,我曾寫下這麼一行代碼:




for

循環很常見,但這行代碼中同時對兩個變數進行迭代。真正發生的事most_common這個列表中的每個元素都返回一個元祖,一組有序唯一值。元組可以通過解包賦值給一組變數,這就是for循環中發生的事情。Python中的元組通常作為返回一組值,但它們有時在ad-hoc數據結構中也很有用。而在Java中,你需要一個完整的類以及很多行的賦值操作。




所有可迭代對象都支持解包。解包也支持嵌套結構,所以a, (b,c) = … 這種形式的可以順利解包。對不定長的序列來說,* leftovers 元素可以出現在任何位置,並且將根據需要獲取儘可能多的元素。也許你真的比較喜歡LISP?




Python

同樣也有創建列表的簡單表達—被稱為列表生成式—這比map之類的函數式方法要更常見。類似的方法也支持創建字典和元組。整個循環都被壓縮到你真正感興趣的一個表達式上。




標準庫還在itertools模塊中包含了一些有趣的迭代,組合器。




最後,Python使用生成器來生成命令代碼的延遲序列。包含yield關鍵字的函數被調用時,不會立即執行,而是產生一個生成器對象。當生成器進行迭代時,代碼運行到yield關鍵字處即停止;返回的值將作為下一次迭代的值。



由於生成器運行是有延遲的,它可以用來生成無限的序列或者在中途中斷。它們可以用來生成大量的大型對象,而不需要為了保持存活而一次性消耗大量內存。它們同樣被用來作為鏈式風格的函數式編程的一般替代。你可以編寫熟悉的命令行代碼,而不用考慮maps 和filters的組合。




在Java中實現一個完全任意延遲的迭代器,你可能需要在迭代器中增加一些代碼來跟蹤其狀態。除了最簡單的情況之外,這將變得有些棘手。Python也有迭代的介面,所以你仍然可以使用這種方式來實現,但生成器非常好用以至於大多數常用的迭代都是用它實現的。




另外,由於生成器可以自動停止,所以它們在另外一些場景下也是非常有用的。通過手動調用生成器(而不是用一次for循環來實現一次性全部迭代),你可以先運行一個函數,使其在某處停止,然後在函數運行過程中執行其他代碼。Python依靠這些特性新增支持了

非同步

I/O

(不使用線程的非阻塞網路),即使現在它已經有專用的async 和 await 語法。




函數




Python

的函數看上去似曾相似。你可以使用參數調用它。參數傳遞的方式也和Java一模一樣 – Python既不是引用也不是隱式複製。Pyhton甚至也有描述符,和Java的注釋類似,但是Python的是一種語法而且在運行過程中是可讀的。




Java

有使用args…的可變參數函數;相應的Python對應的函數使用*args。(支持 *leftovers語法的解包操作正是受函數這種語法的啟發。)但Python在這基礎上還提升了一些技巧。任何參數都可以設置默認值,使其成為可選參數。另外,參數也可以設置名稱-之前我曾經這樣做過Point(x=3, y=4)。*args語法用來在函數調用時接收一個列表作為參數;**kwargs用來接受或者傳遞字典類型的帶名稱參數。參數可以是「僅關鍵字keyword-only」的,所以參數傳遞必須通過名字,這對可選的布爾參數來說非常友好。




當然,Python沒有函數重載的概念,不過大多數你需要它的時候都可以用鴨子類型和可選參數來取代。




這是現階段Python最強大的功能之一。就像動態語言允許你使用封裝或者代理透明的替換一個對象一樣,* args和**kwargs允許任何函數被透明封裝起來。




不好意思,這看上去有點難以理解。不過別太擔心它是怎麼工作的;需要知道的就是函數foo被一個新函數所代替了,它將所有的參數都轉發給foo。不管是foo還是調用都和之前完全一樣。




我還無法想像這個功能有多麼強大。它可以用在日誌、調試、資源管理、緩存、訪問控制,驗證等等。它在串聯其他元編程特性上表現的非常不錯,換句話說,它讓你結構化思考而不只是單單的編程。






對象和動態運行時




運行時是一種在幕後驅動語言核心部分的東西,它可以在運行時被執行。C和C++之類的語言沒有動態運行時;它們源碼的結構被「烘焙」進編譯器輸出,之後就沒有能修改的機會了。另一方面,Java確實有動態運行時,Java甚至還有一整套用來進行自省(反射)。




當然,Python也有自省(反射)。Python有許多簡單的通過自省構建的函數可以用於監控、修改對象的屬性,這對調試和偶發性的異常來說非常有用。


Python

還將這個功能進行了升級。因為一切都在運行時完成,Python使用了很多擴展來自定義其語義。你無法修改語法,所以代碼看上去仍然很像Python,但你可以分解出一些結構,這在死板的語言來說是非常困難的。


舉一個極端點的例子,pytest使用Python的assert語句很智能的完成了很多事情。通常,assert x == 1 這樣的代碼

當判定為假時,

只會簡單的拋出一個屬性異常,這讓你對異常產生的位置和時間毫無概念。這也正是Python內置的unittest模塊(和JUnit以及其他語言的單元測試庫類似)提供了一堆諸如assertEquals之類的函數的原因。不幸的是,這也讓測試在第一眼看上去顯得更加複雜難懂。但在pytest中,assert x == 1是可用的。如果結果為false,pytest將會告訴你x是什麼…或者兩個列表哪裡出現偏離,或者兩個集合有什麼不同,以及其他事情。基於進行的比較和操作數的類型,所有的這一切都是自動產生的。




那pytest又是怎麼運行的呢?這些你並不需要知道。你也不需要搞清楚使用pytest來寫測試-這總是讓人很開心的。




這正是動態運行時的優勢所在。你個人來說可能不會使用這些特性。但你將從使用這些功能的庫中獲益良多而不需要去弄明白它們是如何工作的。甚至Python本身也通過使用自己的擴展擁有了很多特性-這些不需要改變語法和解釋器。




對象




我最喜歡的例子是屬性的獲取。在Java中,一個point類可能會有getX()和setX()方法而不是直接操作x屬性。原因是在不破壞介面的前提下,你可以修改x的讀或寫。在Python中,你不用擔心前面的這些問題,因為必要時你能攔截屬性的存取。




有趣的@property語法是一個裝飾器,和Java的註解看上去很像,但是可以更直接的修改函數和類。




讀取point.x現在調用了一個函數並以函數的返回值作為輸出。這對調用代碼來說是完全透明的-和讀取其他屬性並沒太大區別-但對象可以根據自己的需要干預和處理它。不像Java,讀取屬性是類API的一部分,你可以自由得進行自定義。(注意這個例子使得x是只讀的,因為我並沒有指定如何對其進行修改。可寫屬性的語法看上去可能有點滑稽,這裡暫時不管它如何工作。但你可以規定只有__init__可以賦值給point.x。)




同樣的特性也存在於C#之類的靜態語言中,所以這可能並不會讓人們多麼印象深刻。關於Python真正有意思的部分是屬性本身並沒什麼特別。它這是一種使用純Python語言幾行代碼就能寫下來的內置類型。它能運行主要還是因為Python類可以自定義它的屬性訪問,包括一般的和暗屬性的。封裝、代理和組合是很容易實現的;你可以將所有訪問調用轉發到底層對象,而不必知道它有什麼方法。




同樣的鉤子屬性可用於

惰性載入屬性

或者

自動持有弱引用

的屬性,對調用代碼來說完全透明,全部使用純Python就能實現。




你可能已經注意到至今為止我的代碼都沒有public或者private修飾符。事實上,Python並沒有這些概念。按照慣例,用一個下劃線開頭標識私有,或者更確切地說—「不打算作為穩定公開API的一部分」。但這其實並沒有語法上的意義,Pyhton本身並不會阻止任何人查看或者修改這樣的屬性(調用方法)。同樣,也沒有final、static、const這些概念。




這又是同樣的哲學在起作用:核心Python通常不會阻礙你做任何事情。當我們需要的時候,它將會非常有用。我已經通過啟動時調用或重寫、甚至重新定義私有方法來修改第三方包的BUG。這樣我就不需要重新做一個項目的本地分支,可以省不少事情。而且一旦官方修復了BUG,我可以很容易得刪掉自己的補丁代碼。




同樣,你可以很容易的修改依賴於外部狀態的測試代碼,例如當前時間。如果重構不行,你可以在測試期間使用模擬函數代替time.time()。庫函數就是模塊的屬性(和Java的包類似),Python的模塊和其他任何對象一樣,所以它們也能以同樣的方式來監測和修改。







Java

的類由Class對象支持,但二者並不是通用的。對一個Foo類來說,其Class對象是Foo.class。我不認為Foo可以被它自己所使用,因為它命名了一個類型,Java在處理類型和值的時候有一些微妙的區別。




Python

中的類是一個對象,是類型的一個實例(即類本身是一個對象,也是其自身的一個實例,這樣想的話還是挺有趣。)這樣的話類就可以被當成任何值來對待:可以當成參數傳遞,保存在更大的結構中,檢查和操作。把類作為字典的值有時候特別有用。由於類是其本身實例化後的對象(Python沒有new關鍵字),在很多場合類可以和一些函數通用。一些常用的模式類似工廠模式是如此簡單以至於都快要消失了。




最近我好幾次把函數和普通代碼放在任何類外的頂層。這也是允許的。但其含義可能會有些微妙。在Python中,甚至class和def這樣的表達式都只是運行時執行的普通代碼。Python代碼從上往下執行,class和def也不例外。他們只不過是創建類和函數這兩種對象的語法罷了。




這才是最酷的部分。由於類是對象,他們的類型是type,因此你可以子類化然後修改它運行的方式。然後你可以使用你子類的實例來創建類。


第一次接觸這些可能會有點奇怪。但是再一次說明,你不需要弄清它是怎麼運行的,直接利用它就可以了。例如,Pyhton沒有enum塊,但它有enum模塊

:




Class

語句創建了一個對象,這意味著它調用了某處的構造函數,而這個創建函數可以用來修改類創建的方式。這裡,Enum創建了一個固定數量的類的集合而不是類屬性。所有這些都是用純Python代碼和普通Python語法實現的。


所有的庫都是基於以上思路構建的。你厭倦了單調的使用self.foo = foo 這樣的方式在構造函數中為每個屬性複製了么?然後純手工定義比較、哈希、克隆和開發者可讀的列表?Java可能需要編譯器支持,可能來自於

Amber項目

。Python則足夠靈活,所以在社區中使用attrs庫解決了以上問題。




或者以

SQLAlchemy

為例,這是使用Python寫的一個功能強大的資料庫封裝。它包含了一個靈感來自於Java的

Hibernate

庫的ROM,但你可以直接在類里編寫映射,而不需要在配置文件里定義表結構或者通過其他冗長的註解來實現。




這和Enum都基於同樣的思路,但SQLAlchemy也使用了property類似的鉤子,所以你可以很自然的修改列的值。




最後,類本身可以在運行過程中創建。這有點好處,但thriftpy創建了整個module,其中都是基於 Thrift定義文件的類。在Java中,你需要代碼生成,這增加了一個全新的編譯步驟有可能會引發不同步。




所有的這些例子都依賴於Pyhton已有的語法,但又賦予其新的含義。所有這些也都不是Java或者其他語言所不能實現的,但Python減少了結構上的重複,這也使得代碼更容易讀寫,並減少bug的產生。




回顧




Pyhton

有很多和Java類似的基礎概念,但把它們運用在完全不同的方向,同時也新增了很多思路。Java更關注穩定和可靠性,而Python則更關注表達性和靈活性。它們是命令式編程兩種完全不同的方向。




對你來說我不太確定Python將來會不會在Java現在還領先的領域取而代之。例如,Python可能會在任何速度方面的測試中失敗(但是可以了解下PyPy,一種即時編譯的Python)。Java原生支持線程,而Python社區大都迴避了這些問題。規模較大且複雜程度較高的軟體更喜歡聲明類型並提供合理性檢查的靜態語言(但可以看一下 mypy,Python的一個靜態類型檢查器。)




但也許Python將會在Java暫時不太擅長的領域繼續發光發熱。很多軟體並不需要特別快或者並行執行,而更關注其他方面的效率。我發現使用Pyhton可以很快很容易的啟動一個項目。沒有單獨的編譯步驟,Python的寫完馬上運行的方式要快很多。Python的代碼更短,這經常意味著更易讀。嘗試不同的架構成本也更低。有時候嘗試一些比較愚蠢的主意會比較有趣,

使用庫實現goto功能


我希望你可以試試Python。使用Python非常有趣,我覺得你也會有同感。只要你不把它當成隱藏了所有類型的Java。




最壞的情況,Python還有

Pyjnius

模塊,它會讓你這樣使用Python。





英文原文:https://www.sitepoint.com/python-for-java-people/


譯者:mrwoody



喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python 的精彩文章:

代碼這樣寫不止於優雅(Python 版)
Instagram是如何利用10個月時間順利遷移到Python 3
Python零基礎可以在線學習半年找到高薪工作嗎?
使用Python檢測並繞過Web應用程序防火牆
2017年度15個最好的數據科學領域Python庫

TAG:Python |

您可能感興趣

Python開發者
Facebook:一批開發者已經拿到了Santa Cruz開發機
JetBrains殺入Python開發,發布全新機器學習Web方案!
Python開發
Python web開發:Flask的項目配置
Python web開發:Flask的URL和視圖
Mozilla發布Firefox Reality WebVR開發者指南
Oculus開始向開發者提供VR一體機Santa Cruz
Facebook表示已經有開發者收到Santa Cruz開發機
亞馬遜為開發者推出Alexa Gadgets Toolkit
Mozilla推出開源瀏覽器Firefox Reality,專為VR一體機開發
Mozilla 推出開源瀏覽器 Firefox Reality,專為 VR 一體機和 AR 頭顯開發
Magic Leap開發者大會開放申請;Adobe Captivate
Android之父Essential Phone將停止開發,並出售其公司
如何在Windows下開發Python:在cmd下運行Python腳本
Windows MR頭顯開發入門
谷歌開發者節DevFest&TensorFlowDay,約!
適用於Android和iPhone的Swype鍵盤停止開發
谷歌Shahriar Rabii正式入職Facebook,統領晶元開發部門
引入Skaffold:簡單且可重複的Kubernetes開發