Python進階

Python進階框架

希望大家喜歡,點贊哦
首先感謝廖雪峰老師對于該課程的講解

一、函數式編程

1.1 函數式編程簡介

函數式編程:一種編程范式
函數式編程特點:

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

Python支持的函數式編程:

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

1.2 高階函數

  • 變量可以指向函數
  • 函數名其實就是指向函數的變量

高階函數:能接收函數做參數的函數

  • 變量可以指向函數
  • 函數的參數可以接受變量
  • 一個函數可以接受另一個函數作為參數
  • 能接收函數做參數的函數就是高階函數

1.3 把函數作為參數

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

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

如果傳入abs作為參數f的值:

add(-5, 9, abs)

根據函數的定義,函數執(zhí)行的代碼實際上是:

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()函數完成這個計算:

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)
結果將變?yōu)?25,因為第一輪計算是:

計算初始值和第一個元素: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()也是一個高階函數,它可以接收一個比較函數來實現(xiàn)自定義排序,比較函數的定義是,傳入兩個待比較的元素 x, y,如果 x 應該排在 y 的前面,返回 -1,如果 x 應該排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0

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

def reversed_cmp(x, y):
if x > y:
return -1
if x < y:
return 1
return 0
這樣,調用 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'小。

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()就是執(zhí)行g()函數定義的代碼

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

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

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

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

由于可以返回函數,我們在后續(xù)代碼里就可以決定到底要不要調用該函數。

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

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

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

注意: 發(fā)現(xiàn)沒法把 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現(xiàn)在才計算i*i,但現(xiàn)在i的值已經變?yōu)?

因此,返回函數不要引用任何循環(huán)變量,或者后續(xù)會發(fā)生變化的變量。

正確寫法:

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,返回值就是該表達式的結果。
使用匿名函數,可以不必定義函數名,直接創(chuàng)建一個函數對象,很多時候可以簡化代碼:

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

現(xiàn)在,對于任意函數,@log 都能正常工作。

1.13 編寫帶參數decorator

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

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

發(fā)現(xiàn)對于被裝飾的函數,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()
#執(zhí)行結果:
[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)

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

1.14 完善decorator

@decorator可以動態(tài)實現(xiàn)函數功能的增加,但是,經過@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就是幫助我們創(chuàng)建一個偏函數的,不需要我們自己定義int2(),可以直接使用下面的代碼創(chuàng)建一個新的函數int2:

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

所以,functools.partial可以把一個參數多的函數變成一個參數少的新函數,少的參數需要在創(chuàng)建時指定默認值,這樣,新函數調用的難度就降低了。

二、模塊

2.1 模塊和包的概念

在文件系統(tǒng)中:

  • 包就是文件夾
  • 模塊就是xxx.py
  • 包也可以有多級
    區(qū)分包和普通目錄:
    包下面有個init.py文件,每層都必須要有

2.2 導入模塊

要使用一個模塊,我們必須首先導入該模塊。Python使用import語句導入一個模塊。例如,導入系統(tǒng)自帶的模塊 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現(xiàn)在變成了logger

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

2.3 動態(tài)導入模塊

如果導入的模塊不存在,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代碼的運行速度,最簡單的方法是把某些關鍵函數用 C 語言重寫,這樣就能大大提高執(zhí)行速度。
同樣的功能,StringIO 是純Python代碼編寫的,而 cStringIO 部分函數是 C 寫的,因此 cStringIO 運行速度更快。
利用ImportError錯誤,我們經常在Python中動態(tài)導入模塊:

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

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

2.4 使用future

Python的新版本會引入新的功能,但是,實際上這些功能在上一個老版本中就已經存在了。要“試用”某一新的特性,就可以通過導入future模塊的某些功能來實現(xiàn)。
例如,Python 2.7的整數除法運算結果仍是整數:

>>> 10 / 3
3

但是,Python 3.x已經改進了整數的除法運算,“/”除將得到浮點數,“//”除才仍是整數:

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

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

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

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

2.5 安裝第三方模塊

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

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

三、面向對象編程基礎

3.1 面向對象編程

什么是面向對象編程:

  1. 面向對象編程是一種程序設計范式
  2. 把程序看作不同對象的相互調用
  3. 對現(xiàn)實世界建立對象模型

面向對象編程的基本思想:
類和實例:
類用于定義抽象類型
實例根據類的定義被創(chuàng)建出來

面向對象編程:數據封裝

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

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

class Person(object):
    pass

按照 Python 的編程習慣,類名以大寫字母開頭,緊接著是(object),表示該類是從哪個類繼承下來的。類的繼承將在后面的章節(jié)講解,現(xiàn)在我們只需要簡單地從object類繼承。
有了Person類的定義,就可以創(chuàng)建出具體的xiaoming、xiaohong等實例。創(chuàng)建實例使用 類名+(),類似函數調用的形式創(chuàng)建:

xiaoming = Person()
xiaohong = Person()

3.3 創(chuàng)建實例屬性

