Python3 - 復習(update to Day6)

# 第一優(yōu)先級規(guī)則聲明:

# 除了夢境,每一個意識主進程都必須與一個身體參與的機械進程相匹配,否則結束意識主進程。如學習python同時必須伴有記筆記、敲代碼等機械進程,學習英語必須伴有朗讀、聽說、查字典、查語法書等機械進程跟隨,拆解問題必須在紙上或iPad上實時記錄意識的每一次嘗試。

如果身體狀況或是客觀情況不允許有機械進程存在,請轉(zhuǎn)入其他進程,如注意力恢復(閉目養(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-轉(zhuǎn)為utf-8無BOM格式

I/O:

print()

print在python3.5中正式成為函數(shù),允許用“,”分隔多個字符串,也接受數(shù)學計算

input()

接受用戶的輸入,同時也接受()內(nèi)插入內(nèi)容。

如果要規(guī)范輸入的內(nèi)容,可以用這種方法

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:單引號或者雙引號括起來的任意文本(里邊有特殊符號請轉(zhuǎn)義)。需要轉(zhuǎn)義的入托太多,用r'string' 來帶來一大堆的\。計算string多長,用len()

布爾值:True/False 1/0。如果True/False拼錯了或者大小寫出錯,并不會識別為布爾值(在pycharm中識別成為橙色)??杉訙p乘除,可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內(nèi)置。是一種有序的元素集合(或者說容器),元素是什么數(shù)據(jù)類型都可以可以隨時添加和刪除其中的元素()。同樣,可以用len()獲取list中的個數(shù)(string也能進行l(wèi)en()操作,因為string本質(zhì)上也是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, 務必通過括號內(nèi)逗號來消除歧義。


Dict和Set

dict

其他語言中也稱之為map, 使用key-value存儲,優(yōu)點是查找速度極快。

d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}

為什么查找dict特別快,比查找list要開快得多呢? list查詢是遍歷查詢,從第一個翻到最后一個,查詢速度直接取決于list大小。dict直接計算出對應的位置,直接取出,非常快。根據(jù)什么算出來呢?就是key-value。

dict有個缺點:占用內(nèi)存大。正因為如此,拿空間換時間,占用空間大,所以查找速度快。

既然是通過Key來查找,那么key一定要是不可變對象。通過key來計算位置的算法,就叫做hash算法。

要保證hash正確性,那么key就不可變(如字符串和整數(shù))。但是list這種就是可變的,就不能作為Key。這時候報錯就是Typeerror: unhasable type

d['barry'] = 90 這個過程跟給變量賦值沒什么區(qū)別,相當于給key這個變量賦值。

調(diào)用不存在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內(nèi)部key就不能重復,所以set里邊的元素也不能重復(即使重復,也會自動會被過濾)。一樣,單純的key集合也無所謂先后順序。

這樣,才形成了set數(shù)學意義上的無序+無重復元素的集合。

s = set(list)

list = [1, 2, 3]

可以通過add(key)來添加元素(key)到set當中,重復添加倒是可以,不過還是會被過濾掉。

可以通過remove(key)來刪除元素。

可以s1 & s2 取交集,s1 | s2取并集。(跟linux非常相似)

可變和不可變

不可變對象(如string)調(diào)用對象自身的任意方法,也不會改變對象自身的內(nèi)容。相反這些方法會創(chuàng)建新的對象然后返回,這樣就保證了不可變對象的不便。

函數(shù)

函數(shù)是啥?函數(shù)本質(zhì)上就是把一些現(xiàn)成的功能寫成一個封裝的包(跟第三方庫非常像),調(diào)用即可。也就是說,函數(shù)可以自己寫,也可以用寫好的現(xiàn)成的。比如不用每次都寫平方或者指數(shù)不需要每次都寫x * x...這種底層寫法。很多函數(shù)內(nèi)置,很多函數(shù)在可調(diào)用庫。(import大法好?。?/p>

抽象

抽象是啥?每次都還原成最底層代碼煩不煩?那你需要抽象!

抽象可以使我們每次都調(diào)用一些聚合過的概念,如求和西格瑪符號。

有好多內(nèi)置函數(shù)(build-in functions),可以直接調(diào)用。(至少讀一遍,知道都有啥。Python真的是能夠做到,你能想到的函數(shù),99%都有之前人寫過,stack overflow/github/python各種庫,多讀讀)

Built-in functions:

數(shù)學運算類:

abs(x) 取x的絕對值

divmod(a, b) 返回(商,余數(shù))

float(x) 取浮點數(shù) ;bool() 取布爾值; str()轉(zhuǎn)換成字符; int() 轉(zhuǎn)換成正整數(shù)

int(x, base=n) 取整數(shù),base是可選的,表示進制) 也可以long(x, base=n) 轉(zhuǎ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ù),必須包括這三個方面。要不然調(diào)用函數(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多個值么?可以,但是返回的本質(zhì)上是個tuple

比如剛剛學到的科學計算函數(shù)divmod(x, y), 返回的就是兩個值(商,余數(shù)),但是如果你去嘗試type(),就會看到這是一個tuple

type(divmod(5, 2)

Out: tuple

函數(shù)的參數(shù)

對于日常使用來說,因為python大量的build-in functions和變態(tài)的import功能,我們非常少去自己寫一個函數(shù)。更多的是理解自己調(diào)用的函數(shù)該如何使用——核心就在于,函數(shù)的參數(shù)的了解和使用。日常使用的大量報錯都是因為:對于某個函數(shù)是否能夠接受某種方式傳入的某個參數(shù)的不確定,導致大量debug時間(隨機嘗試不是個長期解決方案,雖然能夠不斷地返回error信息以供調(diào)試)

1. 位置參數(shù)

簡單理解,位置固定的參數(shù),如pow(x, y)計算x的y次方。直接寫pow(3, 2)不再需要規(guī)定哪個是x, 哪個是y

2. 默認參數(shù): 大大減輕了函數(shù)調(diào)用的難度

比如range(5),實際上start位置的參數(shù)就被忽略了,只傳入了stop。但是仍然能運行,因為range()內(nèi)部規(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ù)調(diào)用時自動組裝為一個 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語句]),但是問題也很明顯。

你生成辣么大的列表,占著內(nèi)存,留著過年么?又不見得全都用得上!

這時候你就需要個generator, 也就是 邊生成,邊循環(huán),邊計算。

符號上跟列表生成式的區(qū)別只有一個

List = [x * x for x in range(10)]

Generator = (x * x for x in range(10))

對,沒看錯,就是把中括號變成了小括號,就從list變成了generator。(如果你去type()他們,會看到區(qū)別)。

調(diào)用列表當然很簡單,怎么調(diào)用generator呢。怎么個生成法呢?

用next(generator)來調(diào)用,調(diào)用一次生成一次。也就是每次next(generator)結果都不一樣,next()到最后一步,拋出Stopiteration錯誤。不過你也看出來了,也不能每次都next(),啥時候是個頭???(實際上幾乎用不上next())

所幸,一個generator仍然能被for循環(huán)調(diào)用。

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()調(diào)用并不斷返回下一個值的對象成為迭代器Iterator。想把Iterable對象編程iterator,可以使用Iter()函數(shù)。

for循環(huán)本質(zhì)上就是不斷調(diào)用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也是惰性計算,不調(diào)用不篩選。

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里邊的每一個元素里邊了,所以不用再去考慮怎么樣調(diào)用每一個元素了。比如

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)

不用再想怎么去調(diào)用每一個元素了,比如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__來調(diào)用函數(shù)名稱(后面會詳細解析)

裝飾器就是在函數(shù)動態(tài)運行期間,給函數(shù)增加了的功能(其實就是返回函數(shù))。

@log # 把log函數(shù)通過@方法,放到now()前面,作為裝飾器出現(xiàn)。

def now():

? ? print('2016-10-01')

在面向?qū)ο蟮脑O計模式中,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常用(比如轉(zhuǎn)換成為16進制),那么可以規(guī)定

