學習一門編程語言,通常是學習該語言的以下幾個部分的內(nèi)容:
基礎(chǔ)語法:如,變量的聲明與調(diào)用、基本輸出語句、代碼塊語法、注釋等;
數(shù)據(jù)類型:通常都為 數(shù)字、字符串、布爾值、數(shù)組、鏈表、Map、Set等;
運算符:算術(shù)運算符、賦值運算符、比較運算符、邏輯運算符、位運算符等;
流程控制語句:分支語句、循環(huán)語句;
類的定義與使用:這個是面向?qū)ο缶幊陶Z言才有的內(nèi)容;
常用API的使用:常用方法、工具類或模塊的使用。
掌握上面的內(nèi)容,就算是對一門編程語言入門了,剩下的就是不斷的在使用和總結(jié)中去提升了。本節(jié)我們先來說一說學習Python時的準備工作以及Python的基礎(chǔ)語法。
Hello, World!
Python2.x or Python3.x
變量與常量
接收用戶輸入
指定字符編碼與解釋器
注釋
導入模塊
獲取腳本傳入的參數(shù)
代碼塊語法
Python程序執(zhí)行過程與.pyc文件
在任意一個目錄下創(chuàng)建一個hello.py文件,我們來輸出一個經(jīng)典語句"Hello World!"
print("Hello, World")
執(zhí)行hello.py這個python腳本的方式是:
python hello.py
輸出為:
Hello, World
當然也可以直接進入python解釋器交互式終端去執(zhí)行print("Hello, World"),如下圖所示:
思考:為什么打印一句“Hello World”已經(jīng)成為很多人學習一門新語言的第一句代碼?
有人說,這是學習一門語言入門的象征,因為寫完了這個我就可以對別人說“我會寫xx語言的程序了”。本人認為要理解為什么Hello World如此簡單,卻又如此廣為人知并被傳頌,只需要想清楚一個問題:這個程序帶給我們什么?
它告訴我們在這個編程語言中基本的輸出語句是怎樣的,這很重要;
它告訴我們要怎樣去執(zhí)行這個編程語言編寫的程序,這同樣很重要。
如果不知道上述兩個問題,我們將寸步難行。
我們分別使用Python2.7 和 Python3.5的解釋器提供的交互式終端來分別執(zhí)行以下兩條指令:
print("Hello, World")print "Hello, World"
通過上圖的執(zhí)行結(jié)果會發(fā)現(xiàn),print "Hello, World"
這條語句在Python2.7中可以正常執(zhí)行,而在Python 3.5中會報錯,也就是說Python 3.x與Python 2.x是不兼容的。這貌似是Python開發(fā)者犯的一個錯誤,而事實是Guido Van Rossum(Python語言的最初創(chuàng)建者)故意為之。Guido的本意就是想不考慮太多向后兼容性的問題,去適當?shù)厍謇硪幌翽ython 2.x中不合理的內(nèi)容,而不是把Python 3.x簡單的當做對Python 2.x的更新版本。
實際上,Python 3.0在2008年12月就已經(jīng)發(fā)布了,Python官方在2010年年中發(fā)布2.7時宣布,2.7將是Python 2.x的最后一個主發(fā)布版本。其實Python 2.7 是向Python 3.x的一個過渡版本,里面支持了一些Python 3.x的特性。
2014年11月,Python官方宣布Python 2.7將會被支持到2020年,并再次確認了不會有Python 2.8發(fā)布,希望用戶盡快遷移到Python 3.4+ 。3.x正在處于積極開發(fā)狀態(tài),并且在過去5年里已經(jīng)發(fā)布了多個穩(wěn)定版本,包括2012年的3.3,2014年的3.4,2015年的3.5。這意味著最近所有的標準庫更新將默認只能在Python 3.x中可用。
在Python 3.x中,輸出語句需要使用print()函數(shù),該函數(shù)接收一個關(guān)鍵字參數(shù),以此來代替Python 2.x中的大部分特殊語法。下面是幾個對比項:
目標 | Python 2.x的實現(xiàn) | Python 3.x的實現(xiàn) |
---|---|---|
拼接并輸出多個值 | print "The result is", 2+3 | print("The result is", 2+3) |
打印一個元祖(1,2,3) | print(1,2,3) 或 print (1,2,3) | print((1,2,3)) |
輸出一個內(nèi)容并且不換行 | print “Hello”, | print("Hello", end=" ") |
輸出一個新空白行 | print() | |
將輸出內(nèi)容輸出到標準錯誤輸出文件 | print >>sys.stderr, "fatal error" | print("fatal error", file=sys.stderr) |
自定義多個輸出內(nèi)容之間的分隔/拼接符 | N/A | print("There are <", 2**32, "> possibilites!", sep="") |
Python 2.x中使用的默認字符編碼為ASCII碼,要使用中文字符的話需要指定使用的字符編碼,如UTF-8;Python 3.x中使用的默認字符編碼為Unicode,就不存在這個問題了。
python 2.x中如果要給多個變量同時賦值,要求=號右邊的表達式返回結(jié)果的個數(shù)要與=號左邊接收值的變量個數(shù)相等,不能多,也不能少。如:
a,b,c = (1,2,3) # 正常,a=1, b=2, c=3a,b,c = range(5) # 報錯,ValueError: too many values to unpacka,b,c,d,e = [1,2,3] # 報錯,ValueError: need more than 3 values to unpack
python 3.x中允許=號昨邊的變量數(shù)小于=號右邊表達式返回的結(jié)果的個數(shù),但是需要有1個且只能有1個字典類型的變量來接收多余的返回值。與python 2.x相同的是 python 3.x中=號左邊的變量數(shù)也是不能多與=號右邊表達式的返回值個數(shù),但是錯誤提示語更清晰了。
a,b,c = (1,2,3) # 正常,a=1, b=2, c=3a,*b,c = range(5) # 正常,a=0, b=[1,2,3], c=4a,b,c,d,e = [1,2,3] # 報錯,ValueError: not enough values to unpack (expected 5, got 3)
Old Name | New Name |
---|---|
_winreg | winreg |
ConfigParser | configparser |
copy_reg | copyreg |
Queue | queue |
SocketServer | socketserver |
markupbase | _markupbase |
repr | reprlib |
test.test_support | test.support |
如果是要開發(fā)一個新項目,不用考慮與老項目的兼容問題,最好是使用Python 3,因為就像Python官方說的那樣,Python 3才是Python語言的將來?,F(xiàn)在很多第三方類庫已經(jīng)完成了或者正在積極完成對Python 3的支持,只是有些項目由于過于龐大,很難在短時間內(nèi)完成。我們需要考慮的最大問題在于,新項目中是否存在必須的第三方類庫,且該類庫當前還不支持Python 3。如果不存在這個問題,那堅定的選擇Python 3吧。
這是只是簡單說下Python中變量的定義和使用,方便繼續(xù)下面的內(nèi)容。事實上,Python中變量的使用確實很簡單:
name = "wader"age =28print("Name: ", name,) # Name: waderprint("Age : ", age) # Age : 28
python定義變量無需指定變量類型,python解釋器會在運行時自動推斷變量的數(shù)據(jù)類型。我們可以通過type()方法來查看變量類型:
type(name) # strtype(age) # int
事實上,Python中沒有語法約束下的常量,僅僅是用完全大寫字母的變量來表示這個變量不應該被改變。
COUNT = 10
很多時候都需要與用戶進行交互,通過用戶輸入的內(nèi)容來做下一步操作。這里需要說明的是,Python 2 與Python 3中接收用戶輸入的方法是不一樣的。
Python 2中接收用戶輸入時,主要使用的是raw_input()函數(shù):
name = raw_input("Enter your name: ")print "Your name is ", name
Python 3中接收用戶輸入時,主要使用的是input()函數(shù):
name = input("Enter your name: ")print("Your name is ", name)
通過Python 2中的raw_input() 與 Python 3中的input() 獲取到的值都是str類型,若想轉(zhuǎn)換為其他數(shù)據(jù)類型需要進行強制類型轉(zhuǎn)換,這個等將Python數(shù)據(jù)類型的時候會說。
Python 2中其實也有input()方法,但是通過這個input()方法獲取的值是與輸入內(nèi)容的數(shù)據(jù)類型有關(guān)的,這很容易造成混亂,因此現(xiàn)在Python 2中很少用這個方法,而是用raw_input()代替了。
計算機只認識0和1組成的二進制序列,因此任何文件中的內(nèi)容要想被計算機識別或者想存儲在計算機上都需要轉(zhuǎn)換為二進制序列。那么字符與二進制序列怎么進行想換轉(zhuǎn)換呢?于是人們嘗試建立一個表格來存儲一個字符與一個二進制序列的對應關(guān)系。
編碼 將字符轉(zhuǎn)換為對應的二進制序列的過程叫做字符編碼
解碼 將二進制序列轉(zhuǎn)換為對應的字符的過程叫做字符解碼
最早建立這個字符與十進制數(shù)字對應的關(guān)系的是美國,這張表被稱為ASCII碼(American Standard Code for Information Interface, 美國標準信息交換代碼)。ASCII碼是基于拉丁字母的一套電腦編程系統(tǒng),主要用于顯示現(xiàn)代英語和其他西歐語言。它被設計為用1個字節(jié)來表示一個字符,所以ASCII碼表最多只能表示2**8=256個字符。實際上ASCII碼表中只有128個字符,剩余的128個字符是預留擴展用的。
隨著計算機的普及和發(fā)展,很過國家都開始使用計算機。大家發(fā)現(xiàn)ASCII碼預留的128個位置根本無法存儲自己國家的文字和字符,因此各個國家開始制定各自的字符編碼表,其中中國的的字符編碼表有GB2312和GBK。
后來隨著世界互聯(lián)網(wǎng)的形成和發(fā)展,各國的人們開始有了互相交流的需要。但是這個時候就存在一個問題,每個國家所使用的字符編碼表都是不同的。比如我們發(fā)送一句“你好,我好喜歡你演的愛情動作電影!”給島國的倉老師,蒼老師電腦上用的是日本的字符編碼表,因此她的電腦無法正確顯示我們發(fā)送的內(nèi)容。這個時候,人們希望有一個世界統(tǒng)一的字符編碼表來存放所有國家所使用的文字和符號,這就是Unicode。Unicode又被稱為 統(tǒng)一碼、萬國碼、單一碼,它是為了解決傳統(tǒng)的字符編碼方案的局限性而產(chǎn)生的,它為每種語言中的每個字符設定了統(tǒng)一并且為之一的二進制編碼。Unicode規(guī)定所有的字符和符號最少由2個字節(jié)(16位)來表示,所以Unicode碼可以表示的最少字符個數(shù)為2**16=65536。
為什么已經(jīng)有了Unicode還要UTF-8呢?這是由于當時存儲設備是非常昂貴的,而Unicode中規(guī)定所有字符最少要由2個字節(jié)表示。人們認為像原來ASCII碼中的字符用1個字節(jié)就可以了,因此人們決定創(chuàng)建一個新的字符編碼來節(jié)省存儲空間。UTF-8是對Unicode編碼的壓縮和優(yōu)化,它不在要求最少使用2個字節(jié),而是將所有字符和符號進行分類:
ascii碼中的內(nèi)容用1個字節(jié)保存
歐洲的字符用2個字節(jié)保存
東亞的字符用3個字節(jié)保存
...
UTF-8是目前最常用,也是被推薦使用的字符編碼。
我們上面提到過,一般在兩個地方會用到字符編碼:
磁盤寫入或讀取數(shù)據(jù)時;
程序執(zhí)行時的輸入和輸出;
磁盤寫入或讀取數(shù)據(jù)時使用的字符編碼是由編輯器指定的工程或文件的字符編碼決定的,這與Python解釋器是無關(guān)的;但是Python程序執(zhí)行時,將Python腳本文件加載到內(nèi)存時所使用的字符編碼是主要問題所在。在Python 2中,Python解釋器默認使用的是ASCII碼,此時如果要運行的程序中如果有中文Python解釋器就會報錯。
print("你好,世界")
SyntaxError: Non-ASCII character '\xe4' in file C:/Users/wader/PycharmProjects/LearnPython/day01/code.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
這是因為Python解釋器執(zhí)行該程序時試圖從ASCII編碼表中查找中文字符對應的二進制序列,但是發(fā)現(xiàn)找不到。此時要想該程序正常運行,就需要在python腳本文件的開始位置聲明該文件的所使用的字符編碼:
# -*- coding:utf-8 -*-print("你好,世界")
需要說明的是:
Python 3的解釋器默認使用Unicode編碼,它本身是可以對中文字符進行編碼和解碼的,所以即便不指定字符編碼也能正常運行,但是還是建議保留字符編碼的聲明。
通常python腳本都是跑在Linux上的,為了讓python腳本文件可以像shell腳本那樣可以直接調(diào)用執(zhí)行,我們通常需要在python文件最開始的位置指定python解釋器:
#!/usr/bin/env python# -*- coding:utf-8 -*-print("你好,世界")
不建議寫python解釋器的絕對路徑,如:
#!/usr/bin/python# -*- coding:utf-8 -*-print("你好,世界")
因為這樣寫的話,將來要想更換python解釋器是非常麻煩的。
關(guān)于注釋,有兩個原則:
不寫沒必要的主要:多余的注釋只會讓代碼閱讀者看著更亂,且容易分神
錯誤的注釋不如沒有注釋:更改代碼后,首先要做的就是更改注釋
塊注釋,顧名思義,應該是對一個代碼塊的注釋。顯然,對某個代碼塊的注釋信息應該寫在這個代碼塊的前面,并且縮進到與該代碼塊相同的級別。塊注釋的每一行都要以#號加上單個空格開始(注釋中的縮進文本除外):
# 計算變量a與變量b的和# 然后打印計算值a = 10b = 20sum = a + bprint("sum: %d" % sum)
說明: Python中的單行注意與多行注意都是以# 號來標識的。如果注釋信息只有一行,則為單行注釋;如果注釋信息有多行,則為多行注釋。另外如果多行注釋中有多個段落,則段落之間可以以一個#加單個空格的空注釋行隔開。
如果要注釋的代碼塊只有一行代碼,且注釋信息也很短,也可以把直接注釋要寫在代碼的后面,這就是 行內(nèi)注釋 。行內(nèi)注釋要求代碼與#號之間至少要有2個空格,同時#號與注釋內(nèi)容之間至少要有1個空格。
print("你好,世界") # 打印一行文本
另外,行內(nèi)注釋并不被推薦使用。
文檔字符串通常用來為某個模塊、函數(shù)、類或方法提供比注釋更詳細的使用說明、注意事項、使用用例等幫助信息。文檔字符串以三個引號(單引號和雙引號都可以,通常都使用雙引號)將字符串包起來。由于文檔字符串表現(xiàn)形式類似于Python的多行字符串,因此很多人把它當做Python中的多行注釋來用。
PEP 276 中對“什么是好的文檔字符串的書寫格式”進行了一些定義:
應該為公共模塊、函數(shù)、類和方法編寫文檔字符串。非公共方法需需要docstring,但是應該有一個描述該方法的注釋,且該注釋信息應該出現(xiàn)在def行的行末。
模塊的文檔字符串應該寫在“字符編碼的聲明”與“模塊導入”語句之間;函數(shù)與方法的文檔字符串應該寫在def語句行與函數(shù)體或方法體正式代碼之間;類的文檔字符串應該寫在class語句行與該類的第一個方法定義之間。
如果文檔字符串有多行,那么結(jié)尾的三個引號應該在一個單獨的行。
如果文檔字符串只有一行,那么結(jié)尾的3個引號應該與開始的3個引號以及文檔字符串在同一行。
來看下Python 3中一些內(nèi)置函數(shù)的文檔字符串實例:
def print(self, *args, sep=' ', end='\n', file=None): # known special case of print""" print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False) Prints the values to a stream, or to sys.stdout by default. Optional keyword arguments: file: a file-like object (stream); defaults to the current sys.stdout. sep: string inserted between values, default a space. end: string appended after the last value, default a newline. flush: whether to forcibly flush the stream. """passdef ord(*args, **kwargs): # real signature unknown""" Return the Unicode code point for a one-character string. """passdef exit(*args, **kwargs): # real signature unknownpass
當Python內(nèi)置的核心模塊提供的功能無法滿足我們的需求時就需要導入外部模塊,而導入模塊的功能有兩種方式:
import MODULE :導入整個模塊
from MODULE import XX :導入模塊中的一部分(方法、變量、或常量等)
例如,要想查看或更改python查找模塊的路徑列表就需要使用sys模塊下的path變量;若需要執(zhí)行系統(tǒng)命令可以使用os模塊下的system()方法。
import sysfrom os import systemprint(sys.path)print(system("ping www.baidu.com"))
我們在寫shell腳本時,經(jīng)常會通過接受執(zhí)行腳本時傳入的變量來做相應的操作,來保證腳本的靈活性。比如我們要寫一個腳本來調(diào)用ping命令對指定的域名進行ping測試,這時候顯然將域名當做參數(shù)傳遞給腳本要比把域名寫死在腳本中靈活的多。shell中可以只用$1,$2這樣的特殊變量來獲取傳入的參數(shù),而python中需要用sys模塊下的argv變量來獲取。
sys.argv是一個列表,與shell相同,其第一個元素是當前腳本的名稱,之后才是傳入的參數(shù)。
編寫一個ping.py,內(nèi)容如下:
#!/usr/bin/env python# -*- coding:utf-8 -*-import sysimport osprint(type(sys.argv))print(sys.argv)
執(zhí)行該腳本,結(jié)果如下圖所示:
在Java和C語言中用花括號{}包起來的部分就是一個代碼塊,shell腳本中的代碼塊是由專門的開始和結(jié)束標識的,而python中的代碼塊是靠“縮進對齊”來表示的。下面我們分別一個if-else的條件判斷來對這幾個語言的代碼塊表示方式做一個對比:
...int a = 3;int b = 5;int big_num;if(a > b){ big_num = a;}else{ big_num = b;}System.out.println(big_num)...
declare -i a=3declare -i b=5declare -i big_numif [ $a -gt $b ];thenbig_num=$aelsebig_num=$bfiecho $big_num
a = 3b = 5if a > b: big_num = aelse:big_num = bprint(big_num)
在之前的文章我們已經(jīng)解釋過:Python是一個動態(tài)的、強類型的、解釋型的編程語言。而實際上,解釋型語言與編譯型語言的界限正在變得模糊。包括Python在內(nèi)的很多高級編程語言,會將源代碼先編譯成特定類型的中間代碼,然后再由解釋器去執(zhí)行,這樣可以提高執(zhí)行效率。Python的解釋器同時也是生成Python中間代碼的編譯器,.pyc文件就是存放Python中間代碼的文件。執(zhí)行Python代碼時,如果該源碼文件導入了其他的.py文件,那么執(zhí)行過程中會自動生成一個與導入的.py文件同名的.pyc文件。