# 第一優(yōu)先級規(guī)則聲明:
# 除了夢境万伤,每一個意識主進程都必須與一個身體參與的機械進程相匹配,否則結束意識主進程呜袁。如學習python同時必須伴有記筆記敌买、敲代碼等機械進程,學習英語必須伴有朗讀阶界、聽說虹钮、查字典、查語法書等機械進程跟隨膘融,拆解問題必須在紙上或iPad上實時記錄意識的每一次嘗試芙粱。
如果身體狀況或是客觀情況不允許有機械進程存在,請轉入其他進程氧映,如注意力恢復(閉目養(yǎng)神春畔、睡覺)或是娛樂進程(健身、看電影等)岛都。
# 此優(yōu)先級將在每一個意識進程前聲明律姨。
Day1:
Why Python 3.5(意義賦予):?
- Life is short and I need python.?
- 以前沒有編程基礎,只有Linux和MySQL的基礎臼疫。
- 每天能抽出時間不多择份。
- 工作的強烈需求。
- Python大量的第三方庫
- 開源社區(qū)的強力支持
- 3.5總比2.7強烫堤,長遠來看
Installation: 略
IDLE: Pycharm
編碼聲明:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
也可以借助pycharm右下角的編碼來修改荣赶,或是notepad++的encoding-轉為utf-8無BOM格式
I/O:
print()
print在python3.5中正式成為函數(shù),允許用“鸽斟,”分隔多個字符串拔创,也接受數(shù)學計算
input()
接受用戶的輸入,同時也接受()內插入內容湾盗。
如果要規(guī)范輸入的內容伏蚊,可以用這種方法
int(input(...))
代碼風格:
python使用縮進來組織代碼塊,請移動使用4個空格的縮進(pycharm會對不規(guī)范的代碼風格給出波浪下劃紅線的warning)
數(shù)據(jù)類型和變量:
類型在python中極其重要格粪,最常見的報錯就是“你不能對xx類型進行xx操作”躏吊!
整數(shù)integer:正整數(shù),負整數(shù)帐萎,0
浮點數(shù)float:小數(shù)(叫浮點數(shù)是因為用科學計數(shù)法表示比伏,一個浮點數(shù)的小數(shù)點位置可變),存在四舍五入的誤差疆导。
字符串string:單引號或者雙引號括起來的任意文本(里邊有特殊符號請轉義)赁项。需要轉義的入托太多,用r'string' 來帶來一大堆的\。計算string多長悠菜,用len()
布爾值:True/False 1/0舰攒。如果True/False拼錯了或者大小寫出錯,并不會識別為布爾值(在pycharm中識別成為橙色)悔醋∧η裕可加減乘除,可and/or/not芬骄。
空值:None
變量賦值符號是“=”猾愿,等于號是‘==’,所以請正確理解
x = 3
x = x + 3
賦值過程依照語句順序而來账阻。
常量:不能變的變量
Python支持多種數(shù)據(jù)類型蒂秘,可以把任何數(shù)據(jù)都看成一個對象,
- 變量就是在程序中用來指向這些數(shù)據(jù)對象的淘太。
- 對變量賦值就是把數(shù)據(jù)和變量給關聯(lián)起來
字符串和編碼
Python3支持中文
1個中文字符經(jīng)過utf-8編碼后占用3個字節(jié)姻僧,一個英文字符占用1個字節(jié)。
格式化:
格式化的使用頻率非常高琴儿,其應用主要在于占位符的使用
%s 字符串(如果不清楚數(shù)據(jù)類型段化,可以用%s來接受一切)
%d 整數(shù) (整數(shù)接受%002d這種表達,來填充位數(shù))
%f 浮點數(shù)(浮點數(shù)接受%.3f這種表達造成,來截取位數(shù))
%s 字符串
%x 十六進制整數(shù)
%%來表示正常的百分比
條件判斷和循環(huán)
計算機可以自動化,其中最重要的原因就是因為有條件判斷和循環(huán)
條件判斷
(注意冒號P巯)
if <條件判斷1>:
<執(zhí)行1>
elif <條件判斷2>:
<執(zhí)行2>
elif <條件判斷3>:
<執(zhí)行3>
else:
<執(zhí)行4>
if 語句從上往下判斷晒屎,如果某個判斷是True,執(zhí)行完畢就不執(zhí)行了缓升。
循環(huán)
1. 遍歷式循環(huán)(for x in ...)
for i in ... :
.......
2. while循環(huán): 只要條件滿足鼓鲁,就不斷循環(huán)(類似scratch里用if和forever在循環(huán)中結合的用法)
不過代碼會出現(xiàn)死循環(huán)(如果寫的有問題),就像Human Resource Machine里小人不停地搬箱子港谊。這時候可用ctrl+c結束(與linux一致)
List和Tuple:
list: 列表骇吭,python內置。是一種有序的元素集合(或者說容器)歧寺,元素是什么數(shù)據(jù)類型都可以可以隨時添加和刪除其中的元素()燥狰。同樣,可以用len()獲取list中的個數(shù)(string也能進行l(wèi)en()操作斜筐,因為string本質上也是list)
訪問list中的每個元素需要通過索引(索引:從0開始的序列號)龙致,格式為list[1],超出索引報錯IndexError顷链。也可以通過負數(shù)的索引號來倒取元素
支持list.append()目代,追加元素到末尾
支持list.insert(index, 'element'),插入元素到指定位置。
支持空List榛了,且len(List)為0
因為list是可變的在讶,所以才可以list.sort()來排序
Tuple: 不可變的list是tuple, 不能append/insert操作,可以用索引取值霜大,但不能再次賦值了真朗。
tp = ()
tp = (1,) 如果要定義的是tuple, 務必通過括號內逗號來消除歧義。
Dict和Set
dict
其他語言中也稱之為map, 使用key-value存儲僧诚,優(yōu)點是查找速度極快遮婶。
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
為什么查找dict特別快,比查找list要開快得多呢? list查詢是遍歷查詢湖笨,從第一個翻到最后一個旗扑,查詢速度直接取決于list大小。dict直接計算出對應的位置慈省,直接取出臀防,非常快边败。根據(jù)什么算出來呢袱衷?就是key-value。
dict有個缺點:占用內存大笑窜。正因為如此致燥,拿空間換時間,占用空間大排截,所以查找速度快嫌蚤。
既然是通過Key來查找,那么key一定要是不可變對象断傲。通過key來計算位置的算法脱吱,就叫做hash算法。
要保證hash正確性认罩,那么key就不可變(如字符串和整數(shù))箱蝠。但是list這種就是可變的,就不能作為Key垦垂。這時候報錯就是Typeerror: unhasable type
d['barry'] = 90 這個過程跟給變量賦值沒什么區(qū)別宦搬,相當于給key這個變量賦值。
調用不存在key的乔外,會返回keyerror床三。
怎么判斷Key在不在dict里呢?
1. 可以通過key in dict來判斷杨幼,會返回布爾值撇簿。
2. 也可以d.get['barry']聂渊。如果key不存在,可以返回None, 或者自己制定的value
d.get['key', value]
要刪除一個Key四瘫,就d.pop('key').
既然是一個字典dict汉嗽,那么哪一對key-value在前面是無所謂的。也就是說找蜜,無所謂key-value的順序饼暑,什么時候放入的不重要。
set
set和dict唯一的區(qū)別就是洗做,只存儲Key弓叛,不存儲value。因為dict內部key就不能重復诚纸,所以set里邊的元素也不能重復(即使重復撰筷,也會自動會被過濾)。一樣畦徘,單純的key集合也無所謂先后順序毕籽。
這樣,才形成了set數(shù)學意義上的無序+無重復元素的集合井辆。
s = set(list)
list = [1, 2, 3]
可以通過add(key)來添加元素(key)到set當中关筒,重復添加倒是可以,不過還是會被過濾掉杯缺。
可以通過remove(key)來刪除元素蒸播。
可以s1 & s2 取交集,s1 | s2取并集夺谁。(跟linux非常相似)
可變和不可變
不可變對象(如string)調用對象自身的任意方法廉赔,也不會改變對象自身的內容。相反這些方法會創(chuàng)建新的對象然后返回匾鸥,這樣就保證了不可變對象的不便。
函數(shù)
函數(shù)是啥碉纳?函數(shù)本質上就是把一些現(xiàn)成的功能寫成一個封裝的包(跟第三方庫非常像)勿负,調用即可。也就是說劳曹,函數(shù)可以自己寫奴愉,也可以用寫好的現(xiàn)成的。比如不用每次都寫平方或者指數(shù)不需要每次都寫x * x...這種底層寫法铁孵。很多函數(shù)內置锭硼,很多函數(shù)在可調用庫。(import大法好M扇啊)
抽象
抽象是啥檀头?每次都還原成最底層代碼煩不煩轰异?那你需要抽象!
抽象可以使我們每次都調用一些聚合過的概念暑始,如求和西格瑪符號搭独。
有好多內置函數(shù)(build-in functions)筷屡,可以直接調用鸡捐。(至少讀一遍,知道都有啥闹司。Python真的是能夠做到嗤朴,你能想到的函數(shù)配椭,99%都有之前人寫過,stack overflow/github/python各種庫雹姊,多讀讀)
Built-in functions:
數(shù)學運算類:
abs(x) 取x的絕對值
divmod(a, b) 返回(商股缸,余數(shù))
float(x) 取浮點數(shù) ;bool() 取布爾值容为; str()轉換成字符乓序; int() 轉換成正整數(shù)
int(x, base=n) 取整數(shù),base是可選的坎背,表示進制) 也可以long(x, base=n) 轉Long
pow(x, y, z) x的y次方(如果z存在替劈,那么再對結果取模)
range([start], stop[, step]) 產(chǎn)生一個序列,規(guī)定結束得滤,可以規(guī)定開始和步長陨献。
round(x[,n) 四舍五入,可以規(guī)定n位數(shù)
sum(iterable[, start]) 對可迭代對象求和
集合操作符
iter(o[, sentinel) 生成一個對象的迭代器懂更,第二個參數(shù)表示分隔符(眨业?)
max() min() 最大最小值
dict() set() list() str() tupe()創(chuàng)建
sorted(iterable, key=None, reverse=True/False) 排序
all(iterable) 集合中的所有 元素都為真時候為真,空串返回為真沮协。
any(iterable) 任一為真就為真
IO操作
input() 獲取用戶輸入
open(file_name, mode) mode可以是'r', 'w', 'a'
print() 打印函數(shù)
神器
help()
函數(shù)
自己動手寫函數(shù)
def my_abs(x):
if x > = 0:
return x
else:
return -x
邏輯是: 執(zhí)行到return就結束了龄捡。沒有return,函數(shù)執(zhí)行完畢慷暂,返回None
如果某一個步驟沒想好聘殖,還可以直接pass(放過),占著位置
一個函數(shù)的功能完善行瑞,包括三方面的完善: stdin, stdout, stderror奸腺。所以如果我們要自己定義一個完善的函數(shù),必須包括這三個方面血久。要不然調用函數(shù)錯誤突照,都不知道為什么錯誤。
def my_abs(x):
if not isinstace(x, (int, float)):
raise TypeError(‘bad operand')
if x>= 0:
return x
if x < 0:
return -x
(isinstance(x, type)是判斷x是否為type的函數(shù))
很多函數(shù)都return一個值氧吐,那么可以return多個值么讹蘑?可以末盔,但是返回的本質上是個tuple
比如剛剛學到的科學計算函數(shù)divmod(x, y), 返回的就是兩個值(商,余數(shù))衔肢,但是如果你去嘗試type()庄岖,就會看到這是一個tuple
type(divmod(5, 2)
Out: tuple
函數(shù)的參數(shù)
對于日常使用來說,因為python大量的build-in functions和變態(tài)的import功能角骤,我們非常少去自己寫一個函數(shù)隅忿。更多的是理解自己調用的函數(shù)該如何使用——核心就在于,函數(shù)的參數(shù)的了解和使用邦尊。日常使用的大量報錯都是因為:對于某個函數(shù)是否能夠接受某種方式傳入的某個參數(shù)的不確定背桐,導致大量debug時間(隨機嘗試不是個長期解決方案,雖然能夠不斷地返回error信息以供調試)
1. 位置參數(shù)
簡單理解蝉揍,位置固定的參數(shù)链峭,如pow(x, y)計算x的y次方。直接寫pow(3, 2)不再需要規(guī)定哪個是x, 哪個是y
2. 默認參數(shù): 大大減輕了函數(shù)調用的難度
比如range(5)又沾,實際上start位置的參數(shù)就被忽略了弊仪,只傳入了stop。但是仍然能運行杖刷,因為range()內部規(guī)定了start默認為0励饵。
也可以不接受默認設定,自己規(guī)定:range(2, 5)
但是在自己定義函數(shù)的時候滑燃,必選參數(shù)在前役听,默認參數(shù)在后。且默認參數(shù)指向不可變對象(不能指向個變量什么的!)
3. 可變參數(shù)
并不規(guī)定要傳入幾個參數(shù)表窘,多少個都行典予。define的時候加個星號即可
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
這樣numbers里邊有多少個都沒問題。
如果已經(jīng)有個numbers集合(list或者tuple)咋辦乐严?
num = [1, 2, 3]
calc(*num)
這種寫法瘤袖,就是把num這個list中所有元素都傳入calc()進行計算。如果不加*會咋樣昂验?
TypeError: can't multiply sequence by non-int of type 'list'
原理上孽椰,可變參數(shù)允許你傳入 0 個或任意個參數(shù),這些可變參數(shù)在函數(shù)調用時自動組裝為一個 tuple凛篙。
4. 關鍵字參數(shù)
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
除了規(guī)定的name和age, 還接受擴展性的其他參數(shù)。這就被稱為關鍵字參數(shù)
比如用戶除了必填項之外栏渺,還愿意提供其他參數(shù)呛梆,那么就可以通過關鍵字參數(shù)來擴展。
5. 命名關鍵字參數(shù)(限制了可擴展的關鍵字)
def person(nama, age, *, city, job)
這里的星號后邊的city和job就是命名關鍵字參數(shù)磕诊,也就是限定了的關鍵字參數(shù)填物。(不加星號纹腌,就變成了位置參數(shù))
語法風格
**args: 可變參數(shù),接受的是一個tuple
**kw: 關鍵字參數(shù)滞磺,kw接收的是一個dict
可變參數(shù)既可以直接傳入func(1, 2, 3), 又可以先組裝list或tuple升薯,再通過**args傳入func(*(1, 2, 3))
關鍵字參數(shù)既可以直接傳入 func(a=1, b=2), 又可以先組裝dict, 再通過**kw傳入 func(**{'a': 1, 'b': 2})
切片
list切片 L[1:3]這種方式,包前不包后击困。也可以L[:3]或者L[2:]涎劈。L[:]就是區(qū)這個List本身
實際上,切片的規(guī)則是list[start:stop:step]
list的切片結果是List阅茶,tuple的切片結果是tuple蛛枚。
string也是一種List
迭代:
通過for循環(huán)來遍歷list或是tuple或是其他可迭代對象,這邊遍歷我們稱為迭代iteration脸哀。
python中蹦浦,迭代是通過for...in完成的。
像dict這種類型撞蜂,默認迭代的是key盲镶。想迭代value, 可以for value in d.values()。同時要迭代key和value, 可以for k, v in d.items()
只要是可迭代對象蝌诡,就可迭代溉贿。判斷是否是可迭代對象,方法是
from collections import Iterable
isinstance('abc', Iterable) 前面我們學過了isinstance函數(shù)
列表生成式List comprehesions
顧名思義送漠,就是為了生成List的(list幾乎是應用最為廣泛的類型) [x * x for x in range(1, 11)]
要生成的元素放在前面顽照,后邊跟for循環(huán)(其實就是for循環(huán)的一種簡寫)
還可以加上if(但是不接受else)
x * x for x in range(1, 11) if x % 2 ==0
m + n for m in 'ABC' for n in 'XYZ'
利用這個列表生成式,可以生成非常簡介的代碼闽寡,比如生成當前目錄下所有的文件名稱
import os
[d for d in os.list('.')]
生成器generator
列表生成式雖然生成方便(一個規(guī)則+for循環(huán)[+if語句])代兵,但是問題也很明顯。
你生成辣么大的列表爷狈,占著內存植影,留著過年么?又不見得全都用得上涎永!
這時候你就需要個generator, 也就是 邊生成思币,邊循環(huán),邊計算羡微。
符號上跟列表生成式的區(qū)別只有一個
List = [x * x for x in range(10)]
Generator = (x * x for x in range(10))
對谷饿,沒看錯,就是把中括號變成了小括號妈倔,就從list變成了generator博投。(如果你去type()他們,會看到區(qū)別)盯蝴。
調用列表當然很簡單毅哗,怎么調用generator呢听怕。怎么個生成法呢?
用next(generator)來調用虑绵,調用一次生成一次尿瞭。也就是每次next(generator)結果都不一樣,next()到最后一步翅睛,拋出Stopiteration錯誤声搁。不過你也看出來了,也不能每次都next()宏所,啥時候是個頭八盅蕖?(實際上幾乎用不上next())
所幸爬骤,一個generator仍然能被for循環(huán)調用充石。
for n in generator:
? ? print(n)
那么問題來了:挖掘機技術哪家強?如果列表生成式這種簡單的結構霞玄,描述不了一個generator的算法怎么辦骤铃?(當然能看出來,列表生成式雖然入手容易坷剧,不過干不了太復雜的事情——你看它那個傻樣連else都不接受)
好消息惰爬!好消息!用定義函數(shù)的方法就可以創(chuàng)建generator啦惫企!不過要把return改成yield撕瞧。(等用到再來填坑)
迭代器Iterator
前面說到,可直接作用于for循環(huán)的數(shù)據(jù)類型有
1. 集合數(shù)據(jù)類型:list, tuple, dict, set, str等
2. generator狞尔,包括生成器和帶yield的generator function
這些可以直接作用于for循環(huán)的對象丛版,叫Iterable可迭代對象。判斷類型有兩個方法:type()和isinstance(obj, type)
切記:判斷Iterable之前偏序,請先from collections import Iterable
可以用next()調用并不斷返回下一個值的對象成為迭代器Iterator页畦。想把Iterable對象編程iterator,可以使用Iter()函數(shù)研儒。
for循環(huán)本質上就是不斷調用next()實現(xiàn)的豫缨。以下兩個是等價的:
for x in [1, 2, 3, 4, 5]:
? ? pass
it = iter([1, 2, 3, 4, 5])
while True:
? ? try:
? ? ? ? x = next(it)
? ? except StopIteration
? ? ? ? break
高階函數(shù)
1. 變量可以指向函數(shù),函數(shù)的參數(shù)能接受變量端朵。
2. 所以好芭,一個函數(shù)局可以接收另一個函數(shù)作為參數(shù)。這種函數(shù)就稱為高階函數(shù)冲呢。
def add(x, y, f):
? ?return f(x) + f(y)
就是傳入兩個值栓撞,絕對值后再相加。
map/reduce
map(function, iterable)得到一個Iterator。map就是把一個函數(shù)瓤湘,作用于一個iteratable。
(想要讀取結果恩尾,可以list(iterator))
list(map(str, [1, 2, 3])) # 把列表里的123全部字符串化弛说。
reduce把一個函數(shù)作用在一個序列上,這個函數(shù)必須接受兩個參數(shù)翰意,reduce把結果繼續(xù)和序列的下一個元素做累積計算木人,其效果就是:
from functools import reduce
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
filter函數(shù)用于過濾序列,和map一樣冀偶,filter也接受一個函數(shù)和序列醒第,然后根據(jù)函數(shù)返回值是T/F來決定保留還是丟棄該元素。返回的也是惰性序列进鸠,需要list()稠曼。一樣,filter也是惰性計算客年,不調用不篩選霞幅。
sorted既是可以對list進行排序的函數(shù),也是個高階函數(shù)量瓜。
sorted([36, 5, -12, 9, -21], key=abs)
sorted(['bob', 'about', 'Zoo'], key=str.lower, reverse=True)
高階函數(shù)抽象能力強司恳,代碼簡潔
注意,這些高階函數(shù)因為是把一個函數(shù)分配到iterable里邊的每一個元素里邊了绍傲,所以不用再去考慮怎么樣調用每一個元素了。比如
L= [('Bob',75),('Adam',92),('Bart',66),('Lisa',88)]
def by_name(t):
return t[0].lower()
L2 = sorted(L, key=by_name)
print(L2)
不用再想怎么去調用每一個元素了猎塞,比如L[0][1]這種枫弟。。骇塘。
返回函數(shù)(坑款违,小特性群凶,待填充)
匿名函數(shù)
很多時候我們不需要顯式定義函數(shù),直接做一個匿名函數(shù)更方便,而且函數(shù)名還不會重復赠尾。
list(map(lambda x: x * x, [1, 2, 3, 4])
要比這么寫方便很多
def f(x):
? ? return x * x
關鍵字lambda表示匿名函數(shù)力穗,冒號前面x表示函數(shù)參數(shù)。但是只能有一個表達式气嫁,不用寫return当窗,返回值就是該表達式的結果。
也可以把匿名函數(shù)作為返回值返回
def build(x, y):
? ? return lambda: x * x + y * y
python的一些單條表達式適合用lambda函數(shù)寸宵,都是一些比較簡單的狀況崖面。
裝飾器Decorator(沒搞懂)
函數(shù)支持通過function_name.__name__來調用函數(shù)名稱(后面會詳細解析)
裝飾器就是在函數(shù)動態(tài)運行期間,給函數(shù)增加了的功能(其實就是返回函數(shù))梯影。
@log # 把log函數(shù)通過@方法巫员,放到now()前面,作為裝飾器出現(xiàn)甲棍。
def now():
? ? print('2016-10-01')
在面向對象的設計模式中简识,decorator被成為裝飾模式。OOP的裝飾模式需要通過繼承和組合來實現(xiàn)救军,而Python除了能支持OOP的decorator外财异,直接從語法層次支持decorator。Python的decorator可以用函數(shù)實現(xiàn)唱遭,也可以用類實現(xiàn)戳寸。(啥意思?)
偏函數(shù)
借助functools.partial(需要先import functools)拷泽,可以創(chuàng)建一個部分參數(shù)被固定的常用函數(shù)。這就稱為偏函數(shù)拆吆。
比如int(obj, base=n)。如果進制參數(shù)base的n常用(比如轉換成為16進制)捞奕,那么可以規(guī)定
int(obj, base=16)作為偏函數(shù)。使用方法如下
import functools
int2 = functools.partial(int, base=8)
但是院促,調用的時候如果主動改變規(guī)定好的默認參數(shù)渐溶,比如int2(int, base=16)掌猛,16進制轉換仍然有效废膘。
當函數(shù)的參數(shù)個數(shù)太多需要簡化的時候斋配,固定住某一些參數(shù),從而方便調用甩卓。
模塊
把函數(shù)分組逾柿,放到不同的.py文件里,每一個.py文件就叫做模塊弱匪。
調用模塊的最大好處在于,不需要重復制作輪子财搁,只要調用就好。
模塊會組成package(包)提茁,所以我們會說“調用第三方包/庫铃岔,說的就是這個意思。
包目錄下纺且,必須有__init__.py文件,否則python就不識別這個包目錄嫁艇,而是當成普通目錄。
切記:不能與系統(tǒng)自帶模塊重名歧斟,比如sys静袖。
使用模塊要先import sys队橙。
模塊的編寫(略)
安裝模塊
安裝第三方模塊,是通過pip工具
$ pip install pandas
調用的時候解总, 只import就只會在1. 當前目錄 2. 所有已經(jīng)安裝的內置模塊 3.第三方模塊 進行搜索刻盐,實際上遍歷了sys.path變量下的文件內容
import sys
sys.path
如果要暫時添加敦锌,用append()
sys.path.append('the directory you want to add')?
第二種方法設置環(huán)境變量PYTHONPATH乙墙,該環(huán)境變量的內容會被自動添加到模塊搜索路徑
(待填坑)
面向對象編程Object Orient Programming(OOP)
OOP把對象作為程序設計的基本單元,一個對象包含了數(shù)據(jù)和(操作數(shù)據(jù)的)函數(shù)哗魂。把計算機程序程序視為一組對象的集合朽色,而每個對象都可以接收其他對象發(fā)過來的消息抱冷,并處理這些消息旺遮。執(zhí)行程序就是在對象之間傳遞信息。
面向過程的程序設計把計算機程序視為一系列的命令集合,就是一組函數(shù)的順序執(zhí)行丈积。具體铛纬,就是把函數(shù)切分成為子函數(shù)棺弊。
python中所有數(shù)據(jù)類型都可以視為對象,當然也可以自定義對象缝驳。自定義的對象數(shù)據(jù)類型就是面向對象中的類(class)的概念用狱。
比如class是student, 是泛指學生這個概念,instance則是一個個具體的student, 張三李四溺忧。
所以,面向對象的設計思想是抽象出class歌溉,根據(jù)class創(chuàng)建instance痛垛。
面向對象的抽象成都又比函數(shù)要高,因為一個class既包含數(shù)據(jù)乾胶,又包含操作數(shù)據(jù)的方法。
面向對象三大特點:數(shù)據(jù)封裝喻频、繼承和多態(tài)
是時候祭出這張圖了锻煌!當當當當!
類和實例
類是抽象的模板,比如student類倦沧。實例是根據(jù)類創(chuàng)建出來的一個個具體的對象,每個對象只是數(shù)據(jù)不一樣而已告希,方法是相同的。
定義一個class
class Student(object):
? ? pass
注意:類名通常是首字母大寫的單詞,接下來是(object),表示該類是從哪個類繼承下來的檬贰。如果沒有,就繼承自object類萌踱,這是所有類最終都會繼承的類鸳粉。
定義好了Student類,就可以根據(jù)Student類創(chuàng)建出Student的實例艰山,創(chuàng)建實例是
barry = Student()
barry
<__main__.Student at 0x103d40d68>
后邊的0x103d40d6是內存地址,每個object地址都不一樣舔腾。
每個實例都可以自由綁定屬性,比如
barry.name = 'barry li'
這種方法對于實例雖然自由搂擦,但是沒有讓class起到模板的作用稳诚。我們要把class更加規(guī)范化,從而讓instance更加規(guī)范化瀑踢,這個方法就是定義__init__
class Student(object):
? ? def __init__(self, name, score):
? ? ? ? self.name = name
? ? ? ? self.score = score
__init__方法的第一個參數(shù)永遠是self扳还,表示創(chuàng)建的實例本身。因此橱夭,在__init__方法內部茬暇,就可以把各種屬性(name, score這種)綁定到self,因為self就指向創(chuàng)建的實例本身沧奴。
這樣定義完畢了肆资,就不能再barry = Studeng()了女器,比如傳入
barry = Student('barry li', '100') 來定義
barry.name輸出barry li
barry.score輸出100
def __init__的過程抗愁,跟普通函數(shù)的定義只有一點不同癞季,就是第一個參數(shù)永遠是實例變量self,并且在調用時不用傳遞該參數(shù)购披。所以蝙云,默認參數(shù)抡医、可變參數(shù)、關鍵字參數(shù)和命名關鍵字參數(shù)仍然是存在的。
數(shù)據(jù)封裝
可以直接在class的內部定義訪問數(shù)據(jù)的函數(shù),而不必在外部定義铁追,這樣就把數(shù)據(jù)進行了封裝屿岂。封裝數(shù)據(jù)的函數(shù)和class本身是關聯(lián)起來的空骚,我們稱之為類的方法(所以會經(jīng)常看到“這個類沒有這個方法”)
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))
想要調用
barry.print_score() 得到barry li: 100
(注意, 這個時候print_score(barry)) 就不好用,想想為什么)
這樣诽嘉,封裝的巨大意義就出現(xiàn)了:創(chuàng)建實例只需要給出score, name這些,而打印只需要調用print_score黍少,這些都是在Student類內部定義的,這些數(shù)據(jù)和邏輯封裝起來了,調用很容易,但卻不用知道內部實現(xiàn)的細節(jié)。
(注意print_score仍然是函數(shù)汉操,只不過是class內部的函數(shù)其弊,稱為Method(?), 所以return)
封裝的另一個巨大好處是膀斋,可以給一個定義好的class增加新的方法,比如get_grade
總結下痹雅,類是創(chuàng)建實例的模板仰担,實例是一個個具體的對象,比如你要批量創(chuàng)造鍵盤绩社,那么要先規(guī)定好摔蓝,鍵盤(作為class)的特征是什么,(要傳入的參數(shù)是什么,比如尺寸愉耙,顏色贮尉,軸體,品牌等)朴沿,然后創(chuàng)造出來的具體的鍵盤比如filco圣手二代(實例)猜谚。但其實跟創(chuàng)建的第二個鍵盤Cherry MX2.0沒啥關系,但是他們都是鍵盤呀(攤手)赌渣。
class Keyboard(object):
? ? def __init__(self, size, color, switch, barry)
? ? ? ? self.switch = switch
...
? ? def print_switch(self):
? ? ? ? ?print('%s' % self.switch)
如果我們要知道魏铅,某個鍵盤是什么軸體的,那么可以直接在class內部定義好坚芜,怎樣從外部獲壤婪肌:直接定義訪問軸體get_switch訪問switch這個參數(shù)的內容就好。
通過實例調用方法鸿竖,我們就直接操作了對象內部的數(shù)據(jù)沧竟,但無需知道方法內部的實現(xiàn)細節(jié)。
同樣的缚忧,對一個實例的操作悟泵,并不影響其他實例。比如指著A說“你除了一個鼻子搔谴,兩個眼睛魁袜,是‘人’這個類之外,你還有有個特性:A.feature = '傻逼') 定義了A的feature是個傻逼敦第,但是你如果嘗試調用B.feature峰弹,就會報錯:B連feature這個屬性都沒有,更別提B是不是傻逼了。
訪問限制
外部代碼還是可以自由修改一個實例的name, score屬性芜果。如果要讓內部屬性不被外部訪問鞠呈,可以把屬性的名稱前加上兩個下劃線__,在python中右钾,實例的變量如果以__開頭蚁吝,就變成了一個私有變量(private)旱爆,外部不能訪問,只能通過內部定義的方法來訪問窘茁,這樣就保證了代碼的robust怀伦。
class定義好了規(guī)則,只能通過指定規(guī)則來訪問指定數(shù)據(jù)山林,這種直接從底層拿的方法就被ban掉了房待。
一樣,修改也是可以開放的, 可以在內部定義set_score這種驼抹。換句話說桑孩,修改這個權限也是在內部通過方法實現(xiàn)的。
繼承和多態(tài)
在OOP中框冀,當我們定義一個class的時候流椒,可以從某個現(xiàn)有的class繼承,新的class稱為subclass明也。而被繼承的class被稱為基類宣虾、父類和超類(base class, super class)。
還說機械鍵盤的例子温数,Cherry這個subclass只pass了下峭状,就獲得了來自base class Keyboard的clean_keyboard方法瓦胎。
當然,subclass也不是什么都要跟著base class走。完全可以定義自己的方法辫狼。當然狐粱,如果subclass創(chuàng)建了跟base class同名的方法拗胜,以subclass為主導(這和父母和孩子的關系很像:孩子的后天行為會覆蓋掉先天影響歧匈,但是孩子來源于父母)
比如這個clean_keyboard方法,父類子類都有王带,以子類為主淑蔚。代碼運行,總是調用子類的愕撰。
這時候刹衫,就是繼承的另一個好處:多態(tài)。啥是多態(tài)搞挣?
我們定義了一個class带迟,我們就定義了一個數(shù)據(jù)類型,跟內置的tuple/list/dict/set/string/int沒啥兩樣囱桨。如果我們用常規(guī)測類型的方法isinstance(obj, type)來測仓犬,就會發(fā)現(xiàn)cherry(作為實例,不是class) 確實是屬于Cherry類型舍肠,同時也是Keyboard類型搀继,當然更是object類型(這是根父類)窘面。
所以這三個結果都是True
print(isinstance(cherry,Cherry))
print(isinstance(cherry,Keyboard))
print(isinstance(cherry,object))
理解多態(tài)的好處:
實際上,任何依賴Animal作為參數(shù)的函數(shù)或者方法不加修改地正常運行叽躯。
新增一個Animal的子類财边,不必對run_twice進行修改。也就是說不管傳入的是貓是狗点骑,我們只需要接收Animal類型就好制圈。因為貓狗都是Animal類型,然后按照Animal類型進行操作即可畔况。由于Animal類型有run()方法,因此傳入的任意數(shù)據(jù)類型慧库,只要是Animal的類或子類,就會自動調用實際類型的run()方法齐板,這就是多態(tài)的意思:
對于一個變量吵瞻,我們只需要知道它是Animal類型,無需確切知道它的子類型橡羞,就可以放心地調用run()方法,而具體調用的run()方法是作用在Animal济舆、Dog還是cat對象上卿泽,由運行時該對象的確切類型決定,這就是多態(tài)真正的威力:調用只管調用滋觉,不管細節(jié)签夭。當我們新增一種Animal子類的時候,只要確保子類的run()編寫正確(前提是run()還是要寫到子類里邊的)椎侠,不用管原來的代碼是如何調用的第租。這就是著名的”開閉“原則:
對擴展開放:允許新增Animal子類;
對修改封閉:不需要修改依賴Animal類型的run_twice()等函數(shù)我纪。
繼承還還可以一級一級地繼承狹隘慎宾,就好比從爺爺?shù)桨职帧⒃俚絻鹤舆@樣的關系浅悉。而任何類趟据,最終都可以追溯到根類object,這些繼承關系看上去就像一顆倒著的樹术健。
從這個意義上講之宿,在任何一個更根的類里定義的方法,都可以被這個分支上的所有子類繼承苛坚。
看到這里比被,一定有人像我一樣暈了
dog.run()
run_twice(Dog())
為什么一個dog在前面
一個Dog()在里面色难?
經(jīng)過測試,這兩個寫法居然不是通用的等缀,那是怎么回事枷莉?
dog.run() 是方法的使用范例
run_twice(Dog()) 是函數(shù)的使用范例
方法和函數(shù)的關系是,
函數(shù)(function)就相當于一個數(shù)學公式尺迂,它理論上不與其它東西關系笤妙,它只需要相關的參數(shù)就可以
方法(method)是與某個對象相互關聯(lián)的,也就是說它的實現(xiàn)與某個對象有關聯(lián)關系.也就是說噪裕,在Class定義的函數(shù)就是方法.
簡而言之蹲盘,方法和對象相關,函數(shù)和對象無關膳音。
靜態(tài)語言 vs 動態(tài)語言
對于靜態(tài)語言(如Java)召衔,如果需要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類祭陷,否則將無法調用run()方法苍凛。
對于Python這樣的動態(tài)語言來說,不一定需要傳入Animal類型兵志。我們保證傳入的對象有一個run()方法就可以了醇蝴。鴨子類型和多態(tài)詳解
http://blog.csdn.net/shangzhihaohao/article/details/7065675
以前寫過一篇文章講了一下python中的多態(tài),最后得出結論python不支持多態(tài)想罕,隨著對python理解得加深悠栓,對python中得多態(tài)又有了一些看法。
首先Python不支持多態(tài)按价,也不用支持多態(tài)闸迷,python是一種多態(tài)語言,崇尚鴨子類型俘枫。以下是維基百科中對鴨子類型得論述:
在程序設計中腥沽,鴨子類型(英語:duck typing)是動態(tài)類型的一種風格。在這種風格中鸠蚪,一個對象有效的語義今阳,不是由繼承自特定的類或實現(xiàn)特定的接口,而是由當前方法和屬性的集合決定茅信。這個概念的名字來源于由James Whitcomb Riley提出的鴨子測試盾舌,“鴨子測試”可以這樣表述:
“當看到一只鳥走起來像鴨子、游泳起來像鴨子蘸鲸、叫起來也像鴨子妖谴,那么這只鳥就可以被稱為鴨子。”
在鴨子類型中膝舅,關注的不是對象的類型本身嗡载,而是它是如何使用的。例如仍稀,在不使用鴨子類型的語言中洼滚,我們可以編寫一個函數(shù),它接受一個類型為鴨的對象技潘,并調用它的走和叫方法遥巴。在使用鴨子類型的語言中,這樣的一個函數(shù)可以接受一個任意類型的對象享幽,并調用它的走和叫方法铲掐。如果這些需要被調用的方法不存在,那么將引發(fā)一個運行時錯誤值桩。任何擁有這樣的正確的走和叫方法的對象都可被函數(shù)接受的這種行為引出了以上表述摆霉,這種決定類型的方式因此得名。
鴨子類型通常得益于不測試方法和函數(shù)中參數(shù)的類型颠毙,而是依賴文檔、清晰的代碼和測試來確保正確使用砂碉。從靜態(tài)類型語言轉向動態(tài)類型語言的用戶通常試圖添加一些靜態(tài)的(在運行之前的)類型檢查蛀蜜,從而影響了鴨子類型的益處和可伸縮性,并約束了語言的動態(tài)特性增蹭。
毫無疑問在python中對象也是一塊內存滴某,內存中除了包含屬性、方法之外滋迈,還包含了對象得類型霎奢,我們通過引用來訪問對象,比如a=A()饼灿,首先python創(chuàng)建一個對象A幕侠,然后聲明一個變量a,再將變量a與對象A聯(lián)系起來碍彭。變量a是沒有類型得晤硕,它的類型取決于其關聯(lián)的對象。a=A()時庇忌,a是一個A類型的引用舞箍,我們可以說a是A類型的,如果再將a賦值3皆疹,a=3疏橄,此時a就是一個整型的引用,但python并不是弱類型語言略就,在python中'2'+3會報錯捎迫,而在PHP中'2'+3會得到5晃酒。可以這么理解立砸,在python中變量類似與c中的指針掖疮,和c不同的是python中的變量可以指向任何類型,雖然這么說不太準確颗祝,但是理解起來容易點浊闪。
因此,在python運行過程中螺戳,參數(shù)被傳遞過來之前并不知道參數(shù)的類型搁宾,雖然python中的方法也是后期綁定,但是和Java中多態(tài)的后期綁定卻是不同的倔幼,java中的后期綁定至少知道對象的類型盖腿,而python中就不知道參數(shù)的類型。
還引用上次的例子:
[python]view plaincopy
classA:
defprt(self):
print"A"
classB(A):
defprt(self):
print"B"
classC(A):
defprt(self):
print"C"
classD(A):
pass
classE:
defprt(self):
print"E"
classF:
pass
deftest(arg):
arg.prt()
a?=?A()
b?=?B()
c?=?C()
d?=?D()
e?=?E()
f?=?F()
test(a)
test(b)
test(c)
test(d)
test(e)
test(f)
輸出結果:
[python]view plaincopy
A
B
C
A
E
Traceback?(most?recent?call?last):
File"/Users/shikefu678/Documents/Aptana?Studio?3?Workspace/demo/demo.py",?line33,in
test(a),test(b),test(c),test(d),test(e),test(f)
File"/Users/shikefu678/Documents/Aptana?Studio?3?Workspace/demo/demo.py",?line24,intest
arg.prt()
AttributeError:?F?instance?has?no?attribute'prt'
a损同,b翩腐,c,d都是A類型的變量膏燃,所以可以得到預期的效果(從java角度的預期)茂卦,e并不是A類型的變量但是根據(jù)鴨子類型,走起來像鴨子组哩、游泳起來像鴨子等龙、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子伶贰,e有prt方法蛛砰,所以在test方法中e就是一個A類型的變量,f沒有prt方法黍衙,所以f不是A類型的變量泥畅。
以上是從java的角度分析的,其實上邊都是一派胡言琅翻,只是為了說明python中的運行方法涯捻。沒有誰規(guī)定test方法是接收的參數(shù)是什么類型的。test方法只規(guī)定望迎,接收一個參數(shù)障癌,調用這個參數(shù)的prt方法。在運行的時候如果這個參數(shù)有prt方法辩尊,python就執(zhí)行涛浙,如果沒有,python就報錯,因為abcde都有prt方法轿亮,而f沒有疮薇,所以得到了上邊得結果,這就是python的運行方式我注。
獲取對象信息
當我們拿到一個對象的引用時按咒,如何知道這個對象是什么類型,有哪些方法可以調用但骨?
type()
判斷對象類型励七,基本類型肯定都可以判斷,變量或者類也能判斷奔缠。
In[7]: type(Cat())
Out[7]: __main__.Cat
In[5]: type(abs)
Out[5]: builtin_function_or_method
可見掠抬,type()返回的是對應的class類型。也可以通過if語句來生成布爾值
type('abc') == str
type('abc') == type(123)
也可以判斷一個對象是否為函數(shù)校哎,但是需要調用types(別忘了s傲讲ā!C贫摺)
import types
def fn():
? ?pass
type(fn) == types.FunctionType
type(abs) == types.BuiltinFunctionType
type((x for x in range(10)) == types.GeneratorType
isinstance()
type()直接判斷沒問題腰奋,判斷繼承就很麻煩——我們想知道的是Dog是否屬于Animal,type()只能告訴我Dog()(實例)屬于Dog
一句話:isinstance可以幫你判斷一個對象是否是該類型本身抱怔,或是位于該類型的父繼承鏈上劣坊。
也可以判斷多個,關系是any(或)
isinstance([1, 2, 3], (list, tupe))
>>>True
使用dir() (重頭戲來了)
如果要獲得一個對象的所有屬性和方法野蝇,可以使用dir()函數(shù)讼稚,它返回一個包含字符串的list括儒。(這也是針對”某對象沒有這個方法“報錯的最有效方法——而不是盲目嘗試或是貿然百度绕沈,需要學會調試錯誤)
比如dir('ABC') # 查看一個string所有的屬性和方法,以“__"開頭的暫且不提
'capitalize','casefold','center','count','encode','endswith','index','isdecimal','isdigit','isidentifier','islower',isnumeric','isprintable','isspace','lower','lstrip',maketrans','partition','replace','rindex','just','rpartition','rsplit','rstrip','splitlines', ?(復制的不全)
要清楚一件事情帮寻,len('ABC') 也是通過'ABC'.__len__()實現(xiàn)的
(想想為啥一個寫'ABC'.len()不行乍狐。因為一個字符串并沒有內部的方法叫l(wèi)en(),不信可以自己去看固逗。只有內部確有這個方法可以用object.method())
剩下的都是普通屬性方法浅蚪,比如lower()返回小寫的字符串:'ABC'.lower()
?進階:
可以使用hasattr(object, name) 判斷object是否有這個屬性
可以用setattr(object, name, value) 給object加上name這個屬性,value是xxx
也可以用getattr(object, name)來獲取name的value 等價于obj.name
報錯提醒:如果試圖獲取不存在的屬性烫罩,會拋出AttributeError(最常見的錯誤之一)
可以通過限制getattr(obj, name, 404) 這種方法來限定錯誤返回——即不返回AttriuteError惜傲,而返回指定的value, 比如404
所以,通過內置的type(), isintance(),dir()贝攒,我們可以對任意一個python對象進行解析盗誊,拿到其內部的數(shù)據(jù)。只有不知道不確定對象內部信息的時候,我們才會嘗試去獲取對象信息哈踱。如果你知道可以
sum = obj.x + obj.y沒問題荒适,就不要sum = getattr(obj, 'x') + getattr(obj, 'y')
但是話說回來,根據(jù)鴨子類型原理开镣,只要有x方法刀诬,就可以用調用。有x方法的不一定是同一類對象——但是鴨子根本不在乎邪财。
實例屬性和類屬性
就一點:在寫程序的時候陕壹,千萬不要把實例屬性和類屬性使用相同的名字,因為相同名稱的實例屬性將屏蔽掉類屬性卧蜓。(如果刪除類型屬性帐要,但是當你刪除實例屬性后,再使用相同的名稱弥奸,訪問到的將是類屬性)
我覺得好像從現(xiàn)在開始榨惠,知識的抽象程度就直接升了一個檔次。每一塊花的時間都要比前面的幾塊加起來還要多——但這也正是意義所在盛霎,簡單的東西稍微下功夫就學得會赠橙,但終究價值不大。
面向對象高級編程
數(shù)據(jù)封裝愤炸、繼承和多態(tài)只是面向對象程序設計中的基礎三概念期揪。高級功能還有很多。
使用__slots__
先說一個小技巧规个,可以直接給一個實例綁定一個方法凤薛,通過
from types import MethodType
instance.function = MethodType(function, instance)
但是一般我們不給instance綁定方法,我們都直接給class綁诞仓。有兩個方法缤苫,要么直接在class內綁定,要么通過instance.function = MethodType(function, instance)來綁定方法
內部綁定的方法是常規(guī)方法墅拭,基本上所有語言都能實現(xiàn)活玲;
外部綁定因為是后綁定上去的,或者說隨時想綁就綁的谍婉,稱之為動態(tài)綁定舒憾,只有動態(tài)語言才能實現(xiàn)。
針對instance和class, 如果用這種方法來綁定屬性的話穗熬,基本上想綁啥就綁啥镀迂。其實是很多時候需要限制的,這時候就需要在class內部定義的__slots__唤蔗。
class Student(object):
? ? __slot__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱
這時候嘗試去綁定其他屬性就會返回錯誤
但是探遵!這僅僅限于這個class, 繼承class的其他subclass并不受__slot__限制唧瘾,除非subclass自己也定義上__slot__。
使用@property(和裝飾器相連别凤,實在是沒看懂)
多重繼承
只有通過多重繼承饰序,一個子類就可以同時獲得多個父類的所有功能。
這樣规哪,就避免了繼承的過分的復雜性求豫。
在設計class的繼承關系時,通常都是“一脈相承”诉稍。如果需要額外混入其他功能蝠嘉,通過多重繼承就可以實現(xiàn)。就如同Dog繼承Mammal杯巨,同時也繼承Runnable蚤告。這種設計通常稱之為MixIn。MixIn的目的就是給一個類增加多個功能服爷。這樣我們會優(yōu)先考慮通過多重繼承來組合多個MixIn功能杜恰,而不是設計多層次的復雜的繼承關系。(JAVA這種只允許單一繼承的語言不能使用MixIn的設計)
定制類仍源、枚舉類心褐、元類(略,等待填坑)
6天時間復習到這笼踩。元動力消耗完畢逗爹,任務掛起,切換至pandas嚎于。