int(obj, base=16)作為偏函數(shù)。使用方法如下

import functools

int2 = functools.partial(int, base=8)

但是,調(diào)用的時候如果主動改變規(guī)定好的默認參數(shù),比如int2(int, base=16),16進制轉(zhuǎn)換仍然有效。

當函數(shù)的參數(shù)個數(shù)太多需要簡化的時候,固定住某一些參數(shù),從而方便調(diào)用。

模塊

把函數(shù)分組,放到不同的.py文件里,每一個.py文件就叫做模塊。

調(diào)用模塊的最大好處在于,不需要重復制作輪子,只要調(diào)用就好。

模塊會組成package(包),所以我們會說“調(diào)用第三方包/庫,說的就是這個意思。

包目錄下,必須有__init__.py文件,否則python就不識別這個包目錄,而是當成普通目錄。

切記:不能與系統(tǒng)自帶模塊重名,比如sys。

使用模塊要先import sys。

模塊的編寫(略)

安裝模塊

安裝第三方模塊,是通過pip工具

$ pip install pandas

調(diào)用的時候, 只import就只會在1. 當前目錄 2. 所有已經(jīng)安裝的內(nèi)置模塊 3.第三方模塊 進行搜索,實際上遍歷了sys.path變量下的文件內(nèi)容

