python進階

1、函數式編程

1.1 函數式編程簡介

函數式編程:一種編程範式
函數式編程特色:python

  • 把計算是爲函數而非指令
  • 純函數式編程:不須要變量,沒有反作用,測試簡單
  • 支持高階函數,代碼簡潔

Python支持的函數式編程:編程

  • 不是純函數式編程:容許有變量
  • 支持高階函數:函數也可做爲變量傳入
  • 支持閉包:有了閉包就能返回函數
  • 有限度的支持匿名函數

1.2 高階函數

  • 變量能夠指向函數
  • 函數名其實就是指向函數的變量

高階函數:能接收函數作參數的函數vim

  • 變量能夠指向函數
  • 函數的參數能夠接受變量
  • 一個函數能夠接受另外一個函數做爲參數
  • 能接收函數作參數的函數就是高階函數

1.3 把函數做爲參數

咱們講了高階函數的概念,編寫一個簡單的高階函數:ruby

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

若是傳入abs做爲參數f的值:bash

add(-5, 9, abs)

根據函數的定義,函數執行的代碼其實是:服務器

abs(-5) + abs(9)

因爲參數 x, y 和 f 均可以任意傳入,若是 f 傳入其餘函數,就能夠獲得不一樣的返回值。網絡

1.4 map()函數

map()是 Python 內置的高階函數,它接收一個函數 f 和一個 list,並經過把函數 f 依次做用在 list 的每一個元素上,獲得一個新的 list 並返回。多線程

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

若是但願把list的每一個元素都做平方,就能夠用map()函數:
所以,咱們只須要傳入函數f(x)=x*x,就能夠利用map()函數完成這個計算:app

def f(x): return x*x print map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])

輸出結果:

`[1, 4, 9, 10, 25, 36, 49, 64, 81]
注意:map()函數不改變原有的 list,而是返回一個新的 list。

利用map()函數,能夠把一個 list 轉換爲另外一個 list,只須要傳入轉換函數。

因爲list包含的元素能夠是任何類型,所以,map() 不只僅能夠處理只包含數值的 list,事實上它能夠處理包含任意類型的 list,只要傳入的函數f能夠處理這種數據類型。

1.5 reduce()函數

reduce()函數也是Python內置的一個高階函數。reduce()函數接收的參數和 map()相似,一個函數 f,一個list,但行爲和 map()不一樣,reduce()傳入的函數 f 必須接收兩個參數,reduce()對list的每一個元素反覆調用函數f,並返回最終結果值。

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

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

調用 reduce(f, [1, 3, 5, 7, 9])時,reduce函數將作以下計算:

先計算頭兩個元素:f(1, 3),結果爲4;
再把結果和第3個元素計算:f(4, 5),結果爲9;
再把結果和第4個元素計算:f(9, 7),結果爲16;
再把結果和第5個元素計算:f(16, 9),結果爲25;
因爲沒有更多的元素了,計算結束,返回結果25。
上述計算其實是對 list 的全部元素求和。雖然Python內置了求和函數sum(),可是,利用reduce()求和也很簡單。

reduce()還能夠接收第3個可選參數,做爲計算的初始值。若是把初始值設爲100,計算:

reduce(f, [1, 3, 5, 7, 9], 100)
結果將變爲125,由於第一輪計算是:

計算初始值和第一個元素:f(100, 1),結果爲101。

1.6 filter()函數

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

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

def is_odd(x): return x % 2 == 1

而後,利用filter()過濾掉偶數:

filter(is_odd, [1, 4, 6, 7, 9, 12, 17])

結果:[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'])

結果:['test', 'str', 'END']

注意: s.strip(rm) 刪除 s 字符串中開頭、結尾處的 rm 序列的字符。

當rm爲空時,默認刪除空白符(包括'\n', '\r', '\t', ' '),以下:

a = ' 123'
a.strip()
結果: '123'

1.7 自定義排序函數

Python內置的 sorted()函數可對list進行排序:
>>>sorted([36, 5, 12, 9, 21])
[5, 9, 12, 21, 36]
但 sorted()也是一個高階函數,它能夠接收一個比較函數來實現自定義排序,比較函數的定義是,傳入兩個待比較的元素 x, y,若是 x 應該排在 y 的前面,返回 -1,若是 x 應該排在 y 的後面,返回 1。若是 x 和 y 相等,返回 0

所以,若是咱們要實現倒序排序,只須要編寫一個reversed_cmp函數:

def reversed_cmp(x, y):
if x > y:
return -1
if x < y:
return 1
return 0
這樣,調用 sorted() 並傳入 reversed_cmp 就能夠實現倒序排序:

>>> 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'小。

1.8 返回函數

Python的函數不但能夠返回int、str、list、dict等數據類型,還能夠返回函數!

例如,定義一個函數 f(),咱們讓它返回一個函數 g,能夠這樣寫:

def f(): print 'call f()...' # 定義函數g: def g(): print 'call g()...' # 返回函數g: return g

仔細觀察上面的函數定義,咱們在函數 f 內部又定義了一個函數 g。因爲函數 g 也是一個對象,函數名 g 就是指向函數 g 的變量,因此,最外層函數 f 能夠返回變量 g,也就是函數 g 自己

調用函數 f,咱們會獲得 f 返回的一個函數:

>>> x = f() # 調用f() call f()... >>> x # 變量x是f()返回的函數: <function g at 0x1037bf320> >>> x() # x指向函數,所以能夠調用 call g()... # 調用x()就是執行g()函數定義的代碼

請注意區分返回函數和返回值:

def myabs(): return abs # 返回函數 def myabs2(x): return abs(x) # 返回函數調用的結果,返回值是一個數值

返回函數能夠把一些計算延遲執行。例如,若是定義一個普通的求和函數:

def calc_sum(lst): return sum(lst)

調用calc_sum()函數時,將馬上計算並獲得結果:

>>> calc_sum([1, 2, 3, 4])10
可是,若是返回一個函數,就能夠「延遲計算」:

def calc_sum(lst): def lazy_sum(): return sum(lst) return lazy_sum # 調用calc_sum()並無計算出結果,而是返回函數: >>> f = calc_sum([1, 2, 3, 4]) >>> f <function lazy_sum at 0x1037bfaa0> # 對返回的函數進行調用時,才計算出結果: >>> f() 10

因爲能夠返回函數,咱們在後續代碼裏就能夠決定到底要不要調用該函數。

1.9 閉包

在函數內部定義的函數和外部定義的函數是同樣的,只是他們沒法被外部訪問:

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

將 g 的定義移入函數 f 內部,防止其餘代碼調用 g:

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

可是,考察上一小節定義的 calc_sum 函數:

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

注意: 發現無法把 lazy_sum 移到 calc_sum 的外部,由於它引用了 calc_sum 的參數 lst。

像這種內層函數引用了外層函數的變量(參數也算變量),而後返回內層函數的狀況,稱爲閉包(Closure)。

閉包的特色是返回的函數還引用了外層函數的局部變量,因此,要正確使用閉包,就要確保引用的局部變量在函數返回後不能變。舉例以下:

# 但願一次返回3個函數,分別計算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()

你可能認爲調用f1(),f2()和f3()結果應該是1,4,9,但實際結果所有都是 9(請本身動手驗證)。

緣由就是當count()函數返回了3個函數時,這3個函數所引用的變量 i 的值已經變成了3。因爲f一、f二、f3並無被調用,因此,此時他們並未計算 i*i,當 f1 被調用時:

>>> f1() 9 # 由於f1如今才計算i*i,但如今i的值已經變爲3

所以,返回函數不要引用任何循環變量,或者後續會發生變化的變量。

正確寫法:

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()

1.10 匿名函數

高階函數能夠接收函數作參數,有些時候,咱們不須要顯式地定義函數,直接傳入匿名函數更方便。
在Python中,對匿名函數提供了有限支持。仍是以map()函數爲例,計算 f(x)=x2 時,除了定義一個f(x)的函數外,還能夠直接傳入匿名函數:

>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]) [1, 4, 9, 16, 25, 36, 49, 64, 81]

經過對比能夠看出,匿名函數 lambda x: x * x 實際上就是:

def f(x): return x * x

關鍵字lambda 表示匿名函數,冒號前面的 x 表示函數參數。
匿名函數有個限制,就是隻能有一個表達式,不寫return,返回值就是該表達式的結果。
使用匿名函數,能夠沒必要定義函數名,直接建立一個函數對象,不少時候能夠簡化代碼:

>>> sorted([1, 3, 9, 5, 0], lambda x,y: -cmp(x,y)) [9, 5, 3, 1, 0] 返回函數的時候,也能夠返回匿名函數: >>> myabs = lambda x: -x if x < 0 else x >>> myabs(-1) 1 >>> myabs(1) 1

1.11 decorator裝飾器

認識裝飾器:

  1. 裝飾器用來裝飾函數
  2. 返回一個函數對象
  3. 被裝飾函數標識符指向的函數對象
  4. 語法糖 @deco

1.12 編寫無參數decorator

Python的 decorator 本質上就是一個高階函數,它接收一個函數做爲參數,而後,返回一個新函數。
使用 decorator 用Python提供的 @ 語法,這樣能夠避免手動編寫 f = decorate(f) 這樣的代碼。
考察一個@log的定義:

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

對於階乘函數,@log工做得很好:

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

結果:
call factorial()...
3628800
可是,對於參數不是一個的函數,調用將報錯:

@log def add(x, y): return x + y print add(1, 2)

結果:

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() 函數須要傳入兩個參數,可是 @log 寫死了只含一個參數的返回函數。
要讓 @log 自適應任何參數定義的函數,能夠利用Python的 args 和 *kw,保證任意個數的參數老是能正常調用:

def log(f): def fn(*args, **kw): print 'call ' + f.__name__ + '()...' return f(*args, **kw) return fn

如今,對於任意函數,@log 都能正常工做。

1.13 編寫帶參數decorator

考察上一節的 @log 裝飾器:

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

發現對於被裝飾的函數,log打印的語句是不能變的(除了函數名)。
若是有的函數很是重要,但願打印出'[INFO] call xxx()...',有的函數不過重要,但願打印出'[DEBUG] call xxx()...',這時,log函數自己就須要傳入'INFO'或'DEBUG'這樣的參數,相似這樣:

@log('DEBUG') def my_func(): pass

把上面的定義翻譯成高階函數的調用,就是:

my_func = log('DEBUG')(my_func)

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

log_decorator = log('DEBUG') my_func = log_decorator(my_func)

上面的語句又至關於:

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

因此,帶參數的log函數首先返回一個decorator函數,再讓這個decorator函數接收my_func並返回新函數:

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() #執行結果: [DEBUG] test()... None

對於這種3層嵌套的decorator定義,你能夠先把它拆開:

# 標準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)

拆開之後會發現,調用會失敗,由於在3層嵌套的decorator定義中,最內層的wrapper引用了最外層的參數prefix,因此,把一個閉包拆成普通的函數調用會比較困難。不支持閉包的編程語言要實現一樣的功能就須要更多的代碼。

1.14 完善decorator

@decorator能夠動態實現函數功能的增長,可是,通過@decorator「改造」後的函數,和原函數相比,除了功能多一點外,有沒有其它不一樣的地方?
在沒有decorator的狀況下,打印函數名:
def f1(x):
pass
print f1.name
輸出: f1
有decorator的狀況下,再打印函數名:
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返回的新函數函數名已經不是'f2',而是@log內部定義的'wrapper'。這對於那些依賴函數名的代碼就會失效。decorator還改變了函數的doc等其它屬性。若是要讓調用者看不出一個函數通過了@decorator的「改造」,就須要把原函數的一些屬性複製到新函數中:
def log(f):
def wrapper(args, *kw):
print 'call...'
return f(args, *kw)
wrapper.name = f.name
wrapper.doc = f.doc
return wrapper
這樣寫decorator很不方便,由於咱們也很難把原函數的全部必要屬性都一個一個複製到新函數上,因此Python內置的functools能夠用來自動化完成這個「複製」的任務:
import functools
def log(f):
@functools.wraps(f)
def wrapper(args, *kw):
print 'call...'
return f(args, *kw)
return wrapper
最後須要指出,因爲咱們把原函數簽名改爲了(args, *kw),所以,沒法得到原函數的原始參數信息。即使咱們採用固定參數來裝飾只有一個參數的函數:
def log(f):
@functools.wraps(f)
def wrapper(x):
print 'call...'
return f(x)
return wrapper
也可能改變原函數的參數名,由於新函數的參數名始終是 'x',原函數定義的參數名不必定叫 'x'。

1.15 偏函數

當一個函數有不少參數時,調用者就須要提供多個參數。若是減小參數個數,就能夠簡化調用者的負擔。
好比,int()函數能夠把字符串轉換爲整數,當僅傳入字符串時,int()函數默認按十進制轉換:

>>> int('12345') 12345

但int()函數還提供額外的base參數,默認值爲10。若是傳入base參數,就能夠作 N 進制的轉換:

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

假設要轉換大量的二進制字符串,每次都傳入int(x, base=2)很是麻煩,因而,咱們想到,能夠定義一個int2()的函數,默認把base=2傳進去:

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

這樣,咱們轉換二進制就很是方便了:

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

functools.partial就是幫助咱們建立一個偏函數的,不須要咱們本身定義int2(),能夠直接使用下面的代碼建立一個新的函數int2:

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

因此,functools.partial能夠把一個參數多的函數變成一個參數少的新函數,少的參數須要在建立時指定默認值,這樣,新函數調用的難度就下降了。

2、模塊

2.1 模塊和包的概念

在文件系統中:

  • 包就是文件夾
  • 模塊就是xxx.py
  • 包也能夠有多級
    區分包和普通目錄:
    包下面有個init.py文件,每層都必需要有

2.2 導入模塊

要使用一個模塊,咱們必須首先導入該模塊。Python使用import語句導入一個模塊。例如,導入系統自帶的模塊 math:
import math
你能夠認爲math就是一個指向已導入模塊的變量,經過該變量,咱們能夠訪問math模塊中所定義的全部公開的函數、變量和類:
>>> math.pow(2, 0.5) # pow是函數1.4142135623730951

>>> math.pi # pi是變量3.141592653589793
若是咱們只但願導入用到的math模塊的某幾個函數,而不是全部函數,能夠用下面的語句:
from math import pow, sin, log 這樣,能夠直接引用pow, sin,log` 這3個函數,但math的其餘函數沒有導入進來:

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