雖然可以通過Person類創(chuàng)建出xiaoming、xiaohong等實例,但是這些實例看上除了地址不同外,沒有什么其他不同。在現(xiàn)實世界中,區(qū)分xiaoming、xiaohong要依靠他們各自的名字、性別、生日等屬性。
如何讓每個實例擁有各自不同的屬性?由于Python是動態(tài)語言,對每一個實例,都可以直接給他們的屬性賦值,例如,給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 初始化實例屬性

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

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

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

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'

要特別注意的是,初學者定義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)

這會導致創(chuàng)建失敗或運行不正常,因為第一個參數name被Python解釋器傳入了實例的引用,從而導致整個方法的調用參數位置全部沒有對上。

3.5 訪問限制

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

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 創(chuàng)建類屬性

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

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

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

print Person.address
# => Earth

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

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'

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

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

修改類屬性會導致所有實例訪問到的類屬性全部都受影響,但是,如果在實例變量上修改類屬性會發(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

結果如下:

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

我們發(fā)現(xiàn),在設置了 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'。
可見,當實例屬性和類屬性重名時,實例屬性優(yōu)先級高,它將屏蔽掉對類屬性的訪問。
當我們把 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() 才是方法調用。
因為方法也是一個屬性,所以,它也可以動態(tài)地添加到實例上,只是需要用 types.MethodType() 把一個函數變?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中定義要更直觀。

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.1 什么是繼承

什么是繼承:

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

繼承的好處:

  • 復用已有代碼
  • 自動擁有了現(xiàn)有類的所有功能
  • 只需要編寫缺少的新功能
    不要忘記調用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 多態(tài)

類具有繼承關系,并且子類類型可以向上轉型看做父類類型,如果我們從 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

這種行為稱為多態(tài)。也就是說,方法調用將作用在 x 的實際類型上。s 是Student類型,它實際上擁有自己的 whoAmI()方法以及從 Person繼承的 whoAmI方法,但調用 s.whoAmI()總是先查找它自身的定義,如果沒有定義,則順著繼承鏈向上查找,直到在某個父類中找到為止。
由于Python是動態(tài)語言,所以,傳遞給函數 who_am_i(x)的參數 x 不一定是 Person 或 Person 的子類型。任何數據類型的實例都可以,只要它有一個whoAmI()的方法即可:

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

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

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兩種。
要創(chuàng)建多進程模式的 TCPServer:
class MyTCPServer(TCPServer, ForkingMixin)
pass
要創(chuàng)建多線程模式的 UDPServer:
class MyUDPServer(UDPServer, ThreadingMixin):
pass
如果沒有多重繼承,要實現(xiàn)上述所有可能的組合需要 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.1 什么是特殊方法

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

5.2 strrepr

如果要把一個類的實例變成 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() 不會被調用。
因為 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__

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 類實現(xiàn)了cmp()方法,cmp用實例自身self和傳入的實例 s 進行比較,如果 self 應該排在前面,就返回 -1,如果 s 應該排在前面,就返回1,如果兩者相當,返回 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 可能會報錯:

5.4 len

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

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

只要正確實現(xiàn)了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進行+運算,需要正確實現(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)在可以試試有理數加法:

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

5.6 類型轉換

Rational類實現(xiàn)了有理數運算,但是,如果要把結果轉為 int 或 float 怎么辦?
考察整數和浮點數的轉換:

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

如果要把 Rational 轉為 int,應該使用:

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

要讓int()函數正常工作,只需要實現(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

結果如下:

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

同理,要讓float()函數正常工作,只需要實現(xiàn)特殊方法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裝飾后的副產品。
現(xiàn)在,就可以像使用屬性一樣設置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是動態(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

現(xiàn)在,對實例進行操作:

>>> 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的目的是限制當前類所能擁有的屬性,如果不需要添加任意動態(tài)的屬性,使用slots也能節(jié)省內存。

5.9 call

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

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

由于 f 可以被調用,所以,f 被稱為可調用對象。
所有的函數都是可調用對象。
一個類實例也可以變成一個可調用對象,只需要實現(xiàn)一個特殊方法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

現(xiàn)在可以對 Person 實例直接調用:

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

單看 p('Tim') 你無法確定 p 是一個函數還是一個類實例,所以,在Python中,函數也是對象,對象和函數的區(qū)別并不顯著。
希望大家喜歡,點贊哦

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

相關閱讀更多精彩內容

  • 定義類并創(chuàng)建實例 在Python中,類通過 class 關鍵字定義。以 Person 為例,定義一個Person類...
    績重KF閱讀 4,121評論 0 13
  • 教程地址:python進階 - 慕課網 python函數式編程 變量可以指向函數: f = absprint(f)...
    竹口小生閱讀 683評論 0 2
  • 本文為《爬著學Python》系列第十篇文章。 在實際操作中,可能函數是我們幾乎唯一的實現(xiàn)操作的方式,這是因為函數能...
    SyPy閱讀 5,728評論 0 8
  • 要點: 函數式編程:注意不是“函數編程”,多了一個“式” 模塊:如何使用模塊 面向對象編程:面向對象的概念、屬性、...
    victorsungo閱讀 1,705評論 0 6
  • 如果說前世五百年的回眸才能換來今生的擦肩而過,那我們前世應該回眸了好多次才換來今生的相遇吧!你說你永遠不會...
    邂逅O閱讀 1,104評論 0 0

友情鏈接更多精彩內容