import sys

sys.path

如果要暫時添加,用append()

sys.path.append('the directory you want to add')?

第二種方法設置環(huán)境變量PYTHONPATH,該環(huán)境變量的內(nèi)容會被自動添加到模塊搜索路徑

(待填坑)

面向?qū)ο缶幊蘋bject Orient Programming(OOP)

OOP把對象作為程序設計的基本單元,一個對象包含了數(shù)據(jù)和(操作數(shù)據(jù)的)函數(shù)。把計算機程序程序視為一組對象的集合,而每個對象都可以接收其他對象發(fā)過來的消息,并處理這些消息。執(zhí)行程序就是在對象之間傳遞信息。

面向過程的程序設計把計算機程序視為一系列的命令集合,就是一組函數(shù)的順序執(zhí)行。具體,就是把函數(shù)切分成為子函數(shù)。

python中所有數(shù)據(jù)類型都可以視為對象,當然也可以自定義對象。自定義的對象數(shù)據(jù)類型就是面向?qū)ο笾械念?class)的概念。

比如class是student, 是泛指學生這個概念,instance則是一個個具體的student, 張三李四。

所以,面向?qū)ο蟮脑O計思想是抽象出class,根據(jù)class創(chuàng)建instance。

面向?qū)ο蟮某橄蟪啥加直群瘮?shù)要高,因為一個class既包含數(shù)據(jù),又包含操作數(shù)據(jù)的方法。

面向?qū)ο笕筇攸c:數(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是內(nèi)存地址,每個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__方法內(nèi)部,就可以把各種屬性(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,并且在調(diào)用時不用傳遞該參數(shù)。所以,默認參數(shù)、可變參數(shù)、關鍵字參數(shù)和命名關鍵字參數(shù)仍然是存在的。

數(shù)據(jù)封裝

可以直接在class的內(nèi)部定義訪問數(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))

想要調(diào)用

barry.print_score() 得到barry li: 100

(注意, 這個時候print_score(barry)) 就不好用,想想為什么)

這樣,封裝的巨大意義就出現(xiàn)了:創(chuàng)建實例只需要給出score, name這些,而打印只需要調(diào)用print_score,這些都是在Student類內(nèi)部定義的,這些數(shù)據(jù)和邏輯封裝起來了,調(diào)用很容易,但卻不用知道內(nèi)部實現(xiàn)的細節(jié)。