若是遇到名字衝突怎麼辦?好比math模塊有一個log函數,logging模塊也有一個log函數,若是同時使用,如何解決名字衝突?
若是使用import導入模塊名,因爲必須經過模塊名引用函數名,所以不存在衝突:
import math, logging
print math.log(10) # 調用的是math的log函數
logging.log(10, 'something') # 調用的是logging的log函數
若是使用 from...import 導入 log 函數,勢必引發衝突。這時,能夠給函數起個「別名」來避免衝突:
from math import log
from logging import log as logger # logging的log如今變成了logger

print log(10) # 調用的是math的log logger(10, 'import from logging') # 調用的是logging的log

2.3 動態導入模塊

若是導入的模塊不存在,Python解釋器會報 ImportError 錯誤:

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

有的時候,兩個不一樣的模塊提供了相同的功能,好比 StringIO 和 cStringIO 都提供了StringIO這個功能。
這是由於Python是動態語言,解釋執行,所以Python代碼運行速度慢。
若是要提升Python代碼的運行速度,最簡單的方法是把某些關鍵函數用 C 語言重寫,這樣就能大大提升執行速度。
一樣的功能,StringIO 是純Python代碼編寫的,而 cStringIO 部分函數是 C 寫的,所以 cStringIO 運行速度更快。
利用ImportError錯誤,咱們常常在Python中動態導入模塊:

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

