Python學(xué)習(xí)筆記[2]

要點:

  • 函數(shù)式編程:注意不是“函數(shù)編程”,多了一個“式”
  • 模塊:如何使用模塊
  • 面向?qū)ο缶幊蹋好嫦驅(qū)ο蟮母拍睢傩?、方法、繼承、多態(tài)等
  • 定制類:利用Python的特殊方法定制類

1.函數(shù)式編程

函數(shù)式編程概要

<font color=#FF4500>函數(shù)式編程是一種抽象計算的編程模式</font>

  • 函 數(shù):function
  • 函數(shù)式:functional,一種編程范式
    <font color=#FF4500>函數(shù) 不等于 函數(shù)式!</font>

<font color=#FF4500>函數(shù)式編程的特點</font>

  • 把計算機視為函數(shù)而非指令
  • 純函數(shù)式編程:不需要變量,沒有副作用,測試簡單
  • 支持高階函數(shù),代碼簡單

<font color=#FF4500>Python支持的函數(shù)式編程的特點</font>

  • 不是純函數(shù)式編程:允許有變量
  • 支持高階函數(shù):函數(shù)也可以作為變量傳入
  • 支持閉包:有了閉包就能返回函數(shù)
  • 有限度地支持匿名函數(shù)

高階函數(shù)

  • 變量可以指向函數(shù)
  • 函數(shù)名其實就是指向函數(shù)的變量
  • 高階函數(shù):能夠接受函數(shù)作為參數(shù)的函數(shù)
    • 變量可以指向函數(shù)
    • 函數(shù)的參數(shù)可以接受變量
    • 一個函數(shù)可以接收另一個函數(shù)作為參數(shù)
    • 能接受函數(shù)作參數(shù)的函數(shù)就是高階函數(shù)

把函數(shù)作為參數(shù)

一個簡單的高階函數(shù):

def add(x,y,f):
    return f(x)+f(y)```
如果傳入abs作為參數(shù)的值:

add(-5,9,abs)```
根據(jù)函數(shù)的定義,函數(shù)執(zhí)行的代碼實際上是:

abs(-5)+abs(9)```
由于參數(shù) x, y 和 f 都可以任意傳入,如果 f 傳入其他函數(shù),就可以得到不同的返回值。

>#### map()函數(shù)

**map()**是 Python 內(nèi)置的高階函數(shù),它接收一個**函數(shù) f** 和一個 **list**,并通過把函數(shù) f 依次作用在 list 的每個元素上,得到一個新的 list 并返回。

例如,對于list [1, 2, 3, 4, 5, 6, 7, 8, 9]

如果希望把list的每個元素都作平方,就可以用map()函數(shù),我們只需要傳入函數(shù)f(x)=x*x,就可以利用map()函數(shù)完成這個計算:

def f(x):
return x*x
print map(f,[1, 2, 3, 4, 5, 6, 7, 8, 9])```
打印結(jié)果:

[1, 4, 9, 10, 25, 36, 49, 64, 81]```

**注意:**map()函數(shù)不改變原有的 list,而是返回一個新的 list。

利用map()函數(shù),可以把一個 list 轉(zhuǎn)換為另一個 list,只需要傳入轉(zhuǎn)換函數(shù)。

由于list包含的元素可以是任何類型,因此,map() 不僅僅可以處理只包含數(shù)值的 list,事實上它可以處理包含任意類型的 list,只要傳入的函數(shù)f可以處理這種數(shù)據(jù)類型。

>#### reduce()函數(shù)

**reduce()**函數(shù)也是Python內(nèi)置的一個高階函數(shù)。reduce()函數(shù)接收的參數(shù)和 map()類似,**一個函數(shù) f,一個list**,但行為和 map()不同,reduce()傳入的函數(shù) f 必須接收兩個參數(shù),reduce()對list的每個元素反復(fù)調(diào)用函數(shù)f,并返回最終結(jié)果值

例如,編寫一個f函數(shù),接收x和y,返回x和y的和:

def f(x, y):
return x + y```

調(diào)用 reduce(f, [1, 3, 5, 7, 9])時,reduce函數(shù)將做如下計算:

先計算頭兩個元素:f(1, 3),結(jié)果為4;
再把結(jié)果和第3個元素計算:f(4, 5),結(jié)果為9;
再把結(jié)果和第4個元素計算:f(9, 7),結(jié)果為16;
再把結(jié)果和第5個元素計算:f(16, 9),結(jié)果為25;
由于沒有更多的元素了,計算結(jié)束,返回結(jié)果25。```

上述計算實際上是對 list 的所有元素求和。雖然Python內(nèi)置了求和函數(shù)sum(),但是,利用reduce()求和也很簡單。

**reduce()還可以接收第3個可選參數(shù),作為計算的初始值。**如果把初始值設(shè)為100,計算:

reduce(f,[1,3,5,7,9],100)```
結(jié)果將變?yōu)?25,因為第一輪計算是:
計算初始值和第一個元素:f(100, 1),結(jié)果為101

例子:
Python內(nèi)置了求和函數(shù)sum(),但沒有求積的函數(shù),請利用recude()來求積:
輸入:[2, 4, 5, 7, 12]
輸出:245712的結(jié)果

代碼:

def prod(x, y):
    return x*y
print reduce(prod, [2, 4, 5, 7, 12])

打印結(jié)果:

3360```

>#### filter()函數(shù)

**filter()**函數(shù)是 Python 內(nèi)置的另一個有用的高階函數(shù),filter()函數(shù)接收一個**函數(shù) f **和一個**list**,這個函數(shù) f 的作用是對每個元素進行判斷,返回 True或 False,**filter()根據(jù)判斷結(jié)果自動過濾掉不符合條件的元素,返回由符合條件元素組成的新list。**

例如,要從一個list [1, 4, 6, 7, 9, 12, 17]中刪除偶數(shù),保留奇數(shù),首先,要編寫一個判斷奇數(shù)的函數(shù):

def is_odd(x):
return x%2 == 1```
然后,利用filter()過濾掉偶數(shù):

filter(is_odd,[1,4,6,7,9,12,17])```
結(jié)果:[1,7,9,17]

利用filter(),可以完成很多有用的功能,例如,刪除 None 或者空字符串:

def is_not_empty(s):
return s and len(s.strip())>0
filter(is_not_empty,['test', None, '', 'str', ' ', 'END'])```
結(jié)果:['test', 'str', 'END']
注意: s.strip(rm) 刪除 s 字符串中開頭、結(jié)尾處的 rm 序列的字符。
當(dāng)rm為空時,默認刪除空白符(包括'\n', '\r', '\t', ' '),如下:

a='   123'
a.strip()```
結(jié)果:'123'

a='\t\t123\r\n'
a.strip()```
結(jié)果:'123'

s.lstrip(rm) 刪除s字符串中開頭處,位于 rm刪除序列的字符

s.rstrip(rm) 刪除s字符串中結(jié)尾處,位于 rm刪除序列的字符

利用filter()過濾出1~100中平方根是整數(shù)的數(shù)
代碼:

import math
def is_sqr(x):
    r = int(math.sqrt(x))
    return x==r*r
print filter(is_sqr,range(1,101))```
結(jié)果:

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]


>#### 自定義排序函數(shù)

Python內(nèi)置的 **sorted()**函數(shù)可對list進行排序:

sorted([36, 5, 12, 9, 21])
[5, 9, 12, 21, 36]```

sorted()也是一個高階函數(shù),它可以接收一個比較函數(shù)來實現(xiàn)自定義排序,比較函數(shù)的定義是,傳入兩個待比較的元素 x, y,如果 x 應(yīng)該排在 y 的前面,返回 -1,如果 x 應(yīng)該排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。

因此,如果我們要實現(xiàn)倒序排序,只需要編寫一個reversed_cmp函數(shù):

def reversed_cmp(x, y):
    if x > y:
        return -1
    if x < y:
        return 1
    return 0```
這樣,調(diào)用 sorted() 并傳入 reversed_cmp 就可以實現(xiàn)倒序排序:

sorted([36, 5, 12, 9, 21], reversed_cmp)
[36, 21, 12, 9, 5]```
sorted()也可以對字符串進行排序,字符串默認按照ASCII大小來比較:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']```
'Zoo'排在'about'之前是因為'Z'的ASCII碼比'a'小。


例子:對字符串排序時,有時候忽略大小寫排序更符合習(xí)慣。請利用sorted()高階函數(shù),實現(xiàn)忽略大小寫排序的算法。
輸入:['bob', 'about', 'Zoo', 'Credit']
輸出:['about', 'bob', 'Credit', 'Zoo']