(注意print_score仍然是函數(shù),只不過是class內(nèi)部的函數(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內(nèi)部定義好,怎樣從外部獲?。褐苯佣x訪問軸體get_switch訪問switch這個參數(shù)的內(nèi)容就好。

通過實例調(diào)用方法,我們就直接操作了對象內(nèi)部的數(shù)據(jù),但無需知道方法內(nèi)部的實現(xiàn)細節(jié)。

同樣的,對一個實例的操作,并不影響其他實例。比如指著A說“你除了一個鼻子,兩個眼睛,是‘人’這個類之外,你還有有個特性:A.feature = '傻逼') 定義了A的feature是個傻逼,但是你如果嘗試調(diào)用B.feature,就會報錯:B連feature這個屬性都沒有,更別提B是不是傻逼了。

訪問限制

外部代碼還是可以自由修改一個實例的name, score屬性。如果要讓內(nèi)部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線__,在python中,實例的變量如果以__開頭,就變成了一個私有變量(private),外部不能訪問,只能通過內(nèi)部定義的方法來訪問,這樣就保證了代碼的robust。

class定義好了規(guī)則,只能通過指定規(guī)則來訪問指定數(shù)據(jù),這種直接從底層拿的方法就被ban掉了。

一樣,修改也是可以開放的, 可以在內(nèi)部定義set_score這種。換句話說,修改這個權限也是在內(nèi)部通過方法實現(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為主導(這和父母和孩子的關系很像:孩子的后天行為會覆蓋掉先天影響,但是孩子來源于父母)

這時候得到的輸出結果一定是your cherry is being cleaned

比如這個clean_keyboard方法,父類子類都有,以子類為主。代碼運行,總是調(diào)用子類的。

這時候,就是繼承的另一個好處:多態(tài)。啥是多態(tài)?

我們定義了一個class,我們就定義了一個數(shù)據(jù)類型,跟內(nèi)置的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的類或子類,就會自動調(diào)用實際類型的run()方法,這就是多態(tài)的意思:

對于一個變量,我們只需要知道它是Animal類型,無需確切知道它的子類型,就可以放心地調(diào)用run()方法,而具體調(diào)用的run()方法是作用在Animal、Dog還是cat對象上,由運行時該對象的確切類型決定,這就是多態(tài)真正的威力:調(diào)用只管調(diào)用,不管細節(jié)當我們新增一種Animal子類的時候,只要確保子類的run()編寫正確(前提是run()還是要寫到子類里邊的),不用管原來的代碼是如何調(diào)用的。這就是著名的”開閉“原則:

對擴展開放:允許新增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類型或者它的子類,否則將無法調(diào)用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)類型的一種風格。在這種風格中,一個對象有效的語義,不是由繼承自特定的類或?qū)崿F(xiàn)特定的接口,而是由當前方法和屬性的集合決定。這個概念的名字來源于由James Whitcomb Riley提出的鴨子測試,“鴨子測試”可以這樣表述:

“當看到一只鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子。”

在鴨子類型中,關注的不是對象的類型本身,而是它是如何使用的。例如,在不使用鴨子類型的語言中,我們可以編寫一個函數(shù),它接受一個類型為鴨的對象,并調(diào)用它的走和叫方法。在使用鴨子類型的語言中,這樣的一個函數(shù)可以接受一個任意類型的對象,并調(diào)用它的走和叫方法。如果這些需要被調(diào)用的方法不存在,那么將引發(fā)一個運行時錯誤。任何擁有這樣的正確的走和叫方法的對象都可被函數(shù)接受的這種行為引出了以上表述,這種決定類型的方式因此得名。

鴨子類型通常得益于不測試方法和函數(shù)中參數(shù)的類型,而是依賴文檔、清晰的代碼和測試來確保正確使用。從靜態(tài)類型語言轉(zhuǎn)向動態(tài)類型語言的用戶通常試圖添加一些靜態(tài)的(在運行之前的)類型檢查,從而影響了鴨子類型的益處和可伸縮性,并約束了語言的動態(tài)特性。