上述代碼先嚐試從cStringIO導入,若是失敗了(好比cStringIO沒有被安裝),再嘗試從StringIO導入。這樣,若是cStringIO模塊存在,則咱們將得到更快的運行速度,若是cStringIO不存在,則頂多代碼運行速度會變慢,但不會影響代碼的正常執行。
try 的做用是捕獲錯誤,並在捕獲到指定錯誤時執行 except 語句。

2.4 使用future

Python的新版本會引入新的功能,可是,實際上這些功能在上一個老版本中就已經存在了。要「試用」某一新的特性,就能夠經過導入future模塊的某些功能來實現。
例如,Python 2.7的整數除法運算結果還是整數:

>>> 10 / 3 3

可是,Python 3.x已經改進了整數的除法運算,「/」除將獲得浮點數,「//」除才還是整數:

>>> 10 / 3 3.3333333333333335 >>> 10 // 3 3

要在Python 2.7中引入3.x的除法規則,導入future的division:

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

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

2.5 安裝第三方模塊

方法一:
easy_install
方法二:
pip(推薦,已內置到Python2.7.9)

要想找到合適的第三方模塊的名字能夠去pypi.python.org搜索

3、面向對象編程基礎

3.1 面向對象編程

什麼是面向對象編程:

  1. 面向對象編程是一種程序設計範式
  2. 把程序看做不一樣對象的相互調用
  3. 對現實世界創建對象模型