代碼:

def cmp_ignore_case(s1, s2):
u1 = s1.upper()
u2 = s2.upper()
if u1 < u2:
return -1
if u1 > u2:
return 1
return 0
print sorted(['bob', 'about', 'Zoo', 'Credit'], cmp_ignore_case)```

打印結(jié)果:

['about', 'bob', 'Credit', 'Zoo']```

>#### 返回函數(shù)

Python的函數(shù)不但可以返回int、str、list、dict等數(shù)據(jù)類型,還可以返回函數(shù)!

例如,定義一個函數(shù) f(),我們讓它返回一個函數(shù) g,可以這樣寫:

def f():
print 'call f()...'
# 定義函數(shù)g:
def g():
print 'call g()...'
# 返回函數(shù)g:
return g```
仔細觀察上面的函數(shù)定義,我們在函數(shù) f 內(nèi)部又定義了一個函數(shù) g。由于函數(shù) g 也是一個對象,函數(shù)名 g 就是指向函數(shù) g 的變量,所以,最外層函數(shù) f 可以返回變量 g,也就是函數(shù) g 本身。

調(diào)用函數(shù) f,我們會得到 f 返回的一個函數(shù):

>>> x = f() # 調(diào)用f()
call f()...
>>> x # 變量x是f()返回的函數(shù):
<function g at 0x1037bf320>
>>> x()  # x指向函數(shù),因此可以調(diào)用
call g()... # 調(diào)用x()就是執(zhí)行g(shù)()函數(shù)定義的代碼```

注意區(qū)分返回函數(shù)和返回值:

def myabs():
return abs # 返回函數(shù)
def myabs2(x):
return abs(x) # 返回函數(shù)調(diào)用的結(jié)果,返回值是一個數(shù)值```

返回函數(shù)可以把一些計算延遲執(zhí)行。例如,如果定義一個普通的求和函數(shù):

def calc_sum(lst):
    return sum(lst)```
調(diào)用calc_sum()函數(shù)時,將立刻計算并得到結(jié)果:

calc_sum([1, 2, 3, 4])
10```
但是,如果返回一個函數(shù),就可以“延遲計算”:

def calc_sum(lst):
    def lazy_sum():
        return sum(lst)
    return lazy_sum```

調(diào)用calc_sum()并沒有計算出結(jié)果,而是返回函數(shù):

f = calc_sum([1, 2, 3, 4])
f
<function lazy_sum at 0x1037bfaa0>```
對返回的函數(shù)進行調(diào)用時,才計算出結(jié)果:

>>> f()
10```
由于可以返回函數(shù),我們在后續(xù)代碼里就可以決定到底要不要調(diào)用該函數(shù)。
例子:
編寫一個函數(shù)calc_prod(lst),它接收一個list,返回一個函數(shù),返回函數(shù)可以計算參數(shù)的乘積。

代碼:

def calc_prod(lst):
def lazy_prod():
def f(x,y):
return x*y
return reduce(f,lst,1)
return lazy_prod

f = calc_prod([1, 2, 3, 4])
print f()```

結(jié)果:

24```

>####閉包

在函數(shù)內(nèi)部定義的函數(shù)和外部定義的函數(shù)是一樣的,只是他們無法被外部訪問:

def g():
print 'g()...'

def f():
print 'f()...'
return g```
將** g** 的定義移入函數(shù) f 內(nèi)部,防止其他代碼調(diào)用 g

def f():
    print 'f()...'
    def g():
        print 'g()...'
    return g```

但是,考察上一小節(jié)定義的 **calc_sum **函數(shù):

def calc_sum(lst):
def lazy_sum():
return sum(lst)
return lazy_sum```

注意: 發(fā)現(xiàn)沒法把 lazy_sum 移到 calc_sum 的外部,因為它引用了calc_sum 的參數(shù) lst。

像這種內(nèi)層函數(shù)引用了外層函數(shù)的變量(參數(shù)也算變量),然后返回內(nèi)層函數(shù)的情況,稱為閉包(Closure)。

閉包的特點是返回的函數(shù)還引用了外層函數(shù)的局部變量,所以,要正確使用閉包,就要確保引用的局部變量在函數(shù)返回后不能變。舉例如下:

# 希望一次返回3個函數(shù),分別計算1x1,2x2,3x3:

def count(): 
    fs = [] 
    for i in range(1, 4): 
        def f(): 
            return i*i 
        fs.append(f) 
    return fs
f1, f2, f3 = count()```

你可能認為調(diào)用f1(),f2()和f3()結(jié)果應(yīng)該是1,4,9,但實際結(jié)果全部都是 9(請自己動手驗證)。

原因就是當(dāng)count()函數(shù)返回了3個函數(shù)時,這3個函數(shù)所引用的變量 i的值已經(jīng)變成了3。由于f1、f2、f3并沒有被調(diào)用,所以,此時他們并未計算 i*i,當(dāng) f1 被調(diào)用時:

f1()
9 # 因為f1現(xiàn)在才計算i*i,但現(xiàn)在i的值已經(jīng)變?yōu)?```
因此,返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會發(fā)生變化的變量。

考察下面的函數(shù)** f**:

def f(j): 
    def g(): 
        return j*j 
    return g```
它可以正確地返回一個閉包g,g所引用的變量j不是循環(huán)變量,因此將正常執(zhí)行。

在count函數(shù)的循環(huán)內(nèi)部,如果借助f函數(shù),就可以避免引用循環(huán)變量i。

def count():
fs = []
for i in range(1, 4):
def f(j):
def g():
return j*j
return g
r = f(i)
fs.append(r)
return fs
f1, f2, f3 = count()
print f1(), f2(), f3()```
結(jié)果:

1 4 9```

>#### 匿名函數(shù)

高階函數(shù)可以接收函數(shù)做參數(shù),有些時候,我們不需要顯式地定義函數(shù),直接傳入匿名函數(shù)更方便。
在Python中,對匿名函數(shù)提供了有限支持。還是以map()函數(shù)為例,計算 f(x)=x^2
時,除了定義一個f(x)的函數(shù)外,還可以直接傳入匿名函數(shù):

map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]```
通過對比可以看出,匿名函數(shù) lambda x: x * x 實際上就是:

def f(x):
    return x * x```
關(guān)鍵字lambda 表示匿名函數(shù),冒號前面的 x 表示函數(shù)參數(shù)。

匿名函數(shù)有個限制,就是**只能有一個表達式**,**不寫return**,返回值就是該表達式的結(jié)果。

使用匿名函數(shù),可以不必定義函數(shù)名,直接創(chuàng)建一個函數(shù)對象,很多時候可以簡化代碼:

sorted([1, 3, 9, 5, 0], lambda x,y: -cmp(x,y))
[9, 5, 3, 1, 0]```
返回函數(shù)的時候,也可以返回匿名函數(shù):

>>> myabs = lambda x: -x if x < 0 else x 
>>> myabs(-1)
1
>>> myabs(1)
1```

利用匿名函數(shù)簡化以下代碼:

def is_not_empty(s):
return s and len(s.strip()) > 0
filter(is_not_empty, ['test', None, '', 'str', ' ', 'END'])```

定義匿名函數(shù)時,沒有return關(guān)鍵字,且表達式的值就是函數(shù)返回值。

print filter(lambda s: s and len(s.strip())>0, ['test', None, '', 'str', '  ', 'END'])```
結(jié)果:

['test', 'str', 'END']```

裝飾器decorator

問題:

  • 定義了一個函數(shù)
  • 想在運行時動態(tài)增加功能
  • 又不想改動函數(shù)本身的代碼

可以使用<font color=#FF4500>高階函數(shù):</font>

  • 可以接收函數(shù)做為參數(shù)
  • 可以返回函數(shù)
  • 是否接收一個函,對其進行包裝,然后返回一個新函數(shù)?

Python內(nèi)置的@語法就是為了簡化裝飾器調(diào)用

@new_fn
def f1(x):
    return x^2```

等價于:

def f1(x):
return x^2
f1=new_fn(f1)```

裝飾器的作用

  • 可以極大地簡化代碼,避免每個函數(shù)編寫重復(fù)性代碼
    打印日志:@log
    檢測性能:@performance
    數(shù)據(jù)庫事務(wù):@transaction
    URL路由:@post('/register')

編寫無參數(shù)decorator

Python的 decorator 本質(zhì)上就是一個高階函數(shù),它接收一個函數(shù)作為參數(shù),然后,返回一個新函數(shù)。
使用 decorator 用Python提供的 @ 語法,這樣可以避免手動編寫 f = decorate(f) 這樣的代碼。
考察一個@log的定義:

def log(f):
    def fn(x):
        print 'call ' + f.__name__ + '()...'
        return f(x)
    return fn```
對于階乘函數(shù),@log工作得很好:

def log(f):
def fn(x):
print 'call ' + f.name + '()...'
return f(x)
return fn```
對于階乘函數(shù),@log工作得很好:

@log
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)```
**結(jié)果:**

call factorial()...
3628800```
但是,對于參數(shù)不是一個的函數(shù),調(diào)用將報錯:

def add(x, y):
    return x + y
print add(1, 2)```
**結(jié)果:**

Traceback (most recent call last):
File "test.py", line 15, in <module>
print add(1,2)
TypeError: fn() takes exactly 1 argument (2 given)```
因為 add() 函數(shù)需要傳入兩個參數(shù),但是 @log 寫死了只含一個參數(shù)的返回函數(shù)。
要讓 @log 自適應(yīng)任何參數(shù)定義的函數(shù),可以利用Python的 *args 和 **kw,保證任意個數(shù)的參數(shù)總是能正常調(diào)用:

def log(f):
    def fn(*args, **kw):
        print 'call ' + f.__name__ + '()...'
        return f(*args, **kw)
    return fn```
現(xiàn)在,對于任意函數(shù),@log 都能正常工作。

編寫一個@performance,它可以打印出函數(shù)調(diào)用的時間。

計算函數(shù)調(diào)用的時間可以記錄調(diào)用前后的當(dāng)前時間戳,然后計算兩個時間戳的差。
**代碼:**

import time

def performance(f):
def fn(*args, *kw):
t1 = time.time()
r = f(
args, **kw)
t2 = time.time()
print 'call %s() in %fs' % (f.name, (t2 - t1))
return r
return fn

@performance
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))

print factorial(10)```

結(jié)果:

call factorial() in 0.168969s
3628800```


>#### 編寫帶參數(shù)decorator

考察上一節(jié)的 @log 裝飾器:

def log(f):
def fn(x):
print 'call ' + f.name + '()...'
return f(x)
return fn```

發(fā)現(xiàn)對于被裝飾的函數(shù),log打印的語句是不能變的(除了函數(shù)名)。

如果有的函數(shù)非常重要,希望打印出'[INFO] call xxx()...',有的函數(shù)不太重要,希望打印出'[DEBUG] call xxx()...',這時,log函數(shù)本身就需要傳入'INFO'或'DEBUG'這樣的參數(shù),類似這樣:

@log('DEBUG')
def my_func():
    pass```
把上面的定義翻譯成高階函數(shù)的調(diào)用,就是:

my_func = log('DEBUG')(my_func)```

上面的語句看上去還是比較繞,再展開一下:

log_decorator = log('DEBUG')
my_func = log_decorator(my_func)```
上面的語句又相當(dāng)于:

log_decorator = log('DEBUG')
@log_decorator
def my_func():
pass```

所以,帶參數(shù)的log函數(shù)首先返回一個decorator函數(shù),再讓這個decorator函數(shù)接收my_func并返回新函數(shù):

def log(prefix):
    def log_decorator(f):
        def wrapper(*args, **kw):
            print '[%s] %s()...' % (prefix, f.__name__)
            return f(*args, **kw)
        return wrapper
    return log_decorator

@log('DEBUG')
def test():
    pass
print test()```

**執(zhí)行結(jié)果:**

[DEBUG] test()...
None```

對于這種3層嵌套的decorator定義,可以先把它拆開:

# 標(biāo)準decorator:
def log_decorator(f):
    def wrapper(*args, **kw):
        print '[%s] %s()...' % (prefix, f.__name__)
        return f(*args, **kw)
    return wrapper
return log_decorator

# 返回decorator:
def log(prefix):
    return log_decorator(f)```

拆開以后會發(fā)現(xiàn),調(diào)用會失敗,因為在3層嵌套的decorator定義中,最內(nèi)層的wrapper引用了最外層的參數(shù)prefix,所以,把一個閉包拆成普通的函數(shù)調(diào)用會比較困難。不支持閉包的編程語言要實現(xiàn)同樣的功能就需要更多的代碼。

考察上一節(jié)的@performance只能打印秒,請給 @performace 增加一個參數(shù),允許傳入's'或'ms':

@performance('ms')
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))```
要實現(xiàn)帶參數(shù)的@performance,就需要實現(xiàn):

my_func = performance('ms')(my_func)

需要3層嵌套的decorator來實現(xiàn)。代碼:

import time
def performance(unit):
    def perf_decorator(f):
        def wrapper(*args, **kw):
            t1 = time.time()
            r = f(*args, **kw)
            t2 = time.time()
            t = (t2 - t1) * 1000 if unit=='ms' else (t2 - t1)
            print 'call %s() in %f %s' % (f.__name__, t, unit)
            return r
        return wrapper
    return perf_decorator

@performance('ms')
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)```


>#### 完善decorator

@decorator可以動態(tài)實現(xiàn)函數(shù)功能的增加,但是,經(jīng)過@decorator“改造”后的函數(shù),和原函數(shù)相比,除了功能多一點外,有沒有其它不同的地方?
在沒有decorator的情況下,打印函數(shù)名:

def f1(x):
pass
print f1.name```

有decorator的情況下,再打印函數(shù)名:

def log(f):
    def wrapper(*args, **kw):
        print 'call...'
        return f(*args, **kw)
    return wrapper
@log
def f2(x):
    pass
print f2.__name__```
**輸出:** wrapper

可見,由于decorator返回的新函數(shù)函數(shù)名已經(jīng)不是'f2',而是@log內(nèi)部定義的'wrapper'。這對于那些依賴函數(shù)名的代碼就會失效。decorator還改變了函數(shù)的__doc__等其它屬性。如果要讓調(diào)用者看不出一個函數(shù)經(jīng)過了@decorator的“改造”,就需要把原函數(shù)的一些屬性復(fù)制到新函數(shù)中:

def log(f):
def wrapper(*args, *kw):
print 'call...'
return f(
args, **kw)
wrapper.name = f.name
wrapper.doc = f.doc
return wrapper```
這樣寫decorator很不方便,因為我們也很難把原函數(shù)的所有必要屬性都一個一個復(fù)制到新函數(shù)上,所以Python內(nèi)置的functools可以用來自動化完成這個“復(fù)制”的任務(wù):

import functools
def log(f):
    @functools.wraps(f)
    def wrapper(*args, **kw):
        print 'call...'
        return f(*args, **kw)
    return wrapper```

最后需要指出,由于我們把原函數(shù)簽名改成了(*args, **kw),因此,無法獲得原函數(shù)的原始參數(shù)信息。即便我們采用固定參數(shù)來裝飾只有一個參數(shù)的函數(shù):

def log(f):
@functools.wraps(f)
def wrapper(x):
print 'call...'
return f(x)
return wrapper```

也可能改變原函數(shù)的參數(shù)名,因為新函數(shù)的參數(shù)名始終是 'x',原函數(shù)定義的參數(shù)名不一定叫 'x'。

偏函數(shù)

當(dāng)一個函數(shù)有很多參數(shù)時,調(diào)用者就需要提供多個參數(shù)。如果減少參數(shù)個數(shù),就可以簡化調(diào)用者的負擔(dān)。

比如,int()函數(shù)可以把字符串轉(zhuǎn)換為整數(shù),當(dāng)僅傳入字符串時,int()函數(shù)默認按十進制轉(zhuǎn)換:

>>> int('12345')
12345```

但int()函數(shù)還提供額外的base參數(shù),默認值為10。如果傳入base參數(shù),就可以做 N 進制的轉(zhuǎn)換:

int('12345', base=8)
5349
int('12345', 16)
74565```

假設(shè)要轉(zhuǎn)換大量的二進制字符串,每次都傳入int(x, base=2)非常麻煩,于是,我們想到,可以定義一個int2()的函數(shù),默認把base=2傳進去:

def int2(x, base=2):
    return int(x, base)```

這樣,我們轉(zhuǎn)換二進制就非常方便了:

int2('1000000')
64
int2('1010101')
85```

