不想再像之前那樣,什麼都從頭開始學習語法、總結語法,這樣反而會過度糾結於語法,耽誤了開發,畢竟語言的主要屬性是工具,次要的屬性是語言自己。html
因此仍是先熟練使用語言去進行開發,等足夠熟悉了,再去研究語言自己(編譯原理……)。python
另外對於算法、設計模式、數據結構、網絡知識、操做系統…… 的學習能夠專門針對性的學習,固然更好的方法是以使用語言爲主線,經過具體的應用和實踐來推進這些技術知識的學習。算法
本文是經過廖雪峯的網站學習而整理的(真的是很好的教程,免得我花錢買書了!),而後我沒有去再整理總結語法,而是直接經過寫出代碼段來體現本身的學習,也方便之後的快速複習、回顧。畢竟學習一門語言不是一天能夠完成的,因此本文也不是一蹴而就的,而是會一直更新。編程
也沒有必要再對代碼作過多的文字解釋,一切都能經過代碼自己體現。windows
在Python的交互式命令行寫程序,好比>>>print('hello world')
,好處是一下就能獲得結果,壞處是無法保存,下次還想運行的時候,還要再敲一遍。設計模式
因此實際的開發中,咱們使用一個文本編輯器來寫代碼,而後保存爲一個文件,這樣程序就能夠反覆運行了。好比將print('heool world')
寫在文檔裏注意print前面不能有任何空格,並保存爲 hello.py,而後使用命令行進入該文件所在的目錄,執行python hello.py
就能夠解析執行這個源文件了。數組
絕對不要使用windows自帶的記事本:記事本會自做聰明地在文件開始的地方加上幾個特殊字符(UTF-8 BOM),結果會致使程序運行出現莫名其妙的錯誤。安全
能不能像.exe文件那樣直接運行.py文件呢?在Windows上是不行的,可是,在Mac和Linux上是能夠的,方法是在.py文件(好比是hello.py)的第一行加上一個特殊的註釋:網絡
#!/usr/bin/env python3 print('hello, world')
接着,經過命令行給hello.py以執行權限:chmod a+x hello.py
,而後就能夠在文件所在目錄下直接輸入./hello.py
運行。數據結構
這個和Shell有些像!
>>>print('測試一個運算式', '1+2=', 1+2) 測試一個運算式 1+2= 3
注意遇到print裏面將參數分開的逗號會輸出空格。
>>>name = input() xumenger
當你輸入完name = input()
並按下回車後,Python交互式命令行就等待你的輸入,以這個例子,輸入xumenger,而後回車完成輸入,這時候輸入的字符串就存入到name
變量裏了。
編寫一個test.py文件
name = input('請你輸入你的姓名: ') print('hello', name)
input裏面的字符串參數做爲輸出提示用,而後等待輸入,輸入的字符串(好比xumenger)存入name,而後再輸出 hello xumenger
。
input()
和print()
是在命令行下面最基本的輸入和輸出,可是,用戶也能夠經過其餘更高級的圖形界面完成輸入和輸出,好比,在網頁上的一個文本框輸入本身的名字,點擊「肯定」後在網頁上看到輸出信息。
2015.09.06 23:40,明天開始學習Python基礎,先去睡覺!
Python語法簡單,採用縮進來控制邏輯。沒有規定是幾個空格仍是Tab,可是按照約定俗成的管理,應該始終堅持使用4個空格的縮進。在文本編輯器中,須要設置把Tab自動轉換爲4個空格,確保不混用Tab和空格。
# print absolute value of an integer: a = 100 if a >= 0: print(a) else: print(-a)
以#
開頭的語句是註釋,當語句以:
結尾時,縮進的語句視爲代碼塊。
整數、浮點數
整數和浮點數在計算機內部存儲的方式是不一樣的,整數運算永遠是精確的(除法難道也是精確的?是的!),而浮點數運算則可能會有四捨五入的偏差。
字符串(使用單引號或者雙引號引發來)
若是字符串內部包含'
又包含"
怎麼辦,須要用\
來轉義
print('I\'m \"OK\"!')
表示:I'm "OK"!
判斷
if age==18 print('值爲18') else print('值不是18')
布爾值(True、False,用and、or和not運算)
空值
用 None
表示,None不能理解爲0,由於0是有意義的,而None是一個特殊的空值。
變量
=
是賦值號,另外Python是動態語言,變量自己類型不固定。與之對應的是靜態語言,靜態語言在定義變量的時候必須指定變量的類型,若是賦值的時候類型不匹配,就會報錯,像C、C++、Java。
a='ABC'
Python解釋器解釋它時,,幹了兩件事
在內存中建立一個'ABC'
的字符串
在內存中建立了一個名爲a
的變量,並將它指向'ABC'
也能夠把一個變量a賦值給另外一個變量b,這個操做其實是把變量b指向變量a所指向的數據,例以下面的代碼:
a = 'ABC' b = a a = 'XYZ' print(b)
最後一行打印出變量b的內容究竟是'ABC'呢仍是'XYZ'?若是從數學意義上理解,就會錯誤地得出b和a相同,也應該是'XYZ',但實際上b的值是'ABC'。一步一步理解代碼
在內存中建立 'ABC' 字符串
在內存中建立 a 變量,並將 a 指向 'ABC'
在內存中建立 b 變量,由於將 a 值賦給 b,因此這是b也指向 'ABC'
而後又在內存中建立了 'XYZ' 字符串,並將 a 指向 'XYZ',可是此時b 仍是指向 'ABC'
10/3
獲得的結果是 3.33333333333339/3
獲得的結果是 3.010//3
獲得的結果是 310%3
獲得的結果是 1
這個知識點之前我一直存在疑惑,廖雪峯的教程裏面講得仍是很好的,點擊這裏認真看。重點是字符編碼的原理、如今計算機系統通用的字符編碼工做方式、Python中的編碼方式、亂碼的解釋。
在最新的Python 3版本中,字符串是以 Unicode編碼的,也就是說Python的字符串支持多種語言。
關於Python的字符串類型和bytes類型、在網絡傳輸或者保存到磁盤時候字符串類型與bytes類型的轉換,參考對應的廖雪峯的 字符串和編碼 教程,有詳細的講解。
1箇中文字符通過UTF-8編碼後一般會佔用3個字節,而1個英文字符只佔用1個字節。
print('Hi, %s, you have $%d.' % ('loser', 10))
輸出是:Hi, loser, you have $10
print('Age: %s, Gender: %s' % (25, True))
輸出是:Age: 25, Gender: True
list是一種有序的集合用[]
,能夠隨時添加和刪除其中的元素
mates = ['Machael', 'Bob', 'Tracy'] print('%s' % (len(mates)) print(mates[0]) #第一個元素 print(mates[3]) #越界報錯 print(mates[-1]) #倒數第一個元素 print(mates[-2]) #倒數第二個元素 print(mates[-4]) #越界報錯 mates.append('xumenger') #list是可變列表,如今是在list末尾追加元素 mates.insert(1,'Joker') #插入一個元素在第二個位置 mates.pop() #刪除末尾的元素 mates.pop(1) #刪除第二個元素 mates[1] = 'change' #替換第2個元素 L= ['test', 123, True] #list裏面能夠是不一樣類型的元素 s= ['test', 1, ['asp', 2], True] #list的元素能夠是另外一個list list1 = ['test', True] list2 = [2, False, list1] print(list2[2][0]) #能夠當作是一個二維數組,相似的還有三維、四維……數組,不過不多用到。
tuple和list很類似,可是tuple是一旦初始化就不能再修改的,用()
mates= ('xumeng', 'joker', 'test')
如今,mates這個tuple不能變了,它也沒有append(),insert()這樣的方法。其餘獲取元素的方法和list是同樣的,你能夠正常地使用mates[0],mates[-1],但不能賦值成另外的元素。
不可變的tuple有什麼意義?由於tuple不可變,因此代碼更安全。若是可能,能用tuple代替list就儘可能用tuple。
t=(1)
定義的不是tuple,是1這個數!這是由於括號()既能夠表示tuple,又能夠表示數學公式中的小括號,這就產生了歧義,所以,Python規定,這種狀況下,按小括號進行計算,計算結果天然是1。
要想定義只有一個元素的tuple,應該這樣t=(1,)
「可變的tuple」,能夠是tuple有一個list元素,而後裏面的list可變,能夠看教程中 對應部分 經過展現內存中的存儲原理來講明緣由:
t = ('a', 'b', ['A', 'B']) t[2][0] = 'X'
a = input('輸入你的年齡: ') age = int(a) #由於input輸入的是字符串,因此要轉換成整型 if age >= 18: #注意冒號 : print('adult, your age is', age) elif age >= 6: #注意冒號 : print('teenager, your age is', age) else: #注意冒號 : print('kid')
names = ['nn', 'aa', 'mm']
for name in names: #注意冒號 :
print(name)
sum = 0
for x in[1, 2, 3, 4, 5]
sum = sum +x
print(sum)
sum = 0
for x in range(101): #range(101)就能夠生成0-100的整數序列
sum = sum + x
print(sum)
sum = 0
n = 99
while n>0: #注意冒號:
sum = sum +n n= n-2
print(sum)
Python內置了字典:dict的支持,全稱爲dictionary,在其餘語言中也成爲map,使用鍵-值(key-value)存儲,具備幾塊的查找速度,注意使用{}
。
d = {'key1': 95, 'key2': 54, 'key3': 100} print(d['key1']) #把數據放入dict的方法,除了初始化時指定外,還能夠經過key放入 d['key4'] = 123 #因爲一個key只能對應一個value,因此,屢次對一個key放入value,後面的值會把前面的值沖掉 d['key1'] = 111 if 'key2' in d: #判斷某個鍵是否是在dict中 print('在') if get('key2', -1) == -1: #若是有'key2'則獲取對應的value,不然就返回-1 print('不在') d.pop('key3') #使用pop刪除對應的鍵和值
爲何dict查找速度這麼快?由於dict的實現原理和查字典是同樣的。假設字典包含了1萬個漢字,咱們要查某一個字,一個辦法是把字典從第一頁日後翻,直到找到咱們想要的字爲止,這種方法就是在list中查找元素的方法,list越大,查找越慢。
第二種方法是先在字典的索引表裏(好比部首表)查這個字對應的頁碼,而後直接翻到該頁,找到這個字。不管找哪一個字,這種查找速度都很是快,不會隨着字典大小的增長而變慢。
你能夠猜到,這種key-value存儲方式,在放進去的時候,必須根據key算出value的存放位置,這樣,取的時候才能根據key直接拿到value。
請務必注意,dict內部存放的順序和key放入的順序是沒有關係的。
dict能夠用在須要高速查找的不少地方,在Python代碼中幾乎無處不在,正確使用dict很是重要,須要牢記的第一條就是dict的key必須是不可變對象。
這是由於dict根據key來計算value的存儲位置,若是每次計算相同的key得出的結果不一樣,那dict內部就徹底混亂了。這個經過key計算位置的算法稱爲哈希算法(Hash)。
要保證hash的正確性,做爲key的對象就不能變。在Python中,字符串、整數等都是不可變的,所以,能夠放心地做爲key。而list是可變的,就不能做爲key。
set和dict相似,也是一組key的集合,可是不存儲value,因爲key不能重複,因此在set中,沒有重複的key。
#要建立一個set,須要提供一個list做爲輸入結合 s1 = set([1, 2, 3]) #重複元素在set中被過濾,好比下面的語句,其實只要1,2,3 s= set([1, 1, 2, 2, 2, 3, 3]) #經過add(key)添加元素,重複添加的元素只會保留一個 s.add(4) #remove(key) 刪除元素 s.remove(1,2,3) #set能夠當作數學意義上的無序和無重複元素的集合,所以,兩個set能夠作數學意義上的交集、並集等操做 s2 = set([1,2,3]) s3 = set([2,3,4]) s4 = s2 & s3 #s4是s2和s3的交集 s5 = s2 | s3 #s4是s2和s3的並集
set和dict的惟一區別僅在於沒有存儲對應的value,可是,set的原理和dict同樣,因此,一樣不能夠放入可變對象,由於沒法判斷兩個可變對象是否相等,也就沒法保證set內部「不會有重複元素」。試試把list放入set,看看是否會報錯。
廖雪峯的 講解dict和set的文章 的最後經過說明內存裏面的原理講解了可變對象與不可變對象!很好的理解Python和內存機制的一個知識點!
tuple雖然是不變對象,但試試把(1, 2, 3)和(1, [2, 3])放入dict或set中,並解釋結果。
s1 = set([(1,2,3), (2,3,4)]) #這樣的tuple能夠放進set s2 = set([(1,2, [1,2]), (2,3, [6,8])]) #這樣的tuple不能放進set,這是「可變的tuple」
2015.09.07 23:45, 明天開始學習 Python的函數 ,如今趕忙睡覺,身體最重要!
要調用一個函數,須要知道函數的名稱和參數,好比求絕對值的函數abs,只有一個參數。能夠直接從Python的官方網站查看文檔:http://docs.python.org/3/library/functions.html#abs
也能夠在交互式命令行經過help(abs)查看abs函數的幫助信息。
print(abs(-1)) print(max(1,2,3,4)) print(max(1,2,3)) print(int('123')) #強制類型轉換 print(float('12.34)) print(str(100)) print(bool(1)) #輸出True print(bool('')) #輸出False
函數名實際上是一個指向函數對象的引用,徹底能夠把一個函數名賦值給一個變量,至關於給這個函數起了一個別名:
a=abs print(a(-1)) m=max print(max(1,2,3))
在Python中,定義一個函數要使用def
語句,依次寫出函數名、括號、括號裏的參數和冒號: ,而後,在縮進塊中編寫函數體,函數的返回值用return語句返回。
def my_abs(x): if not isinstance(x, (int, float)): raise TypeError('bad operand type') if x>=0: return x else: return -x
上面的函數中,對參數類型作檢查,只容許整數和浮點數類型的參數,不然就raise一個異常。若是有必要,能夠先對參數的數據類型作檢查,就像這個函數定義,就能夠保證只處理int和float,而假如傳入的是str就會拋出異常。
請注意,函數體內部的語句在執行時,一旦執行到return時,函數就執行完畢,並將結果返回。所以,函數內部經過條件判斷和循環能夠實現很是複雜的邏輯。
若是沒有return語句,函數執行完畢後也會返回結果,只是結果爲None。return None能夠簡寫爲return。
import math def move(x, y, step, angle=0): nx = x + step * math.cos(angle) ny = y - step * math.sin(angle) return nx, ny
import math語句表示導入math包,並容許後續代碼引用math包裏的sin、cos等函數。上面的這個函數有兩個返回值,咱們能夠這樣調用
x, y = move(100, 100, 60, math, pi/6) print(x, y)
可是,其實這個只是假象,Python函數返回的仍然是單一值:
r = move(100, 100, 60, math.pi / 6) print(r) #獲得和上面同樣的結果
原來返回值是一個tuple!可是,在語法上,返回一個tuple能夠省略括號,而多個變量能夠同時接收一個tuple,按位置賦給對應的值,因此,Python的函數返回多值其實就是返回一個tuple,但寫起來更方便。
廖雪峯的函數的參數 這一章講解了位置參數、默認參數、由於參數類型不是不變對象致使使用默認參數出現的"意外"、list和tuple與可變參數、dict與關鍵字參數、命名關鍵參數。
在Python中定義函數,能夠用必選參數、默認參數、可變參數、關鍵字參數和命名關鍵字參數,這5種參數均可以組合使用,除了可變參數沒法和命名關鍵字參數混合。可是請注意,參數定義的順序必須是:必選參數、默認參數、可變參數/命名關鍵字參數和關鍵字參數。
這個太他媽強大了!!可是有點沒看懂,確實純粹的死記硬背仍是不太有效,能夠等到具體項目應用的時候在參考這篇教程!結合具體的應用再來深刻的理解,絕對事半功倍,如今就須要知道Python中有很強大的函數參數的語法,等到具體用的時候知道到哪裏去找相關的資料就好了!
def fact(n) if n==1: return 1 return n* fact(n-1)
使用遞歸函數須要注意防止棧溢出。在計算機中,函數調用是經過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。因爲棧的大小不是無限的,因此,遞歸調用的次數過多,會致使棧溢出。能夠試試fact(1000)。
解決遞歸調用棧溢出的方法是經過尾遞歸優化,具體應用見廖雪峯的遞歸函數 的教程。
尾遞歸調用時,若是作了優化,棧不會增加,所以,不管多少次調用也不會致使棧溢出。遺憾的是,大多數編程語言沒有針對尾遞歸作優化,Python解釋器也沒有作優化,因此,即便把上面的fact(n)函數改爲尾遞歸方式,也會致使棧溢出。
掌握了Python的數據類型、語句和函數,基本上就能夠編寫出不少有用的程序了。
好比構造一個1, 3, 5, 7, ..., 99的列表,能夠經過循環實現:
L = [] n = 1 while n <= 99: L.append(n) n = n + 2
取list的前一半的元素,也能夠經過循環實現。
可是在Python中,代碼不是越多越好,而是越少越好。代碼不是越複雜越好,而是越簡單越好。用任何的語言編程都應該是這樣。
基於這一思想,咱們來介紹Python中很是有用的高級特性,1行代碼能實現的功能,決不寫5行代碼。請始終牢記,代碼越少,開發效率越高。
L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'] L1 = L[:3] #['Michael', 'Sarah', 'Tracy'] L2 = L[1:3] #['Sarah', 'Tracy'] L3 = L[-2:] #['Bob', 'Jack'] L4 = L[-2:-1] #['Bob'] #list的第一個元素的索引是0,倒數第一個元素的索引是-1 LL=list(range(100)) #[1,2,3,...,99] LL1=L[-10:] #[90,91,...,99] 後10個數 LL2=L[10:20] #[10,11,12,...,19] 前11-20個數 LL3=L[:10:2] #[0,2,4,6,8] 前10個數,每兩個取一個 LL4=L[::5] #[0,5,10,...,90,95] 全部數,每5個取一個 LL5=L[:] #甚至什麼都不寫,只寫[:]就能夠原樣複製一個list
tuple也是一種list,惟一區別是tuple不可變。所以,tuple也能夠用切片操做,只是操做的結果還是tuple。
T=(0,1,2,3,4,5) T1=T[:3]
字符串'xxx'也能夠當作是一種list,每一個元素就是一個字符。所以,字符串也能夠用切片操做,只是操做結果還是字符串:
T='ABCDEFG' T1=T[:3] #'ABC' T2=T[::2] #'ACEG'
在不少編程語言中,針對字符串提供了不少各類截取函數(例如,substring),其實目的就是對字符串切片。Python沒有針對字符串的截取函數,只須要切片一個操做就能夠完成,很是簡單。
有了切片操做,不少地方循環就再也不須要了。Python的切片很是靈活,一行代碼就能夠實現不少行循環才能完成的操做。
Python的for循環抽象程度要高於Java的for循環,由於Python的for循環不只能夠用在list或tuple上,還能夠做用在其餘可迭代對象上。
list這種數據類型雖然有下標,但不少其餘數據類型是沒有下標的,可是,只要是可迭代對象,不管有無下標,均可以迭代,好比dict就能夠迭代。
d= {'a':1, 'b':2, 'c':3} for key in d: print(key) #輸出a c b
爲何輸出的結果是a c b,不是a b c,由於dict的存儲不是按照list的方式順序排列,因此,迭代出的結果順序極可能不同。關於dict的存儲的知識,請參見對應的dict教程。
默認狀況下,dict迭代的是key。若是要迭代value,能夠用for value in d.values()
,若是要同時迭代key和value,能夠用for k, v in d.items()
。
因爲字符串也是可迭代對象,因此能夠用於for循環。
for ch in 'ABCD': print ch
因此,當咱們使用for循環時,只要做用於一個可迭代對象,for循環就能夠正常運行,而咱們不太關心該對象到底是list仍是其餘數據類型。那麼,如何判斷一個對象是可迭代對象呢?方法是經過collections模塊的Iterable類型判斷:
from collections import Iterable isinstance('abc', Iterable) # str是否可迭代 True isinstance([1,2,3], Iterable) # list是否可迭代 True isinstance(123, Iterable) # 整數是否可迭代 False
最後一個小問題,若是要對list實現相似Java那樣的下標循環怎麼辦?Python內置的enumerate函數能夠把一個list變成索引-元素對,這樣就能夠在for循環中同時迭代索引和元素自己:
for i, value in enumerate(['A', 'B', 'C']): print(i, value)
上面的for循環裏,同時引用了兩個變量,在Python裏是很常見的,好比下面的代碼:
for x, y in [(1, 1), (2, 4), (3, 9)]: print(x, y)
L=list(range(1,11)) #生成[1,2,3,4,5,6,7,8,9,10] L1=[x*x for x in range(1,11)] #生成[1*1,2*2,...,10*10]
寫列表生成式時,把要生成的元素好比x * x
放到前面,後面跟for循環,就能夠把list建立出來,十分有用,多寫幾回,很快就能夠熟悉這種語法。
for循環後面還能夠加上if判斷,這樣咱們就能夠篩選出僅偶數的平方:
L= [x * x for x in range(1, 11) if x % 2 == 0] #[4, 16, 36, 64, 100]
還可使用兩層循環,能夠生成全排列:
L= [m + n for m in 'ABC' for n in 'XYZ'] #['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
三層和三層以上的循環就不多用到了。
例程,列出當前目錄下的全部文件和目錄名
import os # 導入os模塊,模塊的概念後面講到 L=[d for d in os.listdir('.')] # os.listdir能夠列出文件和目錄 print(L)
for循環其實能夠同時使用兩個甚至多個變量,好比dict的items()能夠同時迭代key和value:
d = {'x': 'A', 'y': 'B', 'z': 'C' } for k, v in d.items(): print(k, '=', v)
所以,列表生成式也可使用兩個變量來生成list:
d = {'x': 'A', 'y': 'B', 'z': 'C' } L= [k + '=' + v for k, v in d.items()]
把一個list中全部的字符串變成小寫:
L = ['Hello', 'World', 'IBM', 'Apple'] L1= [s.lower() for s in L]
經過列表生成式,咱們能夠直接建立一個列表。可是,受到內存限制,列表容量確定是有限的。並且,建立一個包含100萬個元素的列表,不只佔用很大的存儲空間,若是咱們僅僅須要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
因此,若是列表元素能夠按照某種算法推算出來,那咱們是否能夠在循環的過程當中不斷推算出後續的元素呢?這樣就沒必要建立完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱爲生成器:generator。
要建立一個generator,有不少種方法。第一種方法很簡單,只要把一個列表生成式的[]改爲(),就建立了一個generator:
L = [x * x for x in range(10)] print(L) #[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] g = (x * x for x in range(10)) #()是一個generator print(g) #<generator object <genexpr> at 0x1022ef630>
咱們能夠直接打印出list的每個元素,但咱們怎麼打印出generator的每個元素呢?若是要一個一個打印出來,能夠經過next()函數得到generator的下一個返回值。咱們講過,generator保存的是算法,每次調用next(g),就計算出g的下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出StopIteration的錯誤。
可是,若是每次輸出都調用next(g)實在是太變態了,正確的方法是使用for循環,由於generator也是可迭代對象:
g = (x*x for x in range(10)) for n in g print(n)
因此,咱們建立了一個generator後,基本上永遠不會調用next(),而是經過for循環來迭代它,而且不須要關心StopIteration的錯誤。
generator很是強大。若是推算的算法比較複雜,用相似列表生成式的for循環沒法實現的時候,還能夠用函數來實現。這篇對應的教程中還講了更爲牛逼的generator的使用方法!
咱們已經知道,能夠直接做用於for循環的數據類型有如下幾種:
一類是集合數據類型,如list、tuple、dict、set、str等;
一類是generator,包括生成器和帶yield的generator function。
這些能夠直接做用於for循環的對象統稱爲可迭代對象:Iterable。
可使用isinstance()判斷一個對象是不是Iterable對象:
from collections import Iterable isinstance([], Iterable) #True isinstance({}, Iterable) #True isinstance('abc', Iterable) #True isinstance((x for x in range(10)), Iterable) #True isinstance(100, Iterable) #False
而生成器不但能夠做用於for循環,還能夠被next()函數不斷調用並返回下一個值,直到最後拋出StopIteration錯誤表示沒法繼續返回下一個值了。
能夠被next()函數調用並不斷返回下一個值的對象稱爲迭代器:Iterator。
可使用isinstance()判斷一個對象是不是Iterator對象:
from collections import Iterator isinstance((x for x in range(10)), Iterator) #True isinstance([], Iterator) #False isinstance({}, Iterator) #False isinstance('abc', Iterator) #False
生成器都是Iterator對象,但list、dict、str雖然是Iterable,卻不是Iterator。
把list、dict、str等Iterable變成Iterator可使用iter()函數:
isinstance(iter([]), Iterator) #True isinstance(iter('abc'), Iterator) #True
你可能會問,爲何list、dict、str等數據類型不是Iterator?
這是由於Python的Iterator對象表示的是一個數據流,Iterator對象能夠被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。能夠把這個數據流看作是一個有序序列,但咱們卻不能提早知道序列的長度,只能不斷經過next()函數實現按需計算下一個數據,因此Iterator的計算是惰性的,只有在須要返回下一個數據時它纔會計算。
Iterator甚至能夠表示一個無限大的數據流,例如全體天然數。而使用list是永遠不可能存儲全體天然數的。
凡是可做用於for循環的對象都是Iterable類型。
凡是可做用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列。
Python的for循環本質上就是經過不斷調用next()函數實現的,例如:
for x in [1, 2, 3, 4, 5]: pass
實際上徹底等價於:
# 首先得到Iterator對象: it = iter([1, 2, 3, 4, 5]) # 循環: while True: try: # 得到下一個值: x = next(it) except StopIteration: # 遇到StopIteration就退出循環 break
函數式編程的一個特色就是,容許把函數自己做爲參數傳入另外一個函數,還容許返回一個函數!
Python對函數式編程提供部分支持。因爲Python容許使用變量,所以,Python不是純函數式編程語言。
那麼函數名是什麼呢?函數名其實就是指向函數的變量!對於abs()這個函數,徹底能夠把函數名abs當作變量,它指向一個能夠計算絕對值的函數!
若是把abs指向其餘對象,會有什麼狀況發生?
abs = 10 abs(-10)
報錯:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
把abs指向10後,就沒法經過abs(-10)調用該函數了!由於abs這個變量已經不指向求絕對值函數而是指向一個整數10!
固然實際代碼絕對不能這麼寫,這裏是爲了說明函數名也是變量。要恢復abs函數,請重啓Python交互環境。
因爲abs函數其實是定義在__builtin__模塊中的,因此要讓修改abs變量的指向在其它模塊也生效,要用__builtin__.abs = 10。
既然變量能夠指向函數,函數的參數能接收變量,那麼一個函數就能夠接收另外一個函數做爲參數,這種函數就稱之爲高階函數。例子
def add(x, y, f): return f(x) + f(y)
能夠這樣調用:
add(-5, 6, abs)
編寫高階函數,就是讓函數的參數可以接收別的函數。函數式編程就是指這種高度抽象的編程範式。
咱們先看map。map()函數接收兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次做用到序列的每一個元素,並把結果做爲新的Iterator返回。
def f(x): return x*x r = map(f, [1,2,3,4,5,6,7,8,9]) print(list(r)) #[1,4,16,25,36,49,64,81]
再看reduce的用法。reduce把一個函數做用在一個序列[x1, x2, x3, ...]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素作累積計算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
例子,比方說對一個序列求和,就能夠用reduce實現:
from functools import reduce
def add(x, y):
return x + y
print(reduce(add, [1, 3, 5, 7, 9])) #輸出25
這個例子自己沒多大用處,可是,若是考慮到字符串str也是一個序列,對上面的例子稍加改動,配合map(),咱們就能夠寫出把str轉換爲int的函數:
from functools import reduce def fn(x, y): return x * 10 + y def char2num(s): return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s] print(reduce(fn, map(char2num, '13579'))) #輸出13579
整理成一個str2int的函數就是:
from functools import reduce def str2int(s): def fn(x, y): return x * 10 + y def char2num(s): return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s] return reduce(fn, map(char2num, s))
還能夠用lambda函數進一步簡化成:
from functools import reduce def char2num(s): return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s] def str2int(s): return reduce(lambda x, y: x * 10 + y, map(char2num, s))
也就是說,假設Python沒有提供int()函數,你徹底能夠本身寫一個把字符串轉化爲整數的函數,並且只須要幾行代碼!
Python內建的filter()函數用於過濾序列。
和map()相似,filter()也接收一個函數和一個序列。和map()不一樣的時,filter()把傳入的函數依次做用於每一個元素,而後根據返回值是True仍是False決定保留仍是丟棄該元素。
例如,在一個list中,刪掉偶數,只保留奇數,能夠這麼寫:
def is_odd(n): return n % 2 == 1 list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])) # 結果: [1, 5, 9, 15]
可見用filter()這個高階函數,關鍵在於正確實現一個「篩選」函數。
注意到filter()函數返回的是一個Iterator,也就是一個惰性序列,因此要強迫filter()完成計算結果,須要用list()函數得到全部結果並返回list。
排序也是在程序中常常用到的算法。不管使用冒泡排序仍是快速排序,排序的核心是比較兩個元素的大小。若是是數字,咱們能夠直接比較,但若是是字符串或者兩個dict呢?直接比較數學上的大小是沒有意義的,所以,比較的過程必須經過函數抽象出來。一般規定,對於兩個元素x和y,若是認爲x < y,則返回-1,若是認爲x == y,則返回0,若是認爲x > y,則返回1,這樣,排序算法就不用關心具體的比較過程,而是根據比較結果直接排序。
Python內置的sorted()函數就能夠對list進行排序:
sorted([36, 5, -12, 9, -21]) #[-21, -12, 5, 9, 36]
此外,sorted()函數也是一個高階函數,它還能夠接收一個key函數來實現自定義的排序,例如按絕對值大小排序:
sorted([36, 5, -12, 9, -21], key=abs) #[5, 9, -12, -21, 36] #key指定的函數將做用於list的每個元素上,並根據key函數返回的結果進行排序。
咱們再看一個字符串排序的例子:
sorted(['bob', 'about', 'Zoo', 'Credit']) #['Credit', 'Zoo', 'about', 'bob']
默認狀況下,對字符串排序,是按照ASCII的大小比較的,因爲'Z' < 'a',結果,大寫字母Z會排在小寫字母a的前面。
如今,咱們提出排序應該忽略大小寫,按照字母序排序。要實現這個算法,沒必要對現有代碼大加改動,只要咱們能用一個key函數把字符串映射爲忽略大小寫排序便可。忽略大小寫來比較兩個字符串,實際上就是先把字符串都變成大寫(或者都變成小寫),再比較。
這樣,咱們給sorted傳入key函數,便可實現忽略大小寫的排序:
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) #['about', 'bob', 'Credit', 'Zoo']
要進行反向排序,沒必要改動key函數,能夠傳入第三個參數reverse=True:
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) #['Zoo', 'Credit', 'bob', 'about']
從上述例子能夠看出,高階函數的抽象能力是很是強大的,並且,核心代碼能夠保持得很是簡潔。
高階函數除了能夠接受函數做爲參數外,還能夠把函數做爲結果值返回。
咱們來實現一個可變參數的求和。一般狀況下,求和的函數是這樣定義的:
def calc_sum(*args): ax = 0 for n in args: ax = ax + n return ax
可是,若是不須要馬上求和,而是在後面的代碼中,根據須要再計算怎麼辦?能夠不返回求和的結果,而是返回求和的函數:
def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum
當咱們調用lazy_sum()時,返回的並非求和結果,而是求和函數:
f = lazy_sum(1, 3, 5, 7, 9) f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
調用函數f時,才真正計算求和的結果:
f() #25
注意到返回的函數在其定義內部引用了局部變量args,因此,當一個函數返回了一個函數後,其內部的局部變量還被新函數引用,因此,閉包用起來簡單,實現起來可不容易。
另外一個須要注意的問題是,返回的函數並無馬上執行,而是直到調用了f()才執行。咱們來看一個例子:
def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs f1, f2, f3 = count()
在上面的例子中,每次循環,都建立了一個新的函數,而後,把建立的3個函數都返回了。
你可能認爲調用f1(),f2()和f3()結果應該是1,4,9,但實際結果是:
f1() #9 f2() #9 f3() #9
所有都是9!緣由就在於返回的函數引用了變量i,但它並不是馬上執行。等到3個函數都返回時,它們所引用的變量i已經變成了3,所以最終結果爲9。
返回閉包時牢記的一點就是:返回函數不要引用任何循環變量,或者後續會發生變化的變量。
若是必定要引用循環變量怎麼辦?方法是再建立一個函數,用該函數的參數綁定循環變量當前的值,不管該循環變量後續如何更改,已綁定到函數參數的值不變:
def count(): def f(j): def g(): return j*j return g fs = [] for i in range(1, 4): fs.append(f(i)) # f(i)馬上被執行,所以i的當前值被傳入f() return fs
再看看結果:
f1, f2, f3 = count() f1() #1 f2() #4 f3() #9
缺點是代碼較長,可利用lambda函數縮短代碼。
當咱們在傳入函數時,有些時候,不須要顯式地定義函數,直接傳入匿名函數更方便。
在Python中,對匿名函數提供了有限支持。仍是以map()函數爲例,計算f(x)=x2時,除了定義一個f(x)的函數外,還能夠直接傳入匿名函數:
list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])) #[1, 4, 9, 16, 25, 36, 49, 64, 81]
經過對比能夠看出,匿名函數lambda x: x * x實際上就是:
def f(x): return x * x
關鍵字lambda表示匿名函數,冒號前面的x表示函數參數。
匿名函數有個限制,就是隻能有一個表達式,不用寫return,返回值就是該表達式的結果。
用匿名函數有個好處,由於函數沒有名字,沒必要擔憂函數名衝突。此外,匿名函數也是一個函數對象,也能夠把匿名函數賦值給一個變量,再利用變量來調用該函數:
f = lambda x: x * x f #<function <lambda> at 0x101c6ef28> f(5) #25
一樣,也能夠把匿名函數做爲返回值返回,好比:
def build(x, y): return lambda: x * x + y * y
def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*argc, **kw) return wrapper
觀察上面的log,由於它是一個decorator,因此接受一個函數做爲參數,並返回一個函數。咱們要藉助Python的@語法,把decorator置於函數的定義處:
@log def now(): print('2015-3-25')
調用now()函數,不只會運行now()函數自己,還會在運行now()函數前打印一行日誌:
now() #call now(): #2015-3-25
把@log放到now()函數的定義處,至關於執行了語句:
now = log(now)
因爲log()是一個decorator,返回一個函數,因此,原來的now()函數仍然存在,只是如今同名的now變量指向了新的函數,因而調用now()將執行新函數,即在log()函數中返回的wrapper()函數。
wrapper()函數的參數定義是(args, *kw),所以,wrapper()函數能夠接受任意參數的調用。在wrapper()函數內,首先打印日誌,再緊接着調用原始函數。
在面向對象(OOP)的設計模式中,decorator被稱爲裝飾模式。OOP的裝飾模式須要經過繼承和組合來實現,而Python除了能支持OOP的decorator外,直接從語法層次支持decorator。Python的decorator能夠用函數實現,也能夠用類實現。
decorator能夠加強函數的功能,定義起來雖然有點複雜,但使用起來很是靈活和方便。
functools.partial就是幫助咱們建立一個偏函數的,不須要咱們本身定義int2(),能夠直接使用下面的代碼建立一個新的函數int2:
import functools int2 = functools.partial(int, base=2) print(int2('1000000')) #64 print(int2('1010101')) #85
Python的functools模塊提供了不少有用的功能,其中一個就是偏函數(Partial function)。要注意,這裏的偏函數和數學意義上的偏函數不同。
沒太看懂,仍是等到具體研究一個項目源碼,以及本身作開發的時候再去結合實踐深刻理解吧!
2015.09.08 23:59:00 明天繼續看 模塊 的教程,今天對不少知識點並無真正理解,都是有一些印象,因此必須等到本身研究源碼、本身開發的時候,結合運行的效果和理論知識去達到真正的深刻的理解。如今趕忙睡覺!
在計算機程序的開發過程當中,隨着程序代碼越寫越多,在一個文件裏代碼就會愈來愈長,愈來愈不容易維護。
爲了編寫可維護的代碼,咱們把不少函數分組,分別放到不一樣的文件裏,這樣,每一個文件包含的代碼就相對較少,不少編程語言都採用這種組織代碼的方式。在Python中,一個.py文件就稱之爲一個模塊(Module)。
引入了包之後,只要頂層的包名不與別人衝突,那全部模塊都不會與別人衝突。如今,abc.py模塊的名字就變成了mycompany.abc,相似的,xyz.py的模塊名變成了mycompany.xyz。
請注意,每個包目錄下面都會有一個__init__.py的文件,這個文件是必須存在的,不然,Python就把這個目錄當成普通目錄,而不是一個包。__init__.py能夠是空文件,也能夠有Python代碼,由於__init__.py自己就是一個模塊,而它的模塊名就是mycompany。
相似的,能夠有多級目錄,組成多級層次的包結構。
不少強大的第三方庫,要可以充分利用好它們爲我服務!!!
面向對象編程——Object Oriented Programming,簡稱OOP,是一種程序設計思想。OOP把對象做爲程序的基本單元,一個對象包含了數據和操做數據的函數。
面向過程的程序設計把計算機程序視爲一系列的命令集合,即一組函數的順序執行。爲了簡化程序設計,面向過程把函數繼續切分爲子函數,即把大塊函數經過切割成小塊函數來下降系統的複雜度。
而面向對象的程序設計把計算機程序視爲一組對象的集合,而每一個對象均可以接收其餘對象發過來的消息,並處理這些消息,計算機程序的執行就是一系列消息在各個對象之間傳遞。
2015.09.22,明天繼續學習《面向對象編程》
好比一個類的代碼以下
class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score))
能夠這樣使用這個類建立對象
bart = Student('Bart Simpson', 59) lisa = Student('Lisa Simpson', 87) bart.print_score() lisa.print_score()
面向對象的設計思想是從天然界中來的,由於在天然界中,
類(Class)
和實例(Instance)
的概念是很天然的。Class是一種抽象概念,好比咱們定義的Class——Student,是指學生這個概念,而實例(Instance)則是一個個具體的Student,好比,Bart Simpson和Lisa Simpson是兩個具體的Student。
因此,面向對象的設計思想是抽象出Class,根據Class建立Instance。
面向對象的抽象程度又比函數要高,由於一個Class既包含數據,又包含操做數據的方法。
數據封裝、繼承和多態是面向對象的三大特色,咱們後面會詳細講解。
關於面向對象設計其實和C++、Delphi……都很像,可是具體的語法可能不一樣,不過這都是一些表面化的東西。具體去參考Python的編程規範、語法就行了。
這篇教程裏面有關於類、實例、實例的內存地址……的講解,因此要好好看看!
__init__
方法是類的構造方法,self
這個特殊變量的理解。
和普通的函數相比,在類中定義的函數只有一點不一樣,就是第一個參數永遠是實例變量self,而且,調用時,不用傳遞該參數。除此以外,類的方法和普通函數沒有什麼區別,因此,你仍然能夠用默認參數、可變參數、關鍵字參數和命名關鍵字參數。
要定義一個方法,除了第一個參數是self外,其餘和普通函數同樣。要調用一個方法,只須要在實例變量上直接調用,除了self不用傳遞,其餘參數正常傳入。
由於Python是靜態語言,因此語法上還會有其餘更多的區別,因此必定要和其餘的以前我瞭解的語言在語法方面區分開
一些關於變量的權限、訪問限制、命名規範的說明。總的來講就是,Python自己沒有任何機制阻止你幹壞事,一切全靠自覺。
在繼承關係中,若是一個實例的數據類型是某個子類,那它的數據類型也能夠被看作是父類。可是,反過來就不行。可使用isistance()函數來進行判斷。
這篇教程很好的講解了多態的表現形式!!具體的編程語法、代碼實現的細節,認真參考這篇教程!!
type()
isinstance()
dir()
:若是要得到一個對象的全部屬性和方法,可使用dir()函數,它返回一個包含字符串的list,好比,得到一個str對象的全部屬性和方法。