Python是一種至關高級的語言。
TIOBE排行榜
高級編程語言一般都會提供一個比較完善的基礎代碼庫,讓你能直接調用,
許多大型網站就是用Python開發的,例如YouTube、Instagram,還有國內的豆瓣。
Python的哲學就是簡單優雅
那Python適合開發哪些類型的應用呢?
- 網絡應用,包括網站、後臺服務等等;
- 腳本任務等等;
- 把其餘語言開發的程序再包裝起來,方便使用。
缺點:
- 運行速度慢:解釋型語言,執行的時候翻譯稱爲機器代碼。
- 代碼不能加密:靠網站和移動應用賣服務的模式愈來愈多了
安裝
Python3.5
在Windows上運行Python時,請先啓動命令行,而後運行python。
在Mac和Linux上運行Python時,請打開終端,而後運行python3。
Python解釋器
Cpython:官方
第一個Python程序
退出:exit()
命令行模式和Python交互模式
python calc.py
使用文本編輯器
#!/usr/bin/env python3print('hello, world')
直接輸入python進入交互模式,至關於啓動了Python解釋器,可是等待你一行一行地輸入源代碼,每輸入一行就執行一行。
直接運行.py文件至關於啓動了Python解釋器,而後一次性把.py文件的源代碼給執行了,你是沒有機會以交互的方式輸入源代碼的。
Python代碼運行助手
網頁上輸入代碼,經過本機的Python庫進行解釋。
輸入和輸出
print()會依次打印每一個字符串,遇到逗號「,」會輸出一個空格,所以,輸出的字符串是這樣拼起來的:
print()也能夠打印整數,或者計算結果
輸入
name = input()
提示字符串:
name = input('please enter your name: ')
python基礎
其餘每一行都是一個語句,當語句以冒號:結尾時,縮進的語句視爲代碼塊。
縮進的壞處就是「複製-粘貼」功能失效了,
最後,請務必注意,Python程序是大小寫敏感的,若是寫錯了大小寫,程序會報錯。
在文本編輯器中,須要設置把Tab自動轉換爲4個空格,確保不混用Tab和空格。
變量和類型
字符串
字符串是以單引號'或雙引號"括起來的任意文本
用r''表示''內部的字符串默認不轉義
字符串有不少行:
用\n寫在一行裏很差閱讀,爲了簡化,Python容許用'''...'''的格式表示多行內容
>>> print('''line1 ... line2 ... line3''')
line1
line2
line3
布爾值
True、False
if age >= 18:
print('adult')
else:
print('teenager')
空值
空值是Python裏一個特殊的值,用None表示。None不能理解爲0,由於0是有意義的,而None是一個特殊的空值。
變量
理解變量在計算機內存中的表示也很是重要。當咱們寫:
- 在內存中建立了一個'ABC'的字符串;
- 在內存中建立了一個名爲a的變量,並把它指向'ABC'。
a = 'ABC'
b = a
a = 'XYZ'print(b)
執行a = 'ABC'
,解釋器建立了字符串'ABC'
和變量a
,並把a
指向'ABC'
:javascript
執行b = a
,解釋器建立了變量b
,並把b
指向a
指向的字符串'ABC'
:php
執行a = 'XYZ'
,解釋器建立了字符串'XYZ',並把a
的指向改成'XYZ'
,但b
並無更改:css
因此,最後打印變量b
的結果天然是'ABC'
了。java
一種除法是//,稱爲地板除,兩個整數的除法仍然是整數:
Python還提供一個餘數運算,能夠獲得兩個整數相除的餘數:
Python的整數沒有大小限制,而某些語言的整數根據其存儲長度是有大小限制的,例如Java對32位整數的範圍限制在-2147483648~-2147483647。
Python的浮點數也沒有大小限制,可是超出必定範圍就直接表示爲inf(無限大)
字符串和編碼
最先只有127個字符被編碼到計算機裏,也就是大小寫英文字母、數字和一些符號,這個編碼表被稱爲ASCII編碼
可是要處理中文顯然一個字節是不夠的,至少須要兩個字節,並且還不能和ASCII編碼衝突,因此,中國製定了GB2312編碼,用來把中文編進去。
Unicode應運而生。Unicode把全部語言都統一到一套編碼裏,這樣就不會再有亂碼問題了。
若是你寫的文本基本上所有是英文的話,用Unicode編碼比ASCII編碼須要多一倍的存儲空間,在存儲和傳輸上就十分不划算。
把Unicode編碼轉化爲「可變長編碼」的UTF-8編碼
經常使用的英文字母被編碼成1個字節,漢字一般是3個字節,只有很生僻的字符纔會被編碼成4-6個字節
在計算機內存中,統一使用Unicode編碼,當須要保存到硬盤或者須要傳輸的時候,就轉換爲UTF-8編碼。
瀏覽網頁的時候,服務器會把動態生成的Unicode內容轉換爲UTF-8再傳輸到瀏覽器:
因此你看到不少網頁的源碼上會有相似<meta charset="UTF-8" />的信息,表示該網頁正是用的UTF-8編碼。
,Python提供了ord()函數獲取字符的整數表示,chr()函數把編碼轉換爲對應的字符:
若是要在網絡上傳輸,或者保存到磁盤上,就須要把str變爲以字節爲單位的bytes。而在內存中用Unicode進行表示,一個字符對應多個字節
注意區分'ABC'和b'ABC',前者是str,後者雖然內容顯示得和前者同樣,但bytes的每一個字符都只佔用一個字節。
以Unicode表示的str經過encode()方法能夠編碼爲指定的bytes
>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
在bytes中,沒法顯示爲ASCII字符的字節,用\x##顯示。
'中文'.encode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
由於中文編碼的範圍超過了ASCII編碼的範圍,Python會報錯。
若是咱們從網絡或磁盤上讀取了字節流,那麼讀到的數據就是bytes。要把bytes變爲str,就須要用decode()方法:
b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
計算字符數:
len(
'中文'
)
計算字節數:
>>> len('中文'.encode('utf-8'))
6
爲了不亂碼問題,應當始終堅持使用UTF-8編碼對str和bytes進行轉換。
按照UTF-8進行讀取:
格式化
'Hi, %s, you have $%d.' % ('Michael', 1000000)
%d |
整數 |
%f |
浮點數 |
%s |
字符串 |
%x |
十六進制整數 |
>>> '%2d-%02d' % (3, 1)
' 3-01'>>> '%.2f' % 3.1415926'3.14'
補零
'Age: %s. Gender: %s' % (25, True)
會把全部類型轉換爲字符串。
用%%來表示一個%:
list和tuple
list
。list是一種有序的集合,能夠隨時添加和刪除其中的元素。
classmates = ['Michael', 'Bob', 'Tracy']
記得最後一個元素的索引是len(classmates) - 1。
倒數第二個:
classmates[-2]
list是一個可變的有序表,因此,能夠往list中追加元素到末尾:
classmates.append('Adam')
要刪除list末尾的元素,用pop()方法:
要刪除指定位置的元素,用pop(i)方法,其中i是索引位置:
要把某個元素替換成別的元素,能夠直接賦值給對應的索引位置:
classmates[
1
] =
'Sarah'
list裏面的元素的數據類型也能夠不一樣,好比:
>>>
L = [
'Apple'
,
123
,
True
]
也能夠是另外的list:
s = [
'python'
,
'java'
, [
'asp'
,
'php'
],
'scheme'
]
要拿到'php'能夠s[2][1],
tuple
元組
一旦初始化之後就不能修改。
classmates = ('Michael', 'Bob', 'Tracy')
因此也沒有append() , insert()這樣的方法。
代碼能夠更安全,儘量使用。
t = (1)既多是表示元組,也多是表示括號。
消除歧義。
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'>>> t[2][1] = 'Y'>>> t
('a', 'b', ['X', 'Y'])
tuple包含3個元素。
變的實際上是list的元素。
所謂的不變指的是指向永遠不變。
小結
list和tuple是Python內置的有序集合,一個可變,一個不可變。
條件判斷
age = 3if age >= 18:
print('adult')
elif age >= 6:
print('teenager')
else:
print('kid')
注意else後面的冒號。
elif是else if 的縮寫。
若是在某個判斷上是True,把該判斷對應的語句執行後,就忽略掉剩下的elif和else
birth = input('birth: ')
if birth < 2000:
print('00前')
else:
print('00後')
輸入的是字符串。可是2000是數字,不能比較。
s = input('birth: ')
birth = int(s)
if birth < 2000:
print('00前')
else:
print('00後')
經過int(s)把字符串轉換爲數字。
循環
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)
提供一個range()函數,能夠生成一個整數序列,再經過list()函數能夠轉換爲list。好比range(5)生成的序列是從0開始小於5的整數:
range(101)就能夠生成0-100的整數序列
sum = 0
n = 99while n > 0:
sum = sum + n
n = n - 2print(sum)
在循環內部變量n不斷自減,直到變爲-1時,再也不知足while條件,循環退出。
使用dict和set
dict
其餘語言map,鍵值對。
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
在字典的索引表裏(好比部首表)查這個字對應的頁碼,而後直接翻到該頁,找到這個字。
要避免key不存在的錯誤,有兩種辦法,一是經過in判斷key是否存在:
二是經過dict提供的get方法,若是key不存在,能夠返回None,或者本身指定的value:
>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1
要刪除一個key,用pop(key)方法,對應的value也會從dict中刪除:
dict是用空間來換取時間的一種方法。
dict的key必須是不可變對象
經過key計算位置的算法稱爲哈希算法(Hash)。
而list是可變的,就不能做爲key:
set
set和dict相似,也是一組key的集合,但不存儲value。因爲key不能重複,因此,在set中,沒有重複的key。
s = set([1, 2, 3])
重複元素自動過濾
s = set([1, 1, 2, 2, 3, 3])
經過add(key)方法能夠添加元素到set中,能夠重複添加,但不會有效果:
經過remove(key)方法能夠刪除元素:
set能夠當作數學意義上的無序和無重複元素的集合
set和dict的惟一區別僅在於沒有存儲對應的value,可是,set的原理和dict同樣,因此,一樣不能夠放入可變對象,由於沒法判斷兩個可變對象是否相等,也就沒法保證set內部「不會有重複元素」。
再議不可變對象
>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']
內部對象是會變化的。
>>> a = 'abc'>>> a.replace('a', 'A')
'Abc'>>> a
'abc'
雖然replace能夠變出Abc,可是實際上a仍然是abc
a是變量,而'abc'纔是字符串對象!有些時候,咱們常常說,對象a的內容是'abc',但實際上是指,a自己是一個變量,它指向的對象的內容纔是'abc':
replace方法建立了一個新字符串'Abc'並返回,若是咱們用變量b指向該新字符串,就容易理解了,變量a仍指向原有的字符串'abc',但變量b卻指向新字符串'Abc'了:
對於不變對象來講,調用對象自身的任意方法,也不會改變該對象自身的內容。相反,這些方法會建立新的對象並返回,這樣,就保證了不可變對象自己永遠是不可變的。
最經常使用的key是字符串。
函數
定義函數
def my_abs(x):if x >= 0:
return x
else:
return -x
用
from abstest import my_abs來導入my_abs()函數,注意abstest是文件名(不含.py擴展名):
空函數
使用pass語句。
做爲佔位符。
不會對參數類型作檢查。
用內置函數isinstance()實現:
def my_abs(x):if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x
返回多個值
import math語句表示導入math包,並容許後續代碼引用math包裏的sin、cos等函數。
import math
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
返回仍然是單一值,是一個tuple。
函數的參數
位置參數
修改後的power(x, n)函數有兩個參數:x和n,這兩個參數都是位置參數,調用函數時,傳入的兩個值按照位置順序依次賦給參數x和n。
默認參數
def power(x, n=2):
s = 1while n > 0:
n = n - 1
s = s * x
return s
一是必選參數在前,默認參數在後,不然Python的解釋器會報錯
二是如何設置默認參數。
當函數有多個參數時,把變化大的參數放前面,變化小的參數放後面。變化小的參數就能夠做爲默認參數。
def add_end(L=[]):
L.append('END')
return L
此時默認參數是可變的list
第一次調用
後面調用就不對了。
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']
Python函數在定義的時候,默認參數L的值就被計算出來了,即[],由於默認參數L也是一個變量,它指向對象[],每次調用該函數,若是改變了L的內容,則下次調用時,默認參數的內容就變了,再也不是函數定義時的[]了。
因此,定義默認參數要牢記一點:默認參數必須指向不變對象!
能夠經過不變對象None來實現。
def add_end(L=None):if L is None:
L = []
L.append('END')
return L
>>> add_end()
['END']
>>> add_end()
['END']
因爲對象不變,多任務環境下同時讀取對象不須要加鎖,同時讀一點問題都沒有。
可變參數
def calc(*numbers):
sum = 0for n in numbers:
sum = sum + n * n
return sum
Python容許你在list或tuple前面加一個*號,把list或tuple的元素變成可變參數傳進去:
>>> nums = [1, 2, 3]
>>> calc(*nums)
14
關鍵字參數
可變參數容許你傳入0個或任意個參數,這些可變參數在函數調用時自動組裝爲一個tuple。
關鍵字參數容許你傳入0個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝爲一個dict
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
能夠傳入任意個數的關鍵字參數
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
能夠擴展函數的功能。
能夠先組裝爲一個dict,而後傳進去。
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
**extra表示把extra這個dict的全部key-value用關鍵字參數傳入到函數的**kw參數,kw將得到一個dict,注意kw得到的dict是extra的一份拷貝,對kw的改動不會影響到函數外的extra。
命名關鍵字
在內部對傳入的關鍵字進行檢測。
def person(name, age, **kw):if 'city' in kw:
passif 'job' in kw:
pass
print('name:', name, 'age:', age, 'other:', kw)
限制關鍵字參數的名字,能夠用命名關鍵字。
def person(name, age, *, city, job):
print(name, age, city, job)
若是函數定義中已經有了一個可變參數,後面跟着的命名關鍵字參數就再也不須要一個特殊分隔符*了:
def person(name, age, *args, city, job):
print(name, age, args, city, job)
參數組合
參數定義的順序必須是:必選參數、默認參數、可變參數、命名關鍵字參數和關鍵字參數
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
經過一個tuple和dict,你也能夠調用上述函數:
>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
對於任意函數,均可以經過相似func(*args, **kw)的形式調用它,不管它的參數是如何定義的。
小結
默認參數必定要用不可變對象,若是是可變對象,程序運行時會有邏輯錯誤!
要注意定義可變參數和關鍵字參數的語法:
*args是可變參數,args接收的是一個tuple;
**kw是關鍵字參數,kw接收的是一個dict。
以及調用函數時如何傳入可變參數和關鍵字參數的語法:
可變參數既能夠直接傳入:func(1, 2, 3),又能夠先組裝list或tuple,再經過*args傳入:func(*(1, 2, 3));
關鍵字參數既能夠直接傳入:func(a=1, b=2),又能夠先組裝dict,再經過**kw傳入:func(**{'a': 1, 'b': 2})。
命名的關鍵字參數是爲了限制調用者能夠傳入的參數名,同時能夠提供默認值。
定義命名的關鍵字參數在沒有可變參數的狀況下不要忘了寫分隔符*,不然定義的將是位置參數。
遞歸函數
def fact(n):if n==1:
return 1return n * fact(n - 1)
防止棧溢出。
經過尾遞歸優化。和循環的效果同樣。在函數返回的時候,調用自身。return不能包含表達式。
這樣,編譯器或者解釋器就能夠把尾遞歸作優化,使遞歸自己不管調用多少次,都只佔用一個棧幀,不會出現棧溢出的狀況。
要把每一步的乘積傳入到遞歸函數中:
def fact(n):return fact_iter(n, 1)
def fact_iter(num, product):if num == 1:
return product
return fact_iter(num - 1, num * product)
能夠看到,return fact_iter(num - 1, num * product)僅返回遞歸函數自己,num - 1和num * product在函數調用前就會被計算,不影響函數調用。
高級特性
構造列表:
L = []n = 1while n <= 99: L.append(n) n = n + 2
切片
取前N個元素。
>>> r = []
>>> n = 3>>> for i in range(n):
... r.append(L[i])
...
記住倒數第一個元素的索引是
-1
。
前11-20個數:
前10個數,每兩個取一個:
>>> L[:10:2]
全部數每5個取一個
複製應該 list
tuple也是一種list,惟一區別是tuple不可變。所以,tuple也能夠用切片操做,只是操做的結果還是tuple:
>>> (0, 1, 2, 3, 4, 5)[:3]
字符串'xxx'也能夠當作是一種list,每一個元素就是一個字符。所以,字符串也能夠用切片操做,只是操做結果還是字符串
>>> 'ABCDEFG'[:3]
'ABC'>>> 'ABCDEFG'[::2]
'ACEG'
針對字符串提供了不少各類截取函數(例如,substring),其實目的就是對字符串切片。Python沒有針對字符串的截取函數,只須要切片一個操做就能夠完成,很是簡單。
迭代
迭代:遍歷list or tuple
Python中迭代是經過for in 實現的,java是經過下標實現的。
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
... print(key)
...
默認dict迭代的是key
若是要迭代value,能夠用for value in d.values(),若是要同時迭代key和value,能夠用for k, v in d.items()。
因爲字符串也是可迭代對象,所以,也能夠做用於for循環:
如何判斷一個對象是可迭代對象呢?方法是經過collections模塊的Iterable類型判斷:
>>> from collections import Iterable
>>> isinstance('abc', Iterable) True>>> isinstance([1,2,3], Iterable) True>>> isinstance(123, Iterable) False
若是要實現相似Java那樣的下標循環。
Python內置的enumerate函數能夠把一個list變成索引-元素對,這樣就能夠在for循環中同時迭代索引和元素自己:
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
上面的for循環裏,同時引用了兩個變量,在Python裏是很常見的
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:... print(x, y)...1 12 43 9
列表生成式
List Comprehensions :內置的,用來建立list的生成式。
若是要生成[1x1, 2x2, 3x3, ..., 10x10]怎麼作?
>>> [x * x for x in range(1, 11)]
for循環後面還能夠加上if判斷,這樣咱們就能夠篩選出僅偶數的平方:
[x * x for x in range (1 , 11) if x %2 = 0]
還可使用兩層循環,能夠生成全排列:
[m + n for m in 'ABC' for n in 'abc']
列出當前目錄下的文件和目錄
import os [d for d in os.listdir ('.')]
for循環其實能夠同時使用兩個甚至多個變量,好比dict的items()能夠同時迭代key和value:
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
... print(k, '=', v)
...
y = B
x = A
z = C
d = {'x' : 'A' , 'y' : 'B' , 'z' : 'C'} [k + '=' + v for k,v in d.items()]
最後把list的字符串變爲小寫。
L = ['Hello', 'World', 'IBM', 'Apple']
>>> L = ['Hello', 'World', 18, 'Apple', None]>>> [s.lower() for s in L]Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in <listcomp>AttributeError: 'int' object has no attribute 'lower'
若是list中既包含字符串,又包含整數,因爲非字符串類型沒有lower()方法,因此列表生成式會報錯:
使用內建的isinstance函數能夠判斷一個變量是否是字符串:
isinstance(x, str)
L2 = [s.lower() for s in L1 if isinstance (s , str)]
生成器
列表容量是有限的。
列表的元素按照算法推算出
generator:一邊循環一邊計算
把列表表達式中的[]變爲()
打印:next ()
也可使用循環。
g = (x * x for x in range (10))for i in g print (i)
若是推算的算法比較複雜,用相似列表生成式的for循環沒法實現的時候,還能夠用函數來實現。
def fib (max) n , a , b = 0 , 0 , 1 while n < max : print(b) a , b = b , a + b n = n + 1 return 'done'
a , b = b , a + b
至關於
t = (b , a + b)a = t[0]b = t[1]
要把fib函數變成generator,只須要把print(b)改成yield b就能夠了:
fib函數其實是定義了斐波拉契數列的推算規則,能夠從第一個元素開始,推算出後續任意的元素,這種邏輯其實很是相似generator。
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>
最難理解的就是generator和函數的執行流程不同。函數是順序執行,遇到return語句或者最後一行函數語句就返回
而變成generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行。
可是用for循環調用generator時,發現拿不到generator的return語句的返回值,由於每次在yield處就中斷了。
若是想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:
>>> g = fib(6)>>> while True:... try:... x = next(g)... print('g:', x)... except StopIteration as e:... print('Generator return value:', e.value)... break...
普通函數調用直接返回結果:
generator函數的「調用」實際返回一個generator對象:
迭代器
能夠做用於for循環的主要有以下兩種:
- 集合數據類型:list,tuple,dict , set , str
- generator : 生成器和帶yield的generate function
能夠直接做用於for循環的對象,統稱爲可迭代對象:Iterable
可使用isinstance()判斷一個對象是不是Iterable對象:
>>> from collections import Iterable
>>> isinstance([], Iterable)
生成器不但能夠做用於for循環,還能夠被next()函數不斷調用並返回下一個值,直到最後拋出StopIteration錯誤表示沒法繼續返回下一個值了。
能夠被next()函數調用並不斷返回下一個值的對象稱爲迭代器:Iterator。
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
生成器都是Iterator對象,但list、dict、str雖然是Iterable,卻不是Iterator。
把list、dict、str等Iterable變成Iterator可使用iter()函數:
>>> isinstance(iter([]), Iterator)
True
爲何list、dict、str等數據類型不是Iterator?
由於Python的Iterator對象表示的是一個數據流,Iterator對象能夠被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。能夠把這個數據流看作是一個有序序列,但咱們卻不能提早知道序列的長度,只能不斷經過next()函數實現按需計算下一個數據,因此Iterator的計算是惰性的,只有在須要返回下一個數據時它纔會計算。
Iterator甚至能夠表示一個無限大的數據流,例如全體天然數。而使用list是永遠不可能存儲全體天然數的。
小結
凡是可做用於for循環的對象都是Iterable類型;
凡是可做用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;
集合數據類型如list、dict、str等是Iterable但不是Iterator,不過能夠經過iter()函數得到一個Iterator對象。
for循環本質上就是經過不斷調用next()函數實現的
# 首先得到Iterator對象:it = iter([1, 2, 3, 4, 5])# 循環:while True: try: # 得到下一個值: x = next(it) except StopIteration: # 遇到StopIteration就退出循環 break
函數式編程
而函數式編程(請注意多了一個「式」字)——Functional Programming,
在計算機的層次上,CPU執行的是加減乘除的指令代碼,以及各類條件判斷和跳轉指令,因此,彙編語言是最貼近計算機的語言。
而計算則指數學意義上的計算,越是抽象的計算,離計算機硬件越遠。
對應到編程語言,就是越低級的語言,越貼近計算機,抽象程度低,執行效率高,好比C語言;越高級的語言,越貼近計算,抽象程度高,執行效率低,好比Lisp語言。
高階函數
Higher-order function
變量能夠指向函數
abs(-10)是函數調用,而abs是函數自己。
f = abs
結論:函數自己也能夠賦值給變量,即:變量能夠指向函數。
>>> f = abs
>>> f(-10)
10
直接調用abs()函數和調用變量f()徹底相同。
函數名也是變量
對於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函數,請重啓Python交互環境。
注:因爲abs函數其實是定義在import builtins模塊中的,因此要讓修改abs變量的指向在其它模塊也生效,要用import builtins; builtins.abs = 10。
傳入函數
那麼一個函數就能夠接收另外一個函數做爲參數,這種函數就稱之爲高階函數。
def add(x, y, f):return f(x) + f(y)
add(-5, 6, abs)
x = -5
y = 6
f = abs
f(x) + f(y) ==> abs(-5) + abs(6) ==> 11return 11
map/reduce
map()函數接收兩個參數,一個是函數,一個是Iterable,
map將傳入的函數依次做用到序列的每一個元素,並把結果做爲新的Iterator返回。
咱們有一個函數f(x)=x2,要把這個函數做用在一個list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就能夠用map()實現以下:
>>> def f(x):... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
因爲結果r是一個Iterator,Iterator是惰性序列,所以經過list()函數讓它把整個序列都計算出來並返回一個list。
map()做爲高階函數,事實上它把運算規則抽象了,所以,咱們不但能夠計算簡單的f(x)=x2,還能夠計算任意複雜的函數
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce把一個函數做用在一個序列[x1, x2, x3, ...]上,這個函數必須接收兩個參數,reduce把結果繼續和序列的下一個元素作累積計算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
序列求和:
>>> from functools import reduce
>>> def add(x, y):... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
若是要把序列[1, 3, 5, 7, 9]變換成整數13579,reduce就能夠派上用場:
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
果考慮到字符串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]...>>> reduce(fn, map(char2num, '13579'))13579
整理成一個str2int的函數就是:
from functools import reducedef 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 reducedef 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))
from functools import reducedef str2float(L): dotIndex=L.find('.') times=len(L)-dotIndex-1 s=L.replace('.','') 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 fn(x, y): return x * 10 + y L=reduce(fn, map(char2num, s)) print ('times = ', pow(10,times)) return L/pow(10 , times)print(str2float('123.456'))
filter
過濾序列。
接收一個序列。
把傳入的函數做用於每一個元素,根據返回值是ture和False決定保留仍是丟棄。
刪除偶數,保留奇數。
def is_odd(n):return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
若是爲false,則刪除。
s.strip(rm) 刪除s字符串中開頭、結尾處,位於 rm刪除序列的字符
當rm爲空時,默認刪除空白符(包括'\n', '\r', '\t', ' ')
把空字符串刪掉。
def not_empty(s):return s and s.strip()
list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
注意到filter()函數返回的是一個Iterator,也就是一個惰性序列,因此要強迫filter()完成計算結果,須要用list()函數得到全部結果並返回list。
用filter求素數
埃氏篩法
首先,列出從2開始的全部天然數,構造一個序列:
取序列的第一個數2,它必定是素數,而後用2把序列的2的倍數篩掉:
取新序列的第一個數3,它必定是素數,而後用3把序列的3的倍數篩掉:
取新序列的第一個數5,而後用5把序列的5的倍數篩掉:
構造從3開始的奇數序列
def _odd_iter():
n = 1while True:
n = n + 2yield n
注意這是一個生成器,而且是一個無限序列。
定義一個篩選函數:
def _not_divisible(n): return lambda x: x % n > 0
最後,定義一個生成器,不斷返回下一個素數:
def primes(): yield 2 it = _odd_iter() # 初始序列 while True: n = next(it) # 返回序列的第一個數 yield n it = filter(_not_divisible(n), it) # 構造新序列
這個生成器先返回第一個素數2,而後,利用filter()不斷產生篩選後的新的序列。
因爲primes()也是一個無限序列,因此調用時須要設置一個退出循環的條件:
for n in primes(): if n < 1000: print(n) else: break
注意到Iterator是惰性計算的序列,因此咱們能夠用Python表示「全體天然數」,「全體素數」這樣的序列,而代碼很是簡潔。
filter()的做用是從一個序列中篩出符合條件的元素。因爲filter()使用了惰性計算,因此只有在取filter()結果的時候,纔會真正篩選並每次返回下一個篩出的元素。
sorted
高階函數:
sorted([
36
,
5
, -
12
,
9
, -
21
], key=abs)
默認狀況下,對字符串排序,是按照ASCII的大小比較的,因爲'Z' < 'a',結果,大寫字母Z會排在小寫字母a的前面。
忽略大小寫:只要咱們能用一個key函數把字符串映射爲忽略大小寫排序便可。忽略大小寫來比較兩個字符串,實際上就是先把字符串都變成大寫(或者都變成小寫)
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
反向排序:
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
sorted()也是一個高階函數。用sorted()排序的關鍵在於實現一個映射函數。
返回函數
函數做爲返回值。
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時,才真正計算求和的結果:
在這個例子中,咱們在函數lazy_sum中又定義了函數sum,而且,內部函數sum能夠引用外部函數lazy_sum的參數和局部變量,當lazy_sum返回函數sum時,相關參數和變量都保存在返回的函數中,這種稱爲「閉包(Closure)」的程序結構擁有極大的威力。
當咱們調用lazy_sum()時,每次調用都會返回一個新的函數,即便傳入相同的參數:
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False
閉包
注意到返回的函數在其定義內部引用了局部變量args,因此,當一個函數返回了一個函數後,其內部的局部變量還被新函數引用,因此,閉包用起來簡單,實現起來可不容易
返回的函數並無馬上執行,而是直到調用了f()才執行。
另外一個須要注意的問題是,返回的函數並無馬上執行,而是直到調用了f()才執行
def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fsf1, f2, f3 = count()
每次循環,都建立了一個新的函數,而後,把建立的3個函數都返回了。
你可能認爲調用f1(),f2()和f3()結果應該是1,4,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
返回一個函數時,牢記該函數並未執行,返回函數中不要引用任何可能會變化的變量。
匿名函數
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
匿名函數lambda x: x * x實際上就是:
關鍵字lambda表示匿名函數,冒號前面的x表示函數參數。
匿名函數有個限制,就是隻能有一個表達式,不用寫return,返回值就是該表達式的結果。
此外,匿名函數也是一個函數對象,也能夠把匿名函數賦值給一個變量,再利用變量來調用該函數
裝飾器
函數對象有一個__name__屬性,能夠拿到函數的名字:
假設咱們要加強now()函數的功能,好比,在函數調用先後自動打印日誌,但又不但願修改now()函數的定義,這種在代碼運行期間動態增長功能的方式,稱之爲「裝飾器」(Decorator)。
decorator就是一個返回函數的高階函數。因此,咱們要定義一個能打印日誌的decorator,能夠定義以下:
def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper
觀察上面的log,由於它是一個decorator,因此接受一個函數做爲參數,並返回一個函數。咱們要藉助Python的@語法,把decorator置於函數的定義處:
@logdef now(): print('2015-3-25')
把@log放到now()函數的定義處,至關於執行了語句:
now = log(now)
因爲log()是一個decorator,返回一個函數,因此,原來的now()函數仍然存在,只是如今同名的now變量指向了新的函數,因而調用now()將執行新函數,即在log()函數中返回的wrapper()函數。
wrapper()函數的參數定義是(*args, **kw),所以,wrapper()函數能夠接受任意參數的調用。在wrapper()函數內,首先打印日誌,再緊接着調用原始函數。
若是decorator自己須要傳入參數,那就須要編寫一個返回decorator的高階函數,寫出來會更復雜。好比,要自定義log的文本:
def log(text): def decorator(func): def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator
@log('execute')def now(): print('2015-3-25')
偏函數
Partial function
能夠定義一個int2()的函數,默認把base=2傳進去:
functools.partial就是幫助咱們建立一個偏函數的,不須要咱們本身定義int2(),能夠直接使用下面的代碼建立一個新的函數int2:
>>> import functools>>> int2 = functools.partial(int, base=2)>>> int2('1000000')64>>> int2('1010101')85
至關於
kw = { 'base': 2 }
int('10010', **kw)
max2 = functools.partial(max, 10)
實際上會把10做爲*args的一部分自動加到左邊,也就是
至關於
args = (10, 5, 6, 7)max(*args)
模塊
咱們把不少函數分組,分別放到不一樣的文件裏面。
本身在編寫模塊時,沒必要考慮名字會與其餘模塊衝突
若是不一樣的人編寫的模塊名相同怎麼辦?爲了不模塊名衝突,Python又引入了按目錄來組織模塊的方法,稱爲包(Package)。
方法是選擇一個頂層包名,好比mycompany,
每個包目錄下面都會有一個__init__.py的文件,這個文件是必須存在的,不然,Python就把這個目錄當成普通目錄,而不是一個包。__init__.py能夠是空文件,也能夠有Python代碼,由於__init__.py自己就是一個模塊,而它的模塊名就是mycompany。
使用模塊
Mac或Linux上有可能並存Python 3.x和Python 2.x,所以對應的pip命令是pip3。
確保安裝時勾選了pip和Add python.exe to Path。
生成縮略圖
>>> from PIL import Image>>> im = Image.open('test.png')>>> print(im.format, im.size, im.mode)PNG (400, 300) RGB>>> im.thumbnail((200, 100))>>> im.save('thumb.jpg', 'JPEG')
其餘經常使用的第三方庫還有MySQL的驅動:
mysql-connector-python,用於科學計算的NumPy庫:numpy,用於生成文本的模板工具Jinja2,等等。
當咱們試圖加載一個模塊時,Python會在指定的路徑下搜索對應的.py文件
Python解釋器會搜索當前目錄、全部已安裝的內置模塊和第三方模塊,搜索路徑存放在sys模塊的path變量中:
>>> import sys
>>> sys.path
添加本身的目錄:
- 修改sys.path
- 第二種方法是設置環境變量PYTHONPATH
面向對象編程
class Student(object):pass
class後面緊接着是類名,即Student,類名一般是大寫開頭的單詞,緊接着是(object),表示該類是從哪一個類繼承下來的,繼承的概念咱們後面再講,一般,若是沒有合適的繼承類,就使用object類,這是全部類最終都會繼承的類。
class Student(object): def __init__(self, name, score): self.name = name self.score = score
注意到__init__方法的第一個參數永遠是self,表示建立的實例自己,所以,在__init__方法內部,就能夠把各類屬性綁定到self,由於self就指向建立的實例自己。
有了__init__方法,在建立實例的時候,就不能傳入空的參數了,必須傳入與__init__方法匹配的參數,但self不須要傳,Python解釋器本身會把實例變量傳進去:
和普通的函數相比,在類中定義的函數只有一點不一樣,就是第一個參數永遠是實例變量self,而且,調用時,不用傳遞該參數
訪問限制
內部屬性不被外部訪問。把屬性的名稱前加上兩個下劃線__,
原先那種直接經過bart.score = 59也能夠修改啊,爲何要定義一個方法大費周折?由於在方法中,能夠對參數作檢查,避免傳入無效的參數:
繼承和多態
新的class稱爲子類(Subclass),而被繼承的class稱爲基類、父類或超類(Base class、Super class)。
繼承原來的代碼,能夠作必定改進。
子類的run()覆蓋了父類的run()
多態:
由於Dog是從Animal繼承下來的,當咱們建立了一個Dog的實例c時,咱們認爲c的數據類型是Dog沒錯,但c同時也是Animal也沒錯,Dog原本就是Animal的一種!
當咱們定義一個class的時候,咱們實際上就定義了一種數據類型
在繼承關係中,若是一個實例的數據類型是某個子類,那它的數據類型也能夠被看作是父類。可是,反過來就不行:
多態的好處就是,當咱們須要傳入Dog、Cat、Tortoise……時,咱們只須要接收Animal類型就能夠了,由於Dog、Cat、Tortoise……都是Animal類型,而後,按照Animal類型進行操做便可。因爲Animal類型有run()方法,所以,傳入的任意類型,只要是Animal類或者子類,就會自動調用實際類型的run()方法,這就是多態的意思:
對於一個變量,咱們只須要知道它是Animal類型,無需確切地知道它的子類型,就能夠放心地調用run()方法,而具體調用的run()方法是做用在Animal、Dog、Cat仍是Tortoise對象上,由運行時該對象的確切類型決定,這就是多態真正的威力:
調用方只管調用,無論細節,而當咱們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的「開閉」原則:
對擴展開放:容許新增Animal子類;
對修改封閉:不須要修改依賴Animal類型的run_twice()等函數。
靜態語言VS動態語言
對於靜態語言(例如Java)來講,若是須要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,不然,將沒法調用run()方法。
對於Python這樣的動態語言來講,則不必定須要傳入Animal類型。咱們只須要保證傳入的對象有一個run()方法就能夠了:
這就是動態語言的「鴨子類型」,它並不要求嚴格的繼承體系,一個對象只要「看起來像鴨子,走起路來像鴨子」,那它就能夠被看作是鴨子
獲取對象信息
使用type()
果咱們要在if語句中判斷,就須要比較兩個變量的type類型是否相同:
>>> type(fn)==types.FunctionTypeTrue>>> type(abs)==types.BuiltinFunctionTypeTrue>>> type(lambda x: x)==types.LambdaTypeTrue>>> type((x for x in range(10)))==types.GeneratorTypeTrue
isinstance()
object -> Animal -> Dog -> Husky
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Animal)
True
dir()
若是要得到一個對象的全部屬性和方法,可使用dir()函數,它返回一個包含字符串的list,
好比__len__方法返回長度。在Python中,若是你調用len()函數試圖獲取一個對象的長度,實際上,在len()函數內部,它自動去調用該對象的__len__()方法,因此,下面的代碼是等價的:
>>> len('ABC')3>>> 'ABC'.__len__()3
僅僅把屬性和方法列出來是不夠的,配合getattr()、setattr()以及hasattr(),咱們能夠直接操做一個對象的狀態:
咱們能夠對任意一個Python對象進行剖析,拿到其內部的數據。要注意的是,只有在不知道對象信息的時候,咱們纔會去獲取對象信息
能夠用
就不要寫:python
sum = getattr(obj, 'x') + getattr(obj, 'y')
假設咱們但願從文件流fp中讀取圖像,咱們首先要判斷該fp對象是否存在read方法,若是存在,則該對象是一個流,若是不存在,則沒法讀取。hasattr()就派上了用場。
實例屬性和類屬性
>>> class Student(object):... name = 'Student'...>>> s = Student() # 建立實例s>>> print(s.name) # 打印name屬性,由於實例並無name屬性,因此會繼續查找class的name屬性Student>>> print(Student.name) # 打印類的name屬性Student>>> s.name = 'Michael' # 給實例綁定name屬性>>> print(s.name) # 因爲實例屬性優先級比類屬性高,所以,它會屏蔽掉類的name屬性Michael>>> print(Student.name) # 可是類屬性並未消失,用Student.name仍然能夠訪問Student>>> del s.name # 若是刪除實例的name屬性>>> print(s.name) # 再次調用s.name,因爲實例的name屬性沒有找到,類的name屬性就顯示出來了Student
從上面的例子能夠看出,在編寫程序的時候,千萬不要把實例屬性和類屬性使用相同的名字,由於相同名稱的實例屬性將屏蔽掉類屬性,可是當你刪除實例屬性後,再使用相同的名稱,訪問到的將是類屬性。
使用__slots__
class Student(object): pass
>>> s = Student()>>> s.name = 'Michael' # 動態給實例綁定一個屬性>>> print(s.name)Michael
可是,給一個實例綁定的方法,對另外一個實例是不起做用的:
爲了給全部實例都綁定方法,能夠給class綁定方法:
一般狀況下,上面的set_score方法能夠直接定義在class中,但動態綁定容許咱們在程序運行的過程當中動態給class加上功能,這在靜態語言中很難實現。
可是若是想限制實例的屬性
定義一個特殊的__slots__變量,來限制該class實例能添加的屬性:
class Student(object): __slots__ = ('name', 'age') # 用tuple定義容許綁定的屬性名稱
使用__slots__要注意,__slots__定義的屬性僅對當前類實例起做用,對繼承的子類是不起做用的:
除非在子類中也定義__slots__,這樣,子類實例容許定義的屬性就是自身的__slots__加上父類的__slots__。
訪問數據庫
在Mac或Linux上,須要編輯MySQL的配置文件,把數據庫默認的編碼所有改成UTF-8。MySQL的配置文件默認存放在/etc/my.cnf或者/etc/mysql/my.cnf:
[client]default-character-set = utf8[mysqld]default-storage-engine = INNODBcharacter-set-server = utf8collation-server = utf8_general_ci
mysql> show variables like '%char%';
安裝驅動:
pip install mysql-connector
# 導入MySQL驅動:>>> import mysql.connector# 注意把password設爲你的root口令:>>> conn = mysql.connector.connect(user='root', password='password', database='test')>>> cursor = conn.cursor()# 建立user表:>>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')# 插入一行記錄,注意MySQL的佔位符是%s:>>> cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael'])>>> cursor.rowcount1# 提交事務:>>> conn.commit()>>> cursor.close()# 運行查詢:>>> cursor = conn.cursor()>>> cursor.execute('select * from user where id = %s', ('1',))>>> values = cursor.fetchall()>>> values[('1', 'Michael')]# 關閉Cursor和Connection:>>> cursor.close()True>>> conn.close()
附件列表