面向對象編程的基本思想:
類和實例:
類用於定義抽象類型
實例根據類的定義被建立出來

面向對象編程:數據封裝

3.2 定義類並建立實例

在Python中,類經過 class 關鍵字定義。以 Person 爲例,定義一個Person類以下:

class Person(object): pass

按照 Python 的編程習慣,類名以大寫字母開頭,緊接着是(object),表示該類是從哪一個類繼承下來的。類的繼承將在後面的章節講解,如今咱們只須要簡單地從object類繼承。
有了Person類的定義,就能夠建立出具體的xiaoming、xiaohong等實例。建立實例使用 類名+(),相似函數調用的形式建立:

xiaoming = Person() xiaohong = Person()

3.3 建立實例屬性

雖然能夠經過Person類建立出xiaoming、xiaohong等實例,可是這些實例看上除了地址不一樣外,沒有什麼其餘不一樣。在現實世界中,區分xiaoming、xiaohong要依靠他們各自的名字、性別、生日等屬性。
如何讓每一個實例擁有各自不一樣的屬性?因爲Python是動態語言,對每個實例,均可以直接給他們的屬性賦值,例如,給xiaoming這個實例加上name、gender和birth屬性:

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

3.4 初始化實例屬性

雖然咱們能夠自由地給一個實例綁定各類屬性,可是,現實世界中,一種類型的實例應該擁有相同名字的屬性。例如,Person類應該在建立的時候就擁有 name、gender 和 birth 屬性,怎麼辦?
在定義 Person 類時,能夠爲Person類添加一個特殊的init()方法,當建立實例時,init()方法被自動調用,咱們就能在此爲每一個實例都統一加上如下屬性:

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

init() 方法的第一個參數必須是 self(也能夠用別的名字,但建議使用習慣用法),後續參數則能夠自由指定,和定義函數沒有任何區別。
相應地,建立實例時,就必需要提供除 self 之外的參數:

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

有了init()方法,每一個Person實例在建立時,都會有 name、gender 和 birth 這3個屬性,而且,被賦予不一樣的屬性值,訪問屬性使用.操做符:

print xiaoming.name # 輸出 'Xiao Ming' print xiaohong.birth # 輸出 '1992-2-2'

要特別注意的是,初學者定義init()方法經常忘記了 self 參數:

>>> class Person(object): ... def __init__(name, gender, birth): ... pass ... >>> xiaoming = Person('Xiao Ming', 'Male', '1990-1-1') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __init__() takes exactly 3 arguments (4 given)

這會致使建立失敗或運行不正常,由於第一個參數name被Python解釋器傳入了實例的引用,從而致使整個方法的調用參數位置所有沒有對上。

3.5 訪問限制

咱們能夠給一個實例綁定不少屬性,若是有些屬性不但願被外部訪問到怎麼辦?
Python對屬性權限的控制是經過屬性名來實現的,若是一個屬性由雙下劃線開頭(__),該屬性就沒法被外部訪問。看例子:

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的類中被稱爲特殊屬性,有不少預約義的特殊屬性可使用,一般咱們不要把普通屬性用"xxx"定義。**
以單下劃線開頭的屬性"_xxx"雖然也能夠被外部訪問,可是,按照習慣,他們不該該被外部訪問。

3.6 建立類屬性

