記錄個人Python學習筆記

不想再像之前那樣,什麼都從頭開始學習語法、總結語法,這樣反而會過度糾結於語法,耽誤了開發,畢竟語言的主要屬性是工具,次要的屬性是語言自己。html

因此仍是先熟練使用語言去進行開發,等足夠熟悉了,再去研究語言自己(編譯原理……)。python

另外對於算法、設計模式、數據結構、網絡知識、操做系統…… 的學習能夠專門針對性的學習,固然更好的方法是以使用語言爲主線,經過具體的應用和實踐來推進這些技術知識的學習。算法

本文是經過廖雪峯的網站學習而整理的(真的是很好的教程,免得我花錢買書了!),而後我沒有去再整理總結語法,而是直接經過寫出代碼段來體現本身的學習,也方便之後的快速複習、回顧。畢竟學習一門語言不是一天能夠完成的,因此本文也不是一蹴而就的,而是會一直更新。編程

也沒有必要再對代碼作過多的文字解釋,一切都能經過代碼自己體現。windows

交互式命令行

在Python的交互式命令行寫程序,好比>>>print('hello world'),好處是一下就能獲得結果,壞處是無法保存,下次還想運行的時候,還要再敲一遍。設計模式

因此實際的開發中,咱們使用一個文本編輯器來寫代碼,而後保存爲一個文件,這樣程序就能夠反覆運行了。好比將print('heool world')寫在文檔裏注意print前面不能有任何空格,並保存爲 hello.py,而後使用命令行進入該文件所在的目錄,執行python hello.py就能夠解析執行這個源文件了。數組

絕對不要使用windows自帶的記事本:記事本會自做聰明地在文件開始的地方加上幾個特殊字符(UTF-8 BOM),結果會致使程序運行出現莫名其妙的錯誤。安全

直接運行py文件

能不能像.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基礎

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.3333333333333
9/3 獲得的結果是 3.0
10//3 獲得的結果是 3
10%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

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

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)

字典:dict

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

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,但寫起來更方便。

Python強大的函數參數

廖雪峯的函數的參數 這一章講解了位置參數、默認參數、由於參數類型不是不變對象致使使用默認參數出現的"意外"、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/reduce

咱們先看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()函數,你徹底能夠本身寫一個把字符串轉化爲整數的函數,並且只須要幾行代碼!

filter

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。

sorted

排序也是在程序中常常用到的算法。不管使用冒泡排序仍是快速排序,排序的核心是比較兩個元素的大小。若是是數字,咱們能夠直接比較,但若是是字符串或者兩個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對象的全部屬性和方法。

相關文章
相關標籤/搜索