要點:
函數式編程:注意不是“函數編程”,多了一個“式”
模塊:如何使用模塊
面向對象編程:面向對象的概念、屬性、方法、繼承、多態等
定制類:利用Python的特殊方法定制類
1.函數式編程
函數式編程概要
函數式編程是一種抽象計算的編程模式
函 數:function
函數式:functional,一種編程范式
函數 不等于 函數式!
函數式編程的特點
把計算機視為函數而非指令
純函數式編程:不需要變量,沒有副作用,測試簡單
支持高階函數,代碼簡單
Python支持的函數式編程的特點
不是純函數式編程:允許有變量
支持高階函數:函數也可以作為變量傳入
支持閉包:有了閉包就能返回函數
有限度地支持匿名函數
高階函數
變量可以指向函數
函數名其實就是指向函數的變量
高階函數:能夠接受函數作為參數的函數
變量可以指向函數
函數的參數可以接受變量
一個函數可以接收另一個函數作為參數
能接受函數作參數的函數就是高階函數
把函數作為參數
一個簡單的高階函數:
def add(x,y,f):
return f(x)+f(y)```
如果傳入abs作為參數的值:
add(-5,9,abs)```
根據函數的定義,函數執行的代碼實際上是:
abs(-5)+abs(9)```
由于參數 x, y 和 f 都可以任意傳入,如果 f 傳入其他函數,就可以得到不同的返回值。
>#### 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()函數完成這個計算:
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可以處理這種數據類型。
>#### 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。
例子:
Python內置了求和函數sum(),但沒有求積的函數,請利用recude()來求積:
輸入:[2, 4, 5, 7, 12]
輸出:245712的結果
代碼:
def prod(x, y):
return x*y
print reduce(prod, [2, 4, 5, 7, 12])
打印結果:
3360```
>#### 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'
a='\t\t123\r\n'
a.strip()```
結果:'123'
s.lstrip(rm) 刪除s字符串中開頭處,位于 rm刪除序列的字符
s.rstrip(rm) 刪除s字符串中結尾處,位于 rm刪除序列的字符
利用filter()過濾出1~100中平方根是整數的數
代碼:
import math
def is_sqr(x):
r = int(math.sqrt(x))
return x==r*r
print filter(is_sqr,range(1,101))```
結果:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>#### 自定義排序函數
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'小。
例子:對字符串排序時,有時候忽略大小寫排序更符合習慣。請利用sorted()高階函數,實現忽略大小寫排序的算法。
輸入:['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)```
打印結果:
['about', 'bob', 'Credit', 'Zoo']```
>#### 返回函數
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()返回的函數:
>>> 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
```
對返回的函數進行調用時,才計算出結果:
>>> f()
10```
由于可以返回函數,我們在后續代碼里就可以決定到底要不要調用該函數。
例子:
編寫一個函數calc_prod(lst),它接收一個list,返回一個函數,返回函數可以計算參數的乘積。
代碼:
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()```
結果:
24```
>####閉包
在函數內部定義的函數和外部定義的函數是一樣的,只是他們無法被外部訪問:
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。由于f1、f2、f3并沒有被調用,所以,此時他們并未計算 i*i,當 f1 被調用時:
f1()
9 # 因為f1現在才計算i*i,但現在i的值已經變為3```
因此,返回函數不要引用任何循環變量,或者后續會發生變化的變量。
考察下面的函數** f**:
def f(j):
def g():
return j*j
return g```
它可以正確地返回一個閉包g,g所引用的變量j不是循環變量,因此將正常執行。
在count函數的循環內部,如果借助f函數,就可以避免引用循環變量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()```
結果:
1 4 9```
>#### 匿名函數
高階函數可以接收函數做參數,有些時候,我們不需要顯式地定義函數,直接傳入匿名函數更方便。
在Python中,對匿名函數提供了有限支持。還是以map()函數為例,計算 f(x)=x^2
時,除了定義一個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```
利用匿名函數簡化以下代碼:
def is_not_empty(s):
return s and len(s.strip()) > 0
filter(is_not_empty, ['test', None, '', 'str', ' ', 'END'])```
定義匿名函數時,沒有return關鍵字,且表達式的值就是函數返回值。
print filter(lambda s: s and len(s.strip())>0, ['test', None, '', 'str', ' ', 'END'])```
結果:
['test', 'str', 'END']```
裝飾器decorator
問題:
定義了一個函數
想在運行時動態增加功能
又不想改動函數本身的代碼
可以使用高階函數:
可以接收函數做為參數
可以返回函數
是否接收一個函,對其進行包裝,然后返回一個新函數?
Python內置的@語法就是為了簡化裝飾器調用
@new_fn
def f1(x):
return x^2```
等價于:
def f1(x):
return x^2
f1=new_fn(f1)```
裝飾器的作用
可以極大地簡化代碼,避免每個函數編寫重復性代碼
打印日志:@log
檢測性能:@performance
數據庫事務:@transaction
URL路由:@post('/register')
編寫無參數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工作得很好:
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```
但是,對于參數不是一個的函數,調用將報錯:
def add(x, y):
return x + y
print add(1, 2)```
**結果:**
Traceback (most recent call last):
File "test.py", line 15, in
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 都能正常工作。
編寫一個@performance,它可以打印出函數調用的時間。
計算函數調用的時間可以記錄調用前后的當前時間戳,然后計算兩個時間戳的差。
**代碼:**
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)```
結果:
call factorial() in 0.168969s
3628800```
>#### 編寫帶參數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,所以,把一個閉包拆成普通的函數調用會比較困難。不支持閉包的編程語言要實現同樣的功能就需要更多的代碼。
考察上一節的@performance只能打印秒,請給 @performace 增加一個參數,允許傳入's'或'ms':
@performance('ms')
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))```
要實現帶參數的@performance,就需要實現:
my_func = performance('ms')(my_func)
需要3層嵌套的decorator來實現。代碼:
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可以動態實現函數功能的增加,但是,經過@decorator“改造”后的函數,和原函數相比,除了功能多一點外,有沒有其它不同的地方?
在沒有decorator的情況下,打印函數名:
def f1(x):
pass
print f1.name```
有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'。
偏函數
當一個函數有很多參數時,調用者就需要提供多個參數。如果減少參數個數,就可以簡化調用者的負擔。
比如,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.模塊
>#### 模塊和包的概念
代碼越來越多的時候
* 將所有代碼放入一個py文件:無法維護
* 如果將代碼分拆放入多個py文件,好處:
同一個名字的變量互不影響
引用其他模塊
import math
引用完整模塊
import p1.util```
在文件系統中
包就是文件夾
模塊就是xxx.py
包也有多級
如何區分包和普通目錄
包下面必須有一個init.py
注意每層都必須要有!
導入模塊
要使用一個模塊,我們必須首先導入該模塊。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```
動態導入模塊
如果導入的模塊不存在,Python解釋器會報 ImportError 錯誤:
>>> import something
Traceback (most recent call last):
File "", line 1, in
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 **語句。
利用import ... as ...,還可以動態導入不同名稱的模塊。
Python 2.6/2.7提供了json 模塊,但Python 2.5以及更早版本沒有json模塊,不過可以安裝一個simplejson模塊,這兩個模塊提供的函數簽名和功能都一模一樣。
試寫出導入**json **模塊的代碼,能在Python 2.5/2.6/2.7都正常運行。
代碼:
try:
import json
except ImportError:
import simplejson as json
print json.dumps({'python':2.7})```
結果:
{"python": 2.7}```
使用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__**中,以便舊的代碼能在舊版本中測試新特性。
>#### 安裝第三方模塊
Python安裝了許多有用的模塊,可以安裝第三方模塊
Python提供了兩種模塊管理工具:
* easy_install
* pip(推薦,已內置)
### 3.面向對象編程基礎
>#### 面向對象編程
什么是面向對象編程?
* 面向對象編程是一種程序設計范式
* 把程序看成不同對象的相互調用
* 對現實世界建立對象模型
面向對象編程的基本思想?
* 類和實例
* 類用于定義抽象類型
* 實例根據類的定義被創建出來
* 數據封裝
class Person:
def init(self,name):
self.name=name```
p1=Person('Xiao Ming')
P2=Person('Xiao Jun')```
>#### 定義類并創建實例
Python中,類通過 **class **關鍵字定義。以 **Person** 為例,定義一個**Person類**如下:
class Person(object):
pass```
按照 Python 的編程習慣,類名以大寫字母開頭,緊接著是(object),表示該類是從哪個類繼承下來的。
有了Person類的定義,就可以創建出具體的xiaoming、xiaohong等實例。創建實例使用 類名+(),類似函數調用的形式創建
創建實例屬性
雖然可以通過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```
創建包含兩個 **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```
結果:
Adam
Bart
Lisa```
>#### 初始化實例屬性
雖然我們可以自由地給一個實例綁定各種屬性,但是,現實世界中,一種類型的實例應該擁有相同名字的屬性。例如,**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'```
定義Person類的init方法,除了接受 **name、gender **和 **birth **外,還可接受任意關鍵字參數,并把他們都作為屬性賦值給實例。
**kw表示可以任意輸入參數和對應的值,這些鍵值對被組織成字典dict,for k,v in kw.iteritems()表示對該字典的鍵值對進行迭代(k獲得鍵,v獲得值), iteritems()這個方法可以一個一個輸出字典中的鍵值對,setattr(self,k,v)表示對實例自身設置屬性和對應的值,self表示實例自身,k表示參數,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```
結果:
Xiao Ming
Student```
訪問限制
我們可以給一個實例綁定很多屬性,如果有些屬性不希望被外部訪問到怎么辦?
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 "", line 1, in
AttributeError: 'Person' object has no attribute '__job'
可見,只有以雙下劃線開頭的"__job"不能直接被外部訪問。
但是,如果一個屬性以"xxx"的形式定義,那它又可以被外部訪問了,以"xxx"定義的屬性在Python的類中被稱為特殊屬性,有很多預定義的特殊屬性可以使用,通常我們不要把普通屬性用"xxx"定義。
以單下劃線開頭的屬性"_xxx"雖然也可以被外部訪問,但是,按照習慣,他們不應該被外部訪問。
給Person類的init方法中添加name和score參數,并把score綁定到__score屬性上,看看外部是否能訪問到。
創建類屬性
類是模板,而實例則是根據類創建的對象。
綁定在一個實例上的屬性不會影響其他實例,但是,類本身也是一個對象,如果在類上綁定一個屬性,則所有實例都可以訪問類的屬性,并且,所有實例訪問的類屬性都是同一個!也就是說,實例屬性每個實例各自擁有,互相獨立,而類屬性有且只有一份。
定義類屬性可以直接在 **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改變時,所有實例訪問到的類屬性都改變了。
給 **Person 類添加一個類屬性 count,每創建一個實例,count **屬性就加 1,這樣就可以統計出一共創建了多少個 **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```
結果:
1
2
3```
類屬性和實例屬性名字沖突怎么辦
修改類屬性會導致所有實例訪問到的類屬性全部都受影響,但是,如果在實例變量上修改類屬性會發生什么問題呢?
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```
可見,千萬不要在實例上修改類屬性,它實際上并沒有修改類屬性,而是給實例綁定了一個實例屬性。
把上節的 **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```
結果:
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']```
>#### 定義實例方法
一個實例的私有屬性就是以__開頭的屬性,無法被外部訪問,那這些屬性定義有什么用?
雖然私有屬性無法從外部訪問,但是,從類的內部是可以訪問的。除了可以定義實例的屬性外,還可以定義實例的方法。
**實例的方法**就是在類中定義的函數,它的第一個參數永遠是 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
在實例方法內部,可以訪問所有實例屬性,這樣,如果外部需要訪問私有屬性,可以通過方法調用獲得,這種數據封裝的形式除了能保護內部數據一致性外,還可以簡化外部調用的難度。
給 **Person **類增加一個私有屬性 **__score**,表示分數,再增加一個實例方法 **get_grade()**,能根據 **__score **的值分別返回 **A-優秀, B-及格, C-不及格三檔。**
注意**get_grade()**是實例方法,第一個參數為**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()```
結果:
A
B
C```
>#### 方法也是屬性
我們在 **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
=> >
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中定義要更直觀。
定義類方法
和屬性類似,方法也分實例方法和類方法。
在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**。
因為是在類上調用,而非實例上調用,因此類方法無法獲得任何實例變量,只能獲得類的引用。
如果將類屬性 **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()```
結果:
0
1```
### 4.類的繼承和多態
>#### 什么是繼承
如果想寫一個新的類: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
所以:
- 新類不用從頭編寫
- 新類從現有的類繼承,就自動擁有了現有類的所有功能
- 新類只需要編寫現有的類缺少的新功能
繼承的好處:
- 復用已有的代碼
- 自動擁有了現有類的所有功能
- 只需要編寫缺少的新功能
父類和子類:
Person稱為父類、基類,超類
Student稱為子類、派生類、繼承類
繼承的特點:
子類和父類是is關系:
如果一個實例是子類,那么它也是一個父類
Python繼承的特點:
總是從某個類繼承
**不要忘記調用super().__init__**,它是用來初始化父類的,如果忘記,父類的屬性就可能沒有被初始化。
>#### 繼承一個類
如果已經定義了**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()中將隱式傳遞,不需要寫出(也不能寫)。
判斷類型
函數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的實例也是可以的。
這說明在一條繼承鏈上,一個實例可以看成它本身的類型,也可以看成它父類的類型。
>#### 多態
類具有繼承關系,并且子類類型可以向上轉型看做父類類型,如果我們從 **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)最大的差別之一。動態語言調用實例方法,不檢查類型,只要方法存在,參數正確,就可以調用。
>#### 多重繼承
除了從一個父類繼承外,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 個子類。
>#### 獲取對象信息
拿到一個變量,除了用 **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)
>>> s = Student('Bob', 'Male', 88)
>>> type(s)
```
其次,可以用 **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 "", line 1, in
AttributeError: 'Student' object has no attribute 'age'
>>> getattr(s, 'age', 20) # 獲取age屬性,如果屬性不存在,就返回默認值20:
20```
### 5.定制類
>#### 什么是特殊方法
Python如何把任意變量變成str?
因為任何數據類型的實例都有一個特殊方法:
`__str__()`
我們打印list:
print lst.str()
[1,2,3]```
實際是調用的lst的 __str__()方法
Python的特殊方法:
用于print的'str'
用于len的'len'
用于cmp的'cmp'
......
特點:
特殊方法定義在class中
不需要直接調用
Python的某些函數或者操作符會調用對應的特殊方法
正確實現特殊方法:
只需要編寫用到的特殊方法
有關聯的特殊方法都必須實現,如
__getattr__
__setattr__
__delattr__
_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
```
似乎__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```
給Student 類定義str和repr方法,使得能打印出:
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```
結果:
(Student: Bob,male,88)```
_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 可能會報錯:
L = [Student('Tim', 99), Student('Bob', 88), 100, 'Hello']
print sorted(L)```
修改 **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.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)```
結果:
[(Alice: 99), (Tim: 99), (Bob: 88)]```
>#### \__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```
斐波那契數列是由 0, 1, 1, 2, 3, 5, 8...構成。
編寫一個**Fib類**,Fib(10)表示數列的前10個元素,**print Fib(10) **可以打印出數列的前 10 個元素,**len(Fib(10))**可以正確返回數列的個數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)```
結果:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
10```
>#### 數學運算
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```
Rational類雖然可以做加法,但無法做減法、乘方和除法,繼續完善Rational類,實現四則運算。
提示:
減法運算:__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```
結果:
3/4
1/4
1/8
2/1```
類型轉換
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():
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))```
結果:
3.5
0.333333333333```
@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```
這種使用 **get/set **方法來封裝對一個屬性的訪問在許多面向對象編程的語言中都很常見。
但是寫** s.get_score()** 和 **s.set_score() **沒有直接寫 s.score 來得直接。
有沒有兩全其美的方法?----有。
因為Python支持高階函數,在函數式編程中我們介紹了裝飾器函數,可以用裝飾器函數把 **get/set **方法“裝飾”成屬性調用,@property,它是python內置的裝飾器,負責把一個方法變成屬性調用:
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方法。
**注意:get score(self) 不帶參數的返回函數相當于get score(), set score(self, score)傳入了一個score參數相當于 set score(score)
get score(self) 中的self 是get score()作為方法必須要有的。同樣set score()總的self也是。。除去self 不管的話,get score()“相當于”無參函數,返回了score 。而 set score ()需要調用score作為參數。**
_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```
**__slots__**的目的是限制當前類所能擁有的屬性,如果不需要添加任意動態的屬性,使用**`__slots__`**也能節省內存。
假設**Person**類通過**`__slots__`**定義了**name**和**gender**,在派生類**Student**中通過**`__slots__`**繼續添加**score**的定義,使**Student**類可以實現**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```
結果:
99```
>#### \__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中,函數也是對象,對象和函數的區別并不顯著。
改進一下前面定義的斐波那契數列,加一個call方法,讓調用更簡單:
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)```
結果:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]```
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态