毫無疑問在python中對象也是一塊內(nèi)存,內(nèi)存中除了包含屬性、方法之外,還包含了對象得類型,我們通過引用來訪問對象,比如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ù),調(diào)用這個參數(shù)的prt方法。在運行的時候如果這個參數(shù)有prt方法,python就執(zhí)行,如果沒有,python就報錯,因為abcde都有prt方法,而f沒有,所以得到了上邊得結果,這就是python的運行方式。

獲取對象信息

當我們拿到一個對象的引用時,如何知道這個對象是什么類型,有哪些方法可以調(diào)用?

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ù),但是需要調(diào)用types(別忘了s?。。。?/p>

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。(這也是針對”某對象沒有這個方法“報錯的最有效方法——而不是盲目嘗試或是貿(mào)然百度,需要學會調(diào)試錯誤)

比如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()不行。因為一個字符串并沒有內(nèi)部的方法叫l(wèi)en(),不信可以自己去看。只有內(nèi)部確有這個方法可以用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

所以,通過內(nèi)置的type(), isintance(),dir(),我們可以對任意一個python對象進行解析,拿到其內(nèi)部的數(shù)據(jù)。只有不知道不確定對象內(nèi)部信息的時候,我們才會嘗試去獲取對象信息。如果你知道可以

sum = obj.x + obj.y沒問題,就不要sum = getattr(obj, 'x') + getattr(obj, 'y')

但是話說回來,根據(jù)鴨子類型原理,只要有x方法,就可以用調(diào)用。有x方法的不一定是同一類對象——但是鴨子根本不在乎。

實例屬性和類屬性

就一點:在寫程序的時候,千萬不要把實例屬性和類屬性使用相同的名字,因為相同名稱的實例屬性將屏蔽掉類屬性。(如果刪除類型屬性,但是當你刪除實例屬性后,再使用相同的名稱,訪問到的將是類屬性)


我覺得好像從現(xiàn)在開始,知識的抽象程度就直接升了一個檔次。每一塊花的時間都要比前面的幾塊加起來還要多——但這也正是意義所在,簡單的東西稍微下功夫就學得會,但終究價值不大。

面向?qū)ο蟾呒壘幊?/h4>

數(shù)據(jù)封裝、繼承和多態(tài)只是面向?qū)ο蟪绦蛟O計中的基礎三概念。高級功能還有很多。

使用__slots__

先說一個小技巧,可以直接給一個實例綁定一個方法,通過

from types import MethodType

instance.function = MethodType(function, instance)

但是一般我們不給instance綁定方法,我們都直接給class綁。有兩個方法,要么直接在class內(nèi)綁定,要么通過instance.function = MethodType(function, instance)來綁定方法

內(nèi)部綁定的方法是常規(guī)方法,基本上所有語言都能實現(xiàn);

外部綁定因為是后綁定上去的,或者說隨時想綁就綁的,稱之為動態(tài)綁定,只有動態(tài)語言才能實現(xiàn)。

靜態(tài)綁定:內(nèi)部綁定
動態(tài)綁定:MethodType綁定

針對instance和class, 如果用這種方法來綁定屬性的話,基本上想綁啥就綁啥。其實是很多時候需要限制的,這時候就需要在class內(nèi)部定義的__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。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • http://python.jobbole.com/85231/ 關于專業(yè)技能寫完項目接著寫寫一名3年工作經(jīng)驗的J...
    燕京博士閱讀 7,628評論 1 118
  • //Clojure入門教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語閱讀 3,770評論 0 7
  • 前言 人生苦多,快來 Kotlin ,快速學習Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,276評論 9 118
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • 有一天大啊躍問佛:“佛啊佛,世人皆跪你、拜你、有求于你,你累嗎?”佛說:“我本西山之竹,東山之泥。被人們挖了伐了塑...
    大啊躍閱讀 189評論 0 0