類是模板,而實例則是根據類建立的對象。
綁定在一個實例上的屬性不會影響其餘實例,可是,類自己也是一個對象,若是在類上綁定一個屬性,則全部實例均可以訪問類的屬性,而且,全部實例訪問的類屬性都是同一個!也就是說,實例屬性每一個實例各自擁有,互相獨立,而類屬性有且只有一份。
定義類屬性能夠直接在 class 中定義:

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

由於類屬性是直接綁定在類上的,因此,訪問類屬性不須要建立實例,就能夠直接訪問:

print Person.address # => Earth

對一個實例調用類的屬性也是能夠訪問的,全部實例均可以訪問到它所屬的類的屬性:

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

因爲Python是動態語言,類屬性也是能夠動態添加和修改的:

Person.address = 'China' print p1.address # => 'China' print p2.address # => 'China'

由於類屬性只有一份,因此,當Person類的address改變時,全部實例訪問到的類屬性都改變了。

3.7 類屬性和實例實型名字衝突怎麼辦

修改類屬性會致使全部實例訪問到的類屬性所有都受影響,可是,若是在實例變量上修改類屬性會發生什麼問題呢?

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

結果以下:

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

咱們發現,在設置了 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 時,優先查找實例屬性,返回'China'。
訪問 p2.address 時,p2沒有實例屬性address,可是有類屬性address,所以返回'Earth'。
可見,當實例屬性和類屬性重名時,實例屬性優先級高,它將屏蔽掉對類屬性的訪問。
當咱們把 p1 的 address 實例屬性刪除後,訪問 p1.address 就又返回類屬性的值 'Earth'了:

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

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

3.8 定義實例方法

一個實例的私有屬性就是以__開頭的屬性,沒法被外部訪問,那這些屬性定義有什麼用?
雖然私有屬性沒法從外部訪問,可是,從類的內部是能夠訪問的。除了能夠定義實例的屬性外,還能夠定義實例的方法。
實例的方法就是在類中定義的函數,它的第一個參數永遠是 self,指向調用該方法的實例自己,其餘參數和一個普通函數是徹底同樣的:

class Person(object):
def __init__(self, name): self.__name = name def get_name(self): return self.__name

get_name(self) 就是一個實例方法,它的第一個參數是self。init(self, name)其實也可看作是一個特殊的實例方法。
調用實例方法必須在實例上調用:

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

在實例方法內部,能夠訪問全部實例屬性,這樣,若是外部須要訪問私有屬性,能夠經過方法調用得到,這種數據封裝的形式除了能保護內部數據一致性外,還能夠簡化外部調用的難度。

3.9 方法也是屬性

咱們在 class 中定義的實例方法其實也是屬性,它其實是一個函數對象:

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 返回的是一個函數對象,但這個函數是一個綁定到實例的函數,p1.get_grade() 纔是方法調用。
由於方法也是一個屬性,因此,它也能夠動態地添加到實例上,只是須要用 types.MethodType() 把一個函數變爲一個方法:

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 給一個實例動態添加方法並不常見,直接在class中定義要更直觀。

3.10 定義類方法

和屬性相似,方法也分實例方法和類方法。
在class中定義的所有是實例方法,實例方法第一個參數 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()

經過標記一個 @classmethod,該方法將綁定到 Person 類上,而非類的實例。類方法的第一個參數將傳入類自己,一般將參數名命名爲 cls,上面的 cls.count 實際上至關於 Person.count。
由於是在類上調用,而非實例上調用,所以類方法沒法得到任何實例變量,只能得到類的引用。

4、類的繼承

4.1 什麼是繼承

什麼是繼承:

  • 新類沒必要從頭編寫
  • 新類從現有的類繼承,就自動擁有了現有類的全部功能
  • 新類只須要編寫現有類缺乏的新功能

繼承的好處:

  • 複用已有代碼
  • 自動擁有了現有類的全部功能
  • 只須要編寫缺乏的新功能
    不要忘記調用super()init方法

4.2 繼承一個類

若是已經定義了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) 去初始化父類,不然,繼承自 Person 的 Student 將沒有 name 和 gender。
函數super(Student, self)將返回當前類繼承的父類,即 Person ,而後調用init()方法,注意self參數已在super()中傳入,在init()中將隱式傳遞,不須要寫出(也不能寫)。