<font color=#FF4500>functools.partial</font>就是幫助我們創(chuàng)建一個偏函數(shù)的,不需要我們自己定義int2(),可以直接使用下面的代碼創(chuàng)建一個新的函數(shù)<font color=#FF4500>int2</font>:

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85 ```

所以,<font color=#FF4500>functools.partial</font>可以把一個參數(shù)多的函數(shù)變成一個參數(shù)少的新函數(shù),少的參數(shù)需要在創(chuàng)建時指定默認值,這樣,新函數(shù)調(diào)用的難度就降低了。

### 2.模塊

>#### 模塊和包的概念

<font color=#FF4500>代碼越來越多的時候</font>

* 將所有代碼放入一個py文件:無法維護
* 如果將代碼分拆放入多個py文件,好處:
同一個名字的變量互不影響

<font color=#FF4500>引用其他模塊</font>

import math


<font color=#FF4500>引用完整模塊</font>

import p1.util```

<font color=#FF4500>在文件系統(tǒng)中</font>

  • 包就是文件夾
  • 模塊就是xxx.py
  • 包也有多級

<font color=#FF4500>如何區(qū)分包和普通目錄</font>

  • 包下面必須有一個init.py
  • 注意每層都必須要有!

導(dǎo)入模塊

要使用一個模塊,我們必須首先導(dǎo)入該模塊。Python使用import語句導(dǎo)入一個模塊。例如,導(dǎo)入系統(tǒng)自帶的模塊 math:

import math```

可以認為math就是一個指向已導(dǎo)入模塊的變量,通過該變量,我們可以訪問math模塊中所定義的所有公開的函數(shù)、變量和類:

math.pow(2, 0.5) # pow是函數(shù)
1.4142135623730951

math.pi # pi是變量
3.141592653589793```

如果我們只希望導(dǎo)入用到的math模塊的某幾個函數(shù),而不是所有函數(shù),可以用下面的語句:

from math import pow, sin, log```

這樣,可以直接引用 pow, sin, log 這3個函數(shù),但math的其他函數(shù)沒有導(dǎo)入進來:

pow(2, 10)
1024.0
sin(3.14)
0.0015926529164868282```

如果遇到名字沖突怎么辦?比如math模塊有一個log函數(shù),logging模塊也有一個log函數(shù),如果同時使用,如何解決名字沖突?

如果使用import導(dǎo)入模塊名,由于必須通過模塊名引用函數(shù)名,因此不存在沖突:

import math, logging

print math.log(10) # 調(diào)用的是math的log函數(shù)
logging.log(10, 'something') # 調(diào)用的是logging的log函數(shù)```

如果使用 from...import 導(dǎo)入 log 函數(shù),勢必引起沖突。這時,可以給函數(shù)起個“別名”來避免沖突:

from math import log
from logging import log as logger # logging的log現(xiàn)在變成了logger
print log(10) # 調(diào)用的是math的log
logger(10, 'import from logging') # 調(diào)用的是logging的log```

動態(tài)導(dǎo)入模塊

如果導(dǎo)入的模塊不存在,Python解釋器會報 ImportError 錯誤:

>>> import something
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named something```

有的時候,兩個不同的模塊提供了相同的功能,比如 **StringIO **和 **cStringIO **都提供了StringIO這個功能。

這是因為Python是動態(tài)語言,解釋執(zhí)行,因此Python代碼運行速度慢。

如果要提高Python代碼的運行速度,最簡單的方法是把某些關(guān)鍵函數(shù)用 C 語言重寫,這樣就能大大提高執(zhí)行速度。

同樣的功能,StringIO 是純Python代碼編寫的,而 cStringIO 部分函數(shù)是 C 寫的,因此 cStringIO 運行速度更快。

利用ImportError錯誤,我們經(jīng)常在Python中動態(tài)導(dǎo)入模塊:

try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO```

上述代碼先嘗試從cStringIO導(dǎo)入,如果失敗了(比如cStringIO沒有被安裝),再嘗試從StringIO導(dǎo)入。這樣,如果cStringIO模塊存在,則我們將獲得更快的運行速度,如果cStringIO不存在,則頂多代碼運行速度會變慢,但不會影響代碼的正常執(zhí)行。

**try **的作用是捕獲錯誤,并在捕獲到指定錯誤時執(zhí)行 **except **語句。

利用import ... as ...,還可以動態(tài)導(dǎo)入不同名稱的模塊。
Python 2.6/2.7提供了json 模塊,但Python 2.5以及更早版本沒有json模塊,不過可以安裝一個simplejson模塊,這兩個模塊提供的函數(shù)簽名和功能都一模一樣。
試寫出導(dǎo)入**json **模塊的代碼,能在Python 2.5/2.6/2.7都正常運行。
代碼:

try:
    import json
except ImportError:
    import simplejson as json

print json.dumps({'python':2.7})```

結(jié)果:

{"python": 2.7}```

使用future

Python的新版本會引入新的功能,但是,實際上這些功能在上一個老版本中就已經(jīng)存在了。要“試用”某一新的特性,就可以通過導(dǎo)入future模塊的某些功能來實現(xiàn)。

例如,Python 2.7的整數(shù)除法運算結(jié)果仍是整數(shù):

>>> 10 / 3
3```

但是,Python 3.x已經(jīng)改進了整數(shù)的除法運算,“**/**”除將得到浮點數(shù),“**//**”除才仍是整數(shù):

10 / 3
3.3333333333333335
10 // 3
3```

要在Python 2.7中引入3.x的除法規(guī)則,導(dǎo)入futuredivision

>>> from __future__ import division
>>> print 10 / 3
3.3333333333333335```

當(dāng)新版本的一個特性與舊版本不兼容時,該特性將會在舊版本中添加到**__future__**中,以便舊的代碼能在舊版本中測試新特性。

>#### 安裝第三方模塊

Python安裝了許多有用的模塊,可以安裝第三方模塊

Python提供了兩種模塊管理工具:
* easy_install
* pip(推薦,已內(nèi)置)


### 3.面向?qū)ο缶幊袒A(chǔ)

>#### 面向?qū)ο缶幊?
什么是面向?qū)ο缶幊蹋?* 面向?qū)ο缶幊淌且环N程序設(shè)計范式
* 把程序看成不同對象的相互調(diào)用
* 對現(xiàn)實世界建立對象模型

面向?qū)ο缶幊痰幕舅枷耄?* 類和實例
  * 類用于定義抽象類型
  * 實例根據(jù)類的定義被創(chuàng)建出來

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

class Person:
def init(self,name):
self.name=name```

p1=Person('Xiao Ming')
P2=Person('Xiao Jun')```

>#### 定義類并創(chuàng)建實例

Python中,類通過 **class **關(guān)鍵字定義。以 **Person** 為例,定義一個**Person類**如下:

class Person(object):
pass```
按照 Python 的編程習(xí)慣,類名以大寫字母開頭,緊接著是(object),表示該類是從哪個類繼承下來的。

有了Person類的定義,就可以創(chuàng)建出具體的xiaoming、xiaohong等實例。創(chuàng)建實例使用 類名+(),類似函數(shù)調(diào)用的形式創(chuàng)建

創(chuàng)建實例屬性

雖然可以通過Person類創(chuàng)建出xiaoming、xiaohong等實例,但是這些實例看上除了地址不同外,沒有什么其他不同。在現(xiàn)實世界中,區(qū)分xiaoming、xiaohong要依靠他們各自的名字、性別、生日等屬性。

如何讓<font color=#FF4500>每個實例擁有各自不同的屬性</font>?由于Python是動態(tài)語言,對每一個實例,都可以直接給他們的屬性賦值,例如,給xiaoming這個實例加上name、genderbirth屬性:

xiaoming = Person()
xiaoming.name = 'Xiao Ming'
xiaoming.gender = 'Male'
xiaoming.birth = '1990-1-1'```

給**xiaohong**加上的屬性不一定要和**xiaoming**相同:

xiaohong = Person()
xiaohong.name = 'Xiao Hong'
xiaohong.school = 'No. 1 High School'
xiaohong.grade = 2```

實例的屬性可以像普通變量一樣進行操作:

xiaohong.grade = xiaohong.grade + 1```

創(chuàng)建包含兩個 **Person **類的實例的 **list**,并給兩個實例的 **name** 賦值,然后按照 **name** 進行排序。

class Person(object):
pass

p1 = Person()
p1.name = 'Bart'

p2 = Person()
p2.name = 'Adam'

p3 = Person()
p3.name = 'Lisa'

L1 = [p1, p2, p3]
L2 = sorted(L1,lambda p1,p2:cmp(p1.name,p2.name))

print L2[0].name
print L2[1].name
print L2[2].name```

結(jié)果:

Adam
Bart
Lisa```

>#### 初始化實例屬性


雖然我們可以自由地給一個實例綁定各種屬性,但是,現(xiàn)實世界中,一種類型的實例應(yīng)該擁有相同名字的屬性。例如,**Person類**應(yīng)該在創(chuàng)建的時候就擁有 **name、gender **和 **birth **屬性,怎么辦?
在定義 Person 類時,可以為Person類添加一個特殊的**__init__()**方法,當(dāng)創(chuàng)建實例時,**__init__()**方法被自動調(diào)用,我們就能在此為每個實例都統(tǒng)一加上以下屬性:

class Person(object):
def init(self, name, gender, birth):
self.name = name
self.gender = gender
self.birth = birth```

**init() **方法的第一個參數(shù)必須是 self(也可以用別的名字,但建議使用習(xí)慣用法),后續(xù)參數(shù)則可以自由指定,和定義函數(shù)沒有任何區(qū)別。
相應(yīng)地,創(chuàng)建實例時,就必須要提供除 **self **以外的參數(shù):

xiaoming = Person('Xiao Ming', 'Male', '1991-1-1')
xiaohong = Person('Xiao Hong', 'Female', '1992-2-2')```

有了**__init__()**方法,每個Person實例在創(chuàng)建時,都會有 **name、gender **和 **birth **這3個屬性,并且,被賦予不同的屬性值,訪問屬性使用.操作符:

print xiaoming.name

輸出 'Xiao Ming'

print xiaohong.birth

輸出 '1992-2-2'```

定義Person類的init方法,除了接受 **name、gender **和 **birth **外,還可接受任意關(guān)鍵字參數(shù),并把他們都作為屬性賦值給實例。

**kw表示可以任意輸入?yún)?shù)和對應(yīng)的值,這些鍵值對被組織成字典dict,for k,v in kw.iteritems()表示對該字典的鍵值對進行迭代(k獲得鍵,v獲得值), iteritems()這個方法可以一個一個輸出字典中的鍵值對,setattr(self,k,v)表示對實例自身設(shè)置屬性和對應(yīng)的值,self表示實例自身,k表示參數(shù),v表示值。

class Person(object):
    def __init__(self, name, gender, birth, **kw):
        self.name = name
        self.gender = gender
        self.birth = birth
        for k, v in kw.iteritems():
            setattr(self, k, v)
xiaoming = Person('Xiao Ming', 'Male', '1990-1-1', job='Student')
print xiaoming.name
print xiaoming.job```
結(jié)果:

Xiao Ming
Student```

訪問限制

我們可以給一個實例綁定很多屬性,如果有些屬性不希望被外部訪問到怎么辦?
Python對屬性權(quán)限的控制是通過屬性名來實現(xiàn)的,如果一個屬性由雙下劃線開頭(__),該屬性就無法被外部訪問??蠢樱?/p>

class Person(object): 
    def __init__(self, name): 
        self.name = name 
        self._title = 'Mr' 
        self.__job = 'Student'
p = Person('Bob')
print p.name
# => Bob
print p._title
# => Mr
print p.__job
# => Error
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute '__job'

可見,只有以雙下劃線開頭的"__job"不能直接被外部訪問。
但是,如果一個屬性以"xxx"的形式定義,那它又可以被外部訪問了,以"xxx"定義的屬性在Python的類中被稱為特殊屬性,有很多預(yù)定義的特殊屬性可以使用,通常我們不要把普通屬性用"xxx"定義。
以單下劃線開頭的屬性"_xxx"雖然也可以被外部訪問,但是,按照習(xí)慣,他們不應(yīng)該被外部訪問。

Person類init方法中添加namescore參數(shù),并把score綁定到__score屬性上,看看外部是否能訪問到。

創(chuàng)建類屬性

類是模板,而實例則是根據(jù)類創(chuàng)建的對象。
綁定在一個實例上的屬性不會影響其他實例,但是,類本身也是一個對象,如果在類上綁定一個屬性,則所有實例都可以訪問類的屬性,并且,所有實例訪問的類屬性都是同一個!也就是說,實例屬性每個實例各自擁有,互相獨立,而類屬性有且只有一份。
定義類屬性可以直接在 **class **中定義:

class Person(object): 
    <font color=#FF4500>address = 'Earth' </font>
    def __init__(self, name): 
        self.name = name```

因為類屬性是直接綁定在類上的,所以,訪問類屬性不需要創(chuàng)建實例,就可以直接訪問:

print Person.address

=> Earth```

對一個實例調(diào)用類的屬性也是可以訪問的,所有實例都可以訪問到它所屬的類的屬性:

p1 = Person('Bob')
p2 = Person('Alice')
print p1.address
# => Earth
print p2.address
# => Earth```

由于Python是動態(tài)語言,類屬性也是可以動態(tài)添加和修改的:

Person.address = 'China'
print p1.address

=> 'China'

print p2.address

=> 'China'```

因為類屬性只有一份,所以,當(dāng)Person類的address改變時,所有實例訪問到的類屬性都改變了。

給 **Person 類添加一個類屬性 count,每創(chuàng)建一個實例,count **屬性就加 1,這樣就可以統(tǒng)計出一共創(chuàng)建了多少個 **Person **的實例。

class Person(object):
    count=0
    def __init__(self,name):
        Person.count=Person.count+1
        self.name=name

p1 = Person('Bob')
print Person.count

p2 = Person('Alice')
print Person.count

p3 = Person('Tim')
print Person.count```
結(jié)果:

1
2
3```

類屬性和實例屬性名字沖突怎么辦

修改類屬性會導(dǎo)致所有實例訪問到的類屬性全部都受影響,但是,如果在實例變量上修改類屬性會發(fā)生什么問題呢?

class Person(object):
    address = 'Earth'
    def __init__(self, name):
        self.name = name

p1 = Person('Bob')
p2 = Person('Alice')

print 'Person.address = ' + Person.address

p1.address = 'China'
print 'p1.address = ' + p1.address

print 'Person.address = ' + Person.address
print 'p2.address = ' + p2.address```

結(jié)果如下:

Person.address = Earth
p1.address = China
Person.address = Earth
p2.address = Earth```

我們發(fā)現(xiàn),在設(shè)置了 **p1.address = 'China' **后,p1訪問 address 確實變成了 'China',但是,Person.address和p2.address仍然是'Earch',怎么回事?
原因是 p1.address = 'China'并沒有改變 Person 的 address,而是給 p1這個實例綁定了實例屬性address ,對p1來說,它有一個實例屬性address(值是'China'),而它所屬的類Person也有一個類屬性address,所以:
訪問 p1.address ****時,優(yōu)先查找實例屬性,返回'China'。
訪問 p2.address ****時,p2沒有實例屬性address,但是有類屬性address,因此返回'Earth'。
可見,當(dāng)實例屬性和類屬性重名時,實例屬性優(yōu)先級高,它將屏蔽掉對類屬性的訪問。

當(dāng)我們把 p1 的 address 實例屬性刪除后,訪問 p1.address 就又返回類屬性的值 'Earth'了:

del p1.address
print p1.address
# => Earth```

可見,千萬不要在實例上修改類屬性,它實際上并沒有修改類屬性,而是給實例綁定了一個實例屬性。

把上節(jié)的 **Person **類屬性 **count **改為** __count**,再試試能否從實例和類訪問該屬性。

把**count**改為私有**__count**,這樣實例變量在外部無法修改**__count**
**代碼:**

class Person(object):
__count = 0
def init(self, name):
Person.__count = Person.__count + 1
self.name = name
print Person.__count

p1 = Person('Bob')
p2 = Person('Alice')

print Person.__count```
結(jié)果:

1
2
attributeError
['_Person__count', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']```

>#### 定義實例方法


一個實例的私有屬性就是以__開頭的屬性,無法被外部訪問,那這些屬性定義有什么用?
雖然私有屬性無法從外部訪問,但是,從類的內(nèi)部是可以訪問的。除了可以定義實例的屬性外,還可以定義實例的方法。

**實例的方法**就是在類中定義的函數(shù),它的第一個參數(shù)永遠是 self,指向調(diào)用該方法的實例本身,其他參數(shù)和一個普通函數(shù)是完全一樣的:

class Person(object):

def __init__(self, name):
    self.__name = name

def get_name(self):
    return self.__name

**get_name(self) **就是一個實例方法,它的第一個參數(shù)是self。_**_init__(self, name)**其實也可看做是一個特殊的實例方法。

調(diào)用實例方法必須在實例上調(diào)用:

p1 = Person('Bob')
print p1.get_name() # self不需要顯式傳入

=> Bob

在實例方法內(nèi)部,可以訪問所有實例屬性,這樣,如果外部需要訪問私有屬性,可以通過方法調(diào)用獲得,這種數(shù)據(jù)封裝的形式除了能保護內(nèi)部數(shù)據(jù)一致性外,還可以簡化外部調(diào)用的難度。

給 **Person **類增加一個私有屬性 **__score**,表示分數(shù),再增加一個實例方法 **get_grade()**,能根據(jù) **__score **的值分別返回 **A-優(yōu)秀, B-及格, C-不及格三檔。**

注意**get_grade()**是實例方法,第一個參數(shù)為**self**。

**代碼:**

class Person(object):

def __init__(self, name, score):
    self.__name = name
    self.__score = score

def get_grade(self):
    if self.__score >= 80:
        return 'A'
    if self.__score >= 60:
        return 'B'
    return 'C'

p1 = Person('Bob', 90)
p2 = Person('Alice', 65)
p3 = Person('Tim', 48)

print p1.get_grade()
print p2.get_grade()
print p3.get_grade()```
結(jié)果:

A
B
C```

>####  方法也是屬性

我們在 **class** 中定義的實例方法其實也是屬性,它實際上是一個函數(shù)對象:

class Person(object):
def init(self, name, score):
self.name = name
self.score = score
def get_grade(self):
return 'A'
p1 = Person('Bob', 90)
print p1.get_grade

=> <bound method Person.get_grade of <main.Person object at 0x109e58510>>

print p1.get_grade()

=> A```

也就是說,**p1.get_grade 返回的是一個函數(shù)對象,但這個函數(shù)是一個綁定到實例的函數(shù),p1.get_grade() **才是方法調(diào)用。

因為方法也是一個屬性,所以,它也可以動態(tài)地添加到實例上,只是需要用 types.MethodType() 把一個函數(shù)變?yōu)橐粋€方法:

import types
def fn_get_grade(self):
    if self.score >= 80:
        return 'A'
    if self.score >= 60:
        return 'B'
    return 'C'

class Person(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

p1 = Person('Bob', 90)
p1.get_grade = types.MethodType(fn_get_grade, p1, Person)

print p1.get_grade()
# => A
p2 = Person('Alice', 65)
print p2.get_grade()
# ERROR: AttributeError: 'Person' object has no attribute 'get_grade'
# 因為p2實例并沒有綁定get_grade

給一個實例動態(tài)添加方法并不常見,直接在class中定義要更直觀。

定義類方法

和屬性類似,方法也分實例方法和類方法。

class中定義的全部是實例方法,實例方法第一個參數(shù) **self **是實例本身。

要在class中定義類方法,需要這么寫:

class Person(object): 
    count = 0 
    @classmethod 
    def how_many(cls): 
        return cls.count 
    def __init__(self, name): 
        self.name = name 
        Person.count = Person.count + 1
print Person.how_many()
p1 = Person('Bob')
print Person.how_many()```

通過標(biāo)記一個 @classmethod,該方法將綁定到** Person **類上,而非類的實例。類方法的第一個參數(shù)將傳入類本身,通常將參數(shù)名命名為** cls**,上面的 **cls.count **實際上相當(dāng)于 **Person.count**。

因為是在類上調(diào)用,而非實例上調(diào)用,因此類方法無法獲得任何實例變量,只能獲得類的引用。


如果將類屬性 **count **改為私有屬性**__count**,則外部無法讀取**__score**,但可以通過一個類方法獲取,請編寫類方法獲得**__count**值。注意類方法需要添加 @classmethod:

class Person(object):

__count = 0

@classmethod
def how_many(cls):
    return cls.__count
def __init__(self,name):
    self.name=name
    Person.__count=Person.__count+1

print Person.how_many()

p1 = Person('Bob')

print Person.how_many()```

結(jié)果:

0
1```

### 4.類的繼承和多態(tài)

>#### 什么是繼承

如果想寫一個新的類:Student
需要的屬性有:name、gender、school、score
是否需要從頭編寫呢?
能否利用已有的Person類的屬性和方法呢?
考察已有的Person類:

class Person(object):
def init(self,name,gender):
self.name=name
self.gender=gender

可以從Person類中繼承出Student類:

class Student(Person):
def init(self,name,gender,school,score):
super(Student,self).init(name,gender)
self.school=school
self.score=score


所以:
- 新類不用從頭編寫
- 新類從現(xiàn)有的類繼承,就自動擁有了現(xiàn)有類的所有功能
- 新類只需要編寫現(xiàn)有的類缺少的新功能

繼承的好處:
- 復(fù)用已有的代碼
- 自動擁有了現(xiàn)有類的所有功能
- 只需要編寫缺少的新功能


父類和子類:

Person稱為父類、基類,超類
Student稱為子類、派生類、繼承類


繼承的特點:

 子類和父類是is關(guān)系:

如果一個實例是子類,那么它也是一個父類

Python繼承的特點:

總是從某個類繼承
**不要忘記調(diào)用super().__init__**,它是用來初始化父類的,如果忘記,父類的屬性就可能沒有被初始化。

>#### 繼承一個類

如果已經(jīng)定義了**Person**類,需要定義新的**Student**和**Teacher**類時,可以直接從Person類繼承:

class Person(object):
def init(self,name,gender):
self.name=name
self.gender=gender

定義Student類時,只需要把額外的屬性加上,例如score:

class Student(Person):
def init(self,name,gender,score):
super(Student,self).init(name,gender)
self.score=score```

一定要用 super(Student, self).init(name, gender) 去初始化父類,否則,繼承自 PersonStudent 將沒有** name** 和 gender。

函數(shù)super(Student, self)將返回當(dāng)前類繼承的父類,即** Person ,然后調(diào)用init()**方法,注意self參數(shù)已在super()中傳入,在<font color=#FF4500>init()中將隱式傳遞,不需要寫出(也不能寫)</font>。

判斷類型

函數(shù)isinstance()可以判斷一個變量的類型,既可以用在Python內(nèi)置的數(shù)據(jù)類型如str、list、dict,也可以用在我們自定義的類,它們本質(zhì)上都是數(shù)據(jù)類型。

假設(shè)有如下的 Person、Student 和 **Teacher **的定義及繼承關(guān)系如下:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

class Student(Person): 
    def __init__(self, name, gender, score): 
        super(Student, self).__init__(name, gender) 
        self.score = score

class Teacher(Person): 
    def __init__(self, name, gender, course):  
        super(Teacher, self).__init__(name, gender)       
        self.course = course

p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')```


當(dāng)我們拿到變量 **p、s、t **時,可以使用 **isinstance** 判斷類型:

isinstance(p, Person)
True # p是Person類型
isinstance(p, Student)
False # p不是Student類型
isinstance(p, Teacher)False

p不是Teacher類型```

這說明在繼承鏈上,一個父類的實例不能是子類類型,因為子類比父類多了一些屬性和方法。
我們再考察 s :

>>> isinstance(s, Person)
True # s是Person類型
>>> isinstance(s, Student)
True # s是Student類型
>>> isinstance(s, Teacher)
False # s不是Teacher類型```

s 是Student類型,不是Teacher類型,這很容易理解。但是,s 也是Person類型,因為Student繼承自Person,雖然它比Person多了一些屬性和方法,但是,把 s 看成Person的實例也是可以的。


這說明在一條繼承鏈上,一個實例可以看成它本身的類型,也可以看成它父類的類型。

>#### 多態(tài)

類具有繼承關(guān)系,并且子類類型可以向上轉(zhuǎn)型看做父類類型,如果我們從 **Person **派生出 **Student**和**Teacher **,并都寫了一個 **whoAmI() **方法:

class Person(object):
def init(self, name, gender):
self.name = name
self.gender = gender
def whoAmI(self):
return 'I am a Person, my name is %s' % self.name

class Student(Person):
def init(self, name, gender, score):
super(Student, self).init(name, gender)
self.score = score
def whoAmI(self):
return 'I am a Student, my name is %s' % self.name

class Teacher(Person):
def init(self, name, gender, course):
super(Teacher, self).init(name, gender)
self.course = course
def whoAmI(self):
return 'I am a Teacher, my name is %s' % self.name```

在一個函數(shù)中,如果我們接收一個變量** x,則無論該 x Person、Student還是Teacher**,都可以正確打印出結(jié)果:

def who_am_i(x):
    print x.whoAmI()

p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')

who_am_i(p)
who_am_i(s)
who_am_i(t)```
運行結(jié)果:

I am a Person, my name is Tim
I am a Student, my name is Bob
I am a Teacher, my name is Alice```

這種行為稱為多態(tài)。也就是說,方法調(diào)用將作用在 x 的實際類型上。sStudent類型,它實際上擁有自己的** whoAmI()**方法以及從 Person繼承的 whoAmI方法,但調(diào)用 s.whoAmI()總是先查找它自身的定義,如果沒有定義,則順著繼承鏈向上查找,直到在某個父類中找到為止。

由于Python是動態(tài)語言,所以,傳遞給函數(shù) who_am_i(x)的參數(shù) x 不一定是 Person 或 Person 的子類型。任何數(shù)據(jù)類型的實例都可以,只要它有一個whoAmI()的方法即可:

class Book(object):
    def whoAmI(self):
        return 'I am a book'```

這是動態(tài)語言和靜態(tài)語言(例如Java)最大的差別之一。動態(tài)語言調(diào)用實例方法,不檢查類型,只要方法存在,參數(shù)正確,就可以調(diào)用。

>#### 多重繼承

除了從一個父類繼承外,Python允許從多個父類繼承,稱為多重繼承。

多重繼承的繼承鏈就不是一棵樹了,它像這樣:

class A(object):
def init(self, a):
print 'init A...'
self.a = a

class B(A):
def init(self, a):
super(B, self).init(a)
print 'init B...'

class C(A):
def init(self, a):
super(C, self).init(a)
print 'init C...'

class D(B, C):
def init(self, a):
super(D, self).init(a)
print 'init D...'```

像這樣,D 同時繼承自 BC,也就是 D 擁有了 A、B、C 的全部功能。多重繼承通過 super()調(diào)用init()方法時,A 雖然被繼承了兩次,但init()只調(diào)用一次:

>>> d = D('d')
init A...
init C...
init B...
init D...```

**多重繼承的目的**是從兩種繼承樹中分別選擇并繼承出子類,以便組合功能使用。

舉個例子,Python的網(wǎng)絡(luò)服務(wù)器有**TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer**,而服務(wù)器運行模式有 **多進程ForkingMixin **和 **多線程ThreadingMixin**兩種。

要創(chuàng)建多進程模式的** TCPServer**:

class MyTCPServer(TCPServer, ForkingMixin)
pass```

要創(chuàng)建多線程模式的** UDPServer**:

class MyUDPServer(UDPServer, ThreadingMixin):
    pass```

如果沒有多重繼承,要實現(xiàn)上述所有可能的組合需要 4x2=8 個子類。

>#### 獲取對象信息

拿到一個變量,除了用 **isinstance() **判斷它是否是某種類型的實例外,還有沒有別的方法獲取到更多的信息呢?

例如,已有定義:

class Person(object):
def init(self, name, gender):
self.name = name
self.gender = gender

class Student(Person):
def init(self, name, gender, score):
super(Student, self).init(name, gender)
self.score = score
def whoAmI(self):
return 'I am a Student, my name is %s' % self.name```

首先可以用 **type() **函數(shù)獲取變量的類型,它返回一個 **Type **對象:

>>> type(123)
<type 'int'>
>>> s = Student('Bob', 'Male', 88)
>>> type(s)
<class '__main__.Student'>```
其次,可以用 **dir() **函數(shù)獲取變量的所有屬性:

dir(123) # 整數(shù)也有很多屬性...
['abs', 'add', 'and', 'class', 'cmp', ...]

dir(s)
['class', 'delattr', 'dict', 'doc', 'format', 'getattribute', 'hash', 'init', 'module', 'new', 'reduce', 'reduce_ex', 'repr', 'setattr', 'sizeof', 'str', 'subclasshook', 'weakref', 'gender', 'name', 'score', 'whoAmI']```

對于實例變量,dir()返回所有實例屬性,包括__class__這類有特殊意義的屬性。注意到方法whoAmI也是 **s **的一個屬性。

如何去掉__xxx__這類的特殊屬性,只保留我們自己定義的屬性?回顧一下filter()函數(shù)的用法。

dir()返回的屬性是字符串列表,如果已知一個屬性名稱,要獲取或者設(shè)置對象的屬性,就需要用 getattr() setattr( )函數(shù)了:

>>> getattr(s, 'name')  # 獲取name屬性
'Bob'

>>> setattr(s, 'name', 'Adam')  # 設(shè)置新的name屬性

>>> s.name
'Adam'

>>> getattr(s, 'age')  # 獲取age屬性,但是屬性不存在,報錯:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'

>>> getattr(s, 'age', 20)  # 獲取age屬性,如果屬性不存在,就返回默認值20:
20```

### 5.定制類

>#### 什么是特殊方法

Python如何把任意變量變成str?

因為任何數(shù)據(jù)類型的實例都有一個特殊方法:
`__str__()`

我們打印list:

print lst.str()
[1,2,3]```
實際是調(diào)用的lst的 __str__()方法

Python的特殊方法:

用于print的'str'
用于len的'len'
用于cmp的'cmp'
......

特點:

特殊方法定義在class中
不需要直接調(diào)用
Python的某些函數(shù)或者操作符會調(diào)用對應(yīng)的特殊方法

正確實現(xiàn)特殊方法:

只需要編寫用到的特殊方法

有關(guān)聯(lián)的特殊方法都必須實現(xiàn),如
__getattr__
__setattr__
__delattr__

_str_ 和_repr_

如果要把一個類的實例變成 str,就需要實現(xiàn)特殊方法__str__()

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    def __str__(self):
        return '(Person: %s, %s)' % (self.name, self.gender)```
現(xiàn)在,在交互式命令行下用 print 試試:

p = Person('Bob', 'male')
print p
(Person: Bob, male)```
但是,如果直接敲變量 p:

>>> p
<main.Person object at 0x10c941890>```
似乎__str__() 不會被調(diào)用。

因為 Python 定義了__str__()和__repr__()兩種方法,__str__()用于顯示給用戶,而__repr__()用于顯示給開發(fā)人員。

有一個偷懶的定義__repr__的方法:

class Person(object):
def init(self, name, gender):
self.name = name
self.gender = gender
def str(self):
return '(Person: %s, %s)' % (self.name, self.gender)
repr = str```

Student 類定義strrepr方法,使得能打印出<Student: name, gender, score>:

class Person(object):

    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

class Student(Person):

    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score

    def __str__(self):
        return '(Student: %s,%s,%s)' % (self.name,self.gender,self.score)
    __repr__=__str__

s = Student('Bob', 'male', 88)
print s```
結(jié)果:

(Student: Bob,male,88)```

_cmp_

對 int、str 等內(nèi)置數(shù)據(jù)類型排序時,Python的 sorted() 按照默認的比較函數(shù) cmp 排序,但是,如果對一組 Student 類的實例排序時,就必須提供我們自己的特殊方法 cmp():

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def __str__(self):
        return '(%s: %s)' % (self.name, self.score)
    __repr__ = __str__

    def __cmp__(self, s):
        if self.name < s.name:
            return -1
        elif self.name > s.name:
            return 1
        else:
            return 0```

上述 Student 類實現(xiàn)了__cmp__()方法,__cmp__用實例自身self和傳入的實例 s 進行比較,如果 self 應(yīng)該排在前面,就返回 -1,如果 s 應(yīng)該排在前面,就返回1,如果兩者相當(dāng),返回 0。

Student類實現(xiàn)了按name進行排序:

L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
print sorted(L)
[(Alice: 77), (Bob: 88), (Tim: 99)]```
注意: 如果list不僅僅包含 Student 類,則 cmp 可能會報錯:

L = [Student('Tim', 99), Student('Bob', 88), 100, 'Hello']
print sorted(L)```
修改 **Student **的 **__cmp__ **方法,讓它按照分數(shù)從高到底排序,分數(shù)相同的按名字排序。

class Student(object):

def __init__(self, name, score):
    self.name = name
    self.score = score

def __str__(self):
    return '(%s: %s)' % (self.name, self.score)

__repr__ = __str__

def __cmp__(self, s):
    if self.score == s.score:
        return cmp(self.name, s.name)
    return -cmp(self.score, s.score)

L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
print sorted(L)```
結(jié)果:

[(Alice: 99), (Tim: 99), (Bob: 88)]```


>#### \__len__

如果一個類表現(xiàn)得像一個list,要獲取有多少個元素,就得用 len() 函數(shù)。

要讓 len() 函數(shù)工作正常,類必須提供一個特殊方法`__len__()`,它返回元素的個數(shù)。

例如,我們寫一個 Students 類,把名字傳進去:

class Students(object):
def init(self, *args):
self.names = args
def len(self):
return len(self.names)```
只要正確實現(xiàn)了len()方法,就可以用len()函數(shù)返回Students實例的“長度”:

>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print len(ss)
3```
 