43. 判斷類型

函數isinstance()能夠判斷一個變量的類型,既能夠用在Python內置的數據類型如str、list、dict,也能夠用在咱們自定義的類,它們本質上都是數據類型。
假設有以下的 Person、Student 和 Teacher 的定義及繼承關係以下:

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')

當咱們拿到變量 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的實例也是能夠的。
這說明在一條繼承鏈上,一個實例能夠當作它自己的類型,也能夠當作它父類的類型。

4.4 多態

類具備繼承關係,而且子類類型能夠向上轉型看作父類類型,若是咱們從 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

在一個函數中,若是咱們接收一個變量 x,則不管該 x 是 Person、Student仍是 Teacher,均可以正確打印出結果:

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)

運行結果:

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

這種行爲稱爲多態。也就是說,方法調用將做用在 x 的實際類型上。s 是Student類型,它實際上擁有本身的 whoAmI()方法以及從 Person繼承的 whoAmI方法,但調用 s.whoAmI()老是先查找它自身的定義,若是沒有定義,則順着繼承鏈向上查找,直到在某個父類中找到爲止。
因爲Python是動態語言,因此,傳遞給函數 who_am_i(x)的參數 x 不必定是 Person 或 Person 的子類型。任何數據類型的實例均可以,只要它有一個whoAmI()的方法便可:

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

這是動態語言和靜態語言(例如Java)最大的差異之一。動態語言調用實例方法,不檢查類型,只要方法存在,參數正確,就能夠調用。

4.5 多重繼承

除了從一個父類繼承外,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 同時繼承自 B 和 C,也就是 D 擁有了 A、B、C 的所有功能。多重繼承經過 super()調用init()方法時,A 雖然被繼承了兩次,但init()只調用一次:

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

多重繼承的目的是從兩種繼承樹中分別選擇並繼承出子類,以便組合功能使用。
舉個例子,Python的網絡服務器有TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer,而服務器運行模式有 多進程ForkingMixin 和 多線程ThreadingMixin兩種。
要建立多進程模式的 TCPServer:
class MyTCPServer(TCPServer, ForkingMixin)
pass
要建立多線程模式的 UDPServer:
class MyUDPServer(UDPServer, ThreadingMixin):
pass
若是沒有多重繼承,要實現上述全部可能的組合須要 4x2=8 個子類。

4.6 獲取對象信息

拿到一個變量,除了用 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() 函數獲取變量的類型,它返回一個 Type 對象:

>>> type(123) <type 'int'> >>> s = Student('Bob', 'Male', 88) >>> type(s) <class '__main__.Student'>

其次,能夠用 dir() 函數獲取變量的全部屬性:

>>> dir(123) # 整數也有不少屬性... ['__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()函數的用法。
dir()返回的屬性是字符串列表,若是已知一個屬性名稱,要獲取或者設置對象的屬性,就須要用 getattr() 和 setattr( )函數了:

>>> getattr(s, 'name') # 獲取name屬性 'Bob' >>> setattr(s, 'name', 'Adam') # 設置新的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、定製類

5.1 什麼是特殊方法

  • 特殊方法定義在calss中
  • 不須要直接調用
  • Python的某些函數或操做符會調用對應的特殊方法

5.2 str和__repr__

若是要把一個類的實例變成 str,就須要實現特殊方法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)

如今,在交互式命令行下用 print 試試:

>>> p = Person('Bob', 'male') >>> print p (Person: Bob, male) 可是,若是直接敲變量 p: >>> p <main.Person object at 0x10c941890>

彷佛str() 不會被調用。
由於 Python 定義了str()和repr()兩種方法,str()用於顯示給用戶,而repr()用於顯示給開發人員。
有一個偷懶的定義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__

5.3 cmp

對 int、str 等內置數據類型排序時,Python的 sorted() 按照默認的比較函數 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 類實現了cmp()方法,cmp用實例自身self和傳入的實例 s 進行比較,若是 self 應該排在前面,就返回 -1,若是 s 應該排在前面,就返回1,若是二者至關,返回 0。
Student類實現了按name進行排序:

>>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)] >>> print sorted(L) [(Alice: 77), (Bob: 88), (Tim: 99)]

注意: 若是list不只僅包含 Student 類,則 cmp 可能會報錯:

5.4 len

若是一個類表現得像一個list,要獲取有多少個元素,就得用 len() 函數。
要讓 len() 函數工做正常,類必須提供一個特殊方法len(),它返回元素的個數。
例如,咱們寫一個 Students 類,把名字傳進去:

class Students(object): def __init__(self, *args): self.names = args def __len__(self): return len(self.names)

只要正確實現了len()方法,就能夠用len()函數返回Students實例的「長度」:

>>> ss = Students('Bob', 'Alice', 'Tim') >>> print len(ss) 3

5.5 數學運算

Python 提供的基本數據類型 int、float 能夠作整數和浮點的四則運算以及乘方等運算。
可是,四則運算不侷限於int和float,還能夠是有理數、矩陣等。
要表示有理數,能夠用一個Rational類來表示:

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

p、q 都是整數,表示有理數 p/q。
若是要讓Rational進行+運算,須要正確實現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__

如今能夠試試有理數加法:

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

5.6 類型轉換

Rational類實現了有理數運算,可是,若是要把結果轉爲 int 或 float 怎麼辦?
考察整數和浮點數的轉換:

>>> int(12.34) 12 >>> float(12) 12.0

若是要把 Rational 轉爲 int,應該使用:

r = Rational(12, 5) n = int(r)

要讓int()函數正常工做,只須要實現特殊方法int():

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

結果以下:

>>> print int(Rational(7, 2)) 3 >>> print int(Rational(1, 3)) 0

同理,要讓float()函數正常工做,只須要實現特殊方法float()。

5.7 @property

考察 Student 類:

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

當咱們想要修改一個 Student 的 scroe 屬性時,能夠這麼寫:

s = Student('Bob', 59) s.score = 60

可是也能夠這麼寫:

s.score = 1000

顯然,直接給屬性賦值沒法檢查分數的有效性。
若是利用兩個方法:

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

這樣一來,s.set_score(1000) 就會報錯。
這種使用 get/set 方法來封裝對一個屬性的訪問在許多面向對象編程的語言中都很常見。
可是寫 s.get_score() 和 s.set_score() 沒有直接寫 s.score 來得直接。
有沒有一箭雙鵰的方法?----有。
由於Python支持高階函數,在函數式編程中咱們介紹了裝飾器函數,能夠用裝飾器函數把 get/set 方法「裝飾」成屬性調用:

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裝飾後的副產品。
如今,就能夠像使用屬性同樣設置score了:

>>> s = Student('Bob', 59) >>> s.score = 60 >>> print s.score 60 >>> s.score = 1000 Traceback (most recent call last): ... ValueError: invalid score

說明對 score 賦值實際調用的是 set方法。

5.8 slots

因爲Python是動態語言,任何實例在運行期均可以動態地添加屬性。
若是要限制添加的屬性,例如,Student類只容許添加 name、gender和score 這3個屬性,就能夠利用Python的一個特殊的slots來實現。
顧名思義,slots是指一個類容許的屬性列表:

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

如今,對實例進行操做:

>>> s = Student('Bob', 'male', 59) >>> s.name = 'Tim' # OK >>> s.score = 99 # OK >>> s.grade = 'A' Traceback (most recent call last): ... AttributeError: 'Student' object has no attribute 'grade'

slots的目的是限制當前類所能擁有的屬性,若是不須要添加任意動態的屬性,使用slots也能節省內存。

5.9 call

在Python中,函數實際上是一個對象:

>>> f = abs >>> f.__name__ 'abs' >>> f(-123) 123

因爲 f 能夠被調用,因此,f 被稱爲可調用對象。
全部的函數都是可調用對象。
一個類實例也能夠變成一個可調用對象,只須要實現一個特殊方法call()。
咱們把 Person 類變成一個可調用對象:

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

如今能夠對 Person 實例直接調用:

>>> p = Person('Bob', 'male') >>> p('Tim') My name is Bob... My friend is Tim...

單看 p('Tim') 你沒法肯定 p 是一個函數仍是一個類實例,因此,在Python中,函數也是對象,對象和函數的區別並不顯著。

相關文章
相關標籤/搜索