斐波那契數(shù)列是由 0, 1, 1, 2, 3, 5, 8...構(gòu)成。
編寫一個**Fib類**,F(xiàn)ib(10)表示數(shù)列的前10個元素,**print Fib(10) **可以打印出數(shù)列的前 10 個元素,**len(Fib(10))**可以正確返回數(shù)列的個數(shù)10。

class Fib(object):

def __init__(self, num):
    a,b,L=0,1,[]
    for n in range(num):
        L.append(a)
        a,b=b,a+b
    self.numbers=L
def __str__(self):
    return str(self.numbers)
__repr__=__str__
def __len__(self):
    return len(self.numbers)

f = Fib(10)
print f
print len(f)```
結(jié)果:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
10```

>#### 數(shù)學(xué)運算

Python 提供的基本數(shù)據(jù)類型 int、float 可以做整數(shù)和浮點的四則運算以及乘方等運算。

但是,四則運算不局限于int和float,還可以是有理數(shù)、矩陣等。

要表示有理數(shù),可以用一個Rational類來表示:

class Rational(object):
def init(self, p, q):
self.p = p
self.q = q```
p、q 都是整數(shù),表示有理數(shù) p/q。

如果要讓Rational進行+運算,需要正確實現(xiàn)add

class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q
    def __add__(self, r):
        return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
    def __str__(self):
        return '%s/%s' % (self.p, self.q)
    __repr__ = __str__```
現(xiàn)在可以試試有理數(shù)加法:

r1 = Rational(1, 3)
r2 = Rational(1, 2)
print r1 + r2
5/6```

Rational類雖然可以做加法,但無法做減法、乘方和除法,繼續(xù)完善Rational類,實現(xiàn)四則運算。

提示:
減法運算:__sub__
乘法運算:__mul__
除法運算:__div__

def gcd(a, b):
    if b == 0:
        return a
    return gcd(b, a % b)
    
class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q

    def __add__(self, r):
        return Rational(self.p * r.q + self.q * r.p, self.q * r.q)

    def __sub__(self, r):
        return Rational(self.p * r.q - self.q * r.p, self.q * r.q)

    def __mul__(self, r):
        return Rational(self.p * r.p, self.q * r.q)

    def __div__(self, r):
        return Rational(self.p * r.q, self.q * r.p)

    def __str__(self):
        g=gcd(self.p,self.q)
        return '%s/%s' % (self.p/g, self.q/g)

    __repr__ = __str__

r1 = Rational(1, 2)
r2 = Rational(1, 4)
print r1 + r2
print r1 - r2
print r1 * r2
print r1 / r2```

結(jié)果:

3/4
1/4
1/8
2/1```

類型轉(zhuǎn)換

Rational類實現(xiàn)了有理數(shù)運算,但是,如果要把結(jié)果轉(zhuǎn)為 int 或 float 怎么辦?

考察整數(shù)和浮點數(shù)的轉(zhuǎn)換:

>>> int(12.34)
12
>>> float(12)
12.0```
如果要把 Rational 轉(zhuǎn)為 int,應(yīng)該使用:

r = Rational(12, 5)
n = int(r)```
要讓int()函數(shù)正常工作,只需要實現(xiàn)特殊方法int():

class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q
    def __int__(self):
        return self.p // self.q```
結(jié)果如下:

print int(Rational(7, 2))
3
print int(Rational(1, 3))
0```
同理,要讓float()函數(shù)正常工作,只需要實現(xiàn)特殊方法float():

class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q

    def __int__(self):
        return self.p // self.q

    def __float__(self):
        return float(self.p)/self.q

print float(Rational(7, 2))
print float(Rational(1, 3))```
結(jié)果:

3.5
0.333333333333```

@property

考察 Student 類:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score```
當(dāng)我們想要修改一個 Student 的 scroe 屬性時,可以這么寫:

s = Student('Bob', 59)
s.score = 60```
但是也可以這么寫:

s.score = 1000```
顯然,直接給屬性賦值無法檢查分數(shù)的有效性。
如果利用兩個方法:

class Student(object):
def init(self, name, score):
self.name = name
self.__score = score
def get_score(self):
return self.__score
def set_score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')
self.__score = score```

這種使用 **get/set **方法來封裝對一個屬性的訪問在許多面向?qū)ο缶幊痰恼Z言中都很常見。

但是寫** s.get_score()** 和 **s.set_score() **沒有直接寫 s.score 來得直接。

有沒有兩全其美的方法?----有。

因為Python支持高階函數(shù),在函數(shù)式編程中我們介紹了裝飾器函數(shù),可以用裝飾器函數(shù)把 **get/set **方法“裝飾”成屬性調(diào)用,@property,它是python內(nèi)置的裝飾器,負責(zé)把一個方法變成屬性調(diào)用:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.__score = score
    @property
    def score(self):
        return self.__score
    @score.setter
    def score(self, score):
        if score < 0 or score > 100:
            raise ValueError('invalid score')
        self.__score = score```

注意: 第一個score(self)是get方法,用@property裝飾,第二個score(self, score)是set方法,用@score.setter裝飾,@score.setter是前一個@property裝飾后的副產(chǎn)品。

現(xiàn)在,就可以像使用屬性一樣設(shè)置score了:

s = Student('Bob', 59)
s.score = 60
print s.score
60
s.score = 1000
Traceback (most recent call last):
...
ValueError: invalid score```
說明對 score 賦值實際調(diào)用的是 set方法。

**注意:get score(self) 不帶參數(shù)的返回函數(shù)相當(dāng)于get score(), set score(self, score)傳入了一個score參數(shù)相當(dāng)于 set score(score)

get score(self) 中的self 是get score()作為方法必須要有的。同樣set score()總的self也是。。除去self 不管的話,get score()“相當(dāng)于”無參函數(shù),返回了score 。而 set score ()需要調(diào)用score作為參數(shù)。**

_slots_

由于Python是動態(tài)語言,任何實例在運行期都可以動態(tài)地添加屬性。

如果要限制添加的屬性,例如,Student類只允許添加 name、gender和score 這3個屬性,就可以利用Python的一個特殊的__slots__來實現(xiàn)。

顧名思義,__slots__是指一個類允許的屬性列表:

class Student(object):
    __slots__ = ('name', 'gender', 'score')
    def __init__(self, name, gender, score):
        self.name = name
        self.gender = gender
        self.score = score```

**__slots__**的目的是限制當(dāng)前類所能擁有的屬性,如果不需要添加任意動態(tài)的屬性,使用**`__slots__`**也能節(jié)省內(nèi)存。



假設(shè)**Person**類通過**`__slots__`**定義了**name**和**gender**,在派生類**Student**中通過**`__slots__`**繼續(xù)添加**score**的定義,使**Student**類可以實現(xiàn)**name、gender**和**score** 3個屬性。**Student**類的 `__slots__`只需要包含**Person**類不包含的**score**屬性即可.

class Person(object):

__slots__ = ('name', 'gender')

def __init__(self, name, gender):
    self.name = name
    self.gender = gender

class Student(Person):

__slots__ = ('score',)

def __init__(self,name,gender,score):
    super(Student,self).__init__(name,gender)
    self.score=score

s = Student('Bob', 'male', 59)
s.name = 'Tim'
s.score = 99
print s.score```
結(jié)果:

99```





>#### \__call__

在Python中,函數(shù)其實是一個對象:

f = abs
f.name
'abs'
f(-123)
123```
由于 f 可以被調(diào)用,所以,f 被稱為可調(diào)用對象。

所有的函數(shù)都是可調(diào)用對象。

一個類實例也可以變成一個可調(diào)用對象,只需要實現(xiàn)一個特殊方法call()。

我們把 Person 類變成一個可調(diào)用對象:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def __call__(self, friend):
        print 'My name is %s...' % self.name
        print 'My friend is %s...' % friend```
現(xiàn)在可以對 Person 實例直接調(diào)用:

p = Person('Bob', 'male')
p('Tim')
My name is Bob...
My friend is Tim...```
單看 p('Tim') 你無法確定 p 是一個函數(shù)還是一個類實例,所以,在Python中,函數(shù)也是對象,對象和函數(shù)的區(qū)別并不顯著。

改進一下前面定義的斐波那契數(shù)列,加一個call方法,讓調(diào)用更簡單:

class Fib(object):
    def __call__(self,num):
        a,b,L=0,1,[]
        for n in range(num):
            L.append(a)
            a,b=b,a+b
        return L

f = Fib()
print f(10)```

結(jié)果:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]```

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容