一個(gè)很全的在線手冊(cè)中心:
https://docs.pythontab.com/
《Intermediate Python》在線中文手冊(cè):https://docs.pythontab.com/interpy/
1.0 *args 和 **kwargs
用法
*args 和 **kwargs 主要用于函數(shù)定義。 你可以將不定數(shù)量的參數(shù)傳遞給一個(gè)函數(shù)廓握。
其中跨晴,**kwargs 允許你將不定長(zhǎng)度的鍵值對(duì), 作為參數(shù)傳遞給一個(gè)函數(shù)容客。 如果你想要在一個(gè)函數(shù)里處理帶名字的參數(shù), 你應(yīng)該使用**kwargs洁墙。
這里的不定的意思是:預(yù)先并不知道, 函數(shù)使用者會(huì)傳遞多少個(gè)參數(shù)給你, 所以在這個(gè)場(chǎng)景下使用這兩個(gè)關(guān)鍵字曙旭。 *args 是用來(lái)發(fā)送一個(gè)非鍵值對(duì)的可變數(shù)量的參數(shù)列表給一個(gè)函數(shù).
示例
def test_var_args(f_arg, *argv):
print("first normal arg:", f_arg)
for arg in argv:
print("another arg through *argv:", arg)
test_var_args('yasoob', 'python', 'eggs', 'test')
def greet_me(**kwargs):
for key, value in kwargs.items():
print("{0} == {1}".format(key, value))
greet_me(var1="arg1", var2="arg2")
def test_args_kwargs(arg1, arg2, arg3):
print("arg1:", arg1)
print("arg2:", arg2)
print("arg3:", arg3)
args = ("two", 3, 5)
test_args_kwargs(*args)
kargs = {"arg3": 3, "arg2": "two", "arg1": 5}
test_args_kwargs(**kargs)
標(biāo)準(zhǔn)參數(shù)與args荡碾、*kwargs在使用時(shí)的順序
那么如果你想在函數(shù)里同時(shí)使用所有這三種參數(shù)蕊程, 順序是這樣的:
some_func(fargs, *args, **kwargs)
什么時(shí)候用他們
最常見的用例是在寫函數(shù)裝飾器的時(shí)候
此外它也可以用來(lái)做猴子補(bǔ)丁(monkey patching)。猴子補(bǔ)丁的意思是在程序運(yùn)行時(shí)(runtime)修改某些代碼藕施。 打個(gè)比方寇损,你有一個(gè)類,里面有個(gè)叫g(shù)et_info的函數(shù)會(huì)調(diào)用一個(gè)API并返回相應(yīng)的數(shù)據(jù)裳食。如果我們想測(cè)試它矛市,可以把API調(diào)用替換成一些測(cè)試數(shù)據(jù)。例如:
import someclass
def get_info(self, *args):
return "Test data"
someclass.get_info = get_info
猴子補(bǔ)痘寤觥:monkey patch指的是在運(yùn)行時(shí)動(dòng)態(tài)替換,一般是在startup的時(shí)候.
用過gevent就會(huì)知道,會(huì)在最開頭的地方gevent.monkey.patch_all();把標(biāo)準(zhǔn)庫(kù)中的thread/socket等給替換掉.這樣我們?cè)诤竺媸褂胹ocket的時(shí)候可以跟平常一樣使用,無(wú)需修改任何代碼,但是它變成非阻塞的了.
例如浊吏,想把json 替換為ujson(ujson效率更高),不需要把每個(gè)文件的import json 修改為import ujson as json
其實(shí)只需要在進(jìn)程startup的地方monkey patch就行了.是影響整個(gè)進(jìn)程空間的.
同一進(jìn)程空間中一個(gè)module只會(huì)被運(yùn)行一次.
下面是代碼main.py:import json import ujson def monkey_patch_json(): json.__name__ = 'ujson' json.dumps = ujson.dumps json.loads = ujson.loads monkey_patch_json() print 'main.py',json.__name__
2.0 調(diào)試
從命令行運(yùn)行
你可以在命令行使用Python debugger運(yùn)行一個(gè)腳本:
這會(huì)觸發(fā)debugger在腳本第一行指令處停止執(zhí)行救氯。這在腳本很短時(shí)會(huì)很有幫助找田。你可以通過(Pdb)模式接著查看變量信息,并且逐行調(diào)試着憨。
從腳本內(nèi)部運(yùn)行
同時(shí)墩衙,你也可以在腳本內(nèi)部設(shè)置斷點(diǎn),這樣就可以在某些特定點(diǎn)查看變量信息和各種執(zhí)行時(shí)信息了。這里將使用pdb.set_trace()方法來(lái)實(shí)現(xiàn)漆改。
import pdb
def make_bread():
pdb.set_trace()
return "I don't have time"
print(make_bread())
命令列表:
c: 繼續(xù)執(zhí)行
w: 顯示當(dāng)前正在執(zhí)行的代碼行的上下文信息
a: 打印當(dāng)前函數(shù)的參數(shù)列表
s: 執(zhí)行當(dāng)前代碼行心铃,并停在第一個(gè)能停的地方(相當(dāng)于單步進(jìn)入)
n: 繼續(xù)執(zhí)行到當(dāng)前函數(shù)的下一行,或者當(dāng)前行直接返回(單步跳過)
3.0 生成器(Generators)
迭代器:
迭代器是一個(gè)讓程序員可以遍歷一個(gè)容器(特別是列表)的對(duì)象挫剑。然而去扣,一個(gè)迭代器在遍歷并讀取一個(gè)容器的數(shù)據(jù)元素時(shí),并不會(huì)執(zhí)行一個(gè)迭代暮顺。
- 可迭代對(duì)象(Iterable)
Python中任意的對(duì)象厅篓,只要它定義了可以返回一個(gè)迭代器的iter方法,或者定義了可以支持下標(biāo)索引的getitem方法(這些雙下劃線方法會(huì)在其他章節(jié)中全面解釋)捶码,那么它就是一個(gè)可迭代對(duì)象羽氮。簡(jiǎn)單說(shuō),可迭代對(duì)象就是能提供迭代器的任意對(duì)象惫恼。 - 迭代器(Iterator)
任意對(duì)象档押,只要定義了next(Python2) 或者next方法,它就是一個(gè)迭代器 - 迭代(Iteration)
用簡(jiǎn)單的話講祈纯,它就是從某個(gè)地方(比如一個(gè)列表)取出一個(gè)元素的過程令宿。當(dāng)我們使用一個(gè)循環(huán)來(lái)遍歷某個(gè)東西時(shí),這個(gè)過程本身就叫迭代⊥罂現(xiàn)在既然我們有了這些術(shù)語(yǔ)的基本理解粒没,那我們開始理解生成器吧。
生成器(Generators):
生成器也是一種迭代器簇爆,但是你只能對(duì)其迭代一次癞松。這是因?yàn)樗鼈儾]有把所有的值存在內(nèi)存中,而是在運(yùn)行時(shí)生成值入蛆。你通過遍歷來(lái)使用它們响蓉,要么用一個(gè)“for”循環(huán),要么將它們傳遞給任意可以進(jìn)行迭代的函數(shù)和結(jié)構(gòu)哨毁。大多數(shù)時(shí)候生成器是以函數(shù)來(lái)實(shí)現(xiàn)的枫甲。然而,它們并不返回一個(gè)值扼褪,而是yield(暫且譯作“生出”)一個(gè)值想幻。
生成器函數(shù)的例子:
def generator_function():
for i in range(10):
yield i
for item in generator_function():
print(item)
# Output: 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
這個(gè)案例并不是非常實(shí)用(這樣做會(huì)消耗大量資源)。生成器最佳應(yīng)用場(chǎng)景是:你不想同一時(shí)間將所有計(jì)算出來(lái)的大量結(jié)果集分配到內(nèi)存當(dāng)中话浇,特別是結(jié)果集里還包含循環(huán)脏毯。
下面是一個(gè)計(jì)算斐波那契數(shù)列的生成器:
# generator version
def fibon(n):
a = b = 1
for i in range(n):
yield a
a, b = b, a + b
函數(shù)使用方法如下:
for x in fibon(1000000):
print(x)
用這種方式,我們可以不用擔(dān)心它會(huì)使用大量資源凳枝。然而,之前如果我們這樣來(lái)實(shí)現(xiàn)的話:
def fibon(n):
a = b = 1
result = []
for i in range(n):
result.append(a)
a, b = b, a + b
return result
這也許會(huì)在計(jì)算很大的輸入?yún)?shù)時(shí),用盡所有的資源岖瑰。我們已經(jīng)討論過生成器使用一次迭代叛买,但我們并沒有測(cè)試過。在測(cè)試前你需要再知道一個(gè)Python內(nèi)置函數(shù):next()蹋订。它允許我們獲取一個(gè)序列的下一個(gè)元素率挣。那我們來(lái)驗(yàn)證下我們的理解:
def generator_function():
for i in range(3):
yield i
gen = generator_function()
print(next(gen))
# Output: 0
print(next(gen))
# Output: 1
print(next(gen))
# Output: 2
print(next(gen))
# Output: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# StopIteration
結(jié)果驗(yàn)證:
我們可以看到,在yield掉所有的值后露戒,next()觸發(fā)了一個(gè)StopIteration的異常椒功。基本上這個(gè)異常告訴我們智什,所有的值都已經(jīng)被yield完了动漾。你也許會(huì)奇怪,為什么我們?cè)谑褂胒or循環(huán)時(shí)沒有這個(gè)異常呢荠锭?啊哈旱眯,答案很簡(jiǎn)單。for循環(huán)會(huì)自動(dòng)捕捉到這個(gè)異常并停止調(diào)用next()证九。
Python中一些內(nèi)置數(shù)據(jù)類型也支持迭代:
my_string = "Yasoob"
next(my_string)
# Output: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: str object is not an iterator
好吧删豺,這不是我們預(yù)期的。這個(gè)異常說(shuō)那個(gè)str對(duì)象不是一個(gè)迭代器愧怜。對(duì)呀页,就是這樣!它是一個(gè)可迭代對(duì)象拥坛,而不是一個(gè)迭代器蓬蝶。這意味著它支持迭代,但我們不能直接對(duì)其進(jìn)行迭代操作渴逻。那我們?cè)鯓硬拍軐?duì)它實(shí)施迭代呢疾党?是時(shí)候?qū)W習(xí)下另一個(gè)內(nèi)置函數(shù),iter惨奕。它將根據(jù)一個(gè)可迭代對(duì)象返回一個(gè)迭代器對(duì)象雪位。這里是我們?nèi)绾问褂盟?/p>
my_string = "Yasoob"
my_iter = iter(my_string)
next(my_iter)
# Output: 'Y'
4.0 Map,Filter和Reduce
5.0 set(集合)數(shù)據(jù)結(jié)構(gòu)
set(集合)是一個(gè)非常有用的數(shù)據(jù)結(jié)構(gòu)。它與列表(list)的行為類似梨撞,區(qū)別在于set不能包含重復(fù)的值雹洗。
交集
可以對(duì)比兩個(gè)集合的交集(兩個(gè)集合中都有的數(shù)據(jù)),如下:
valid = set(['yellow', 'red', 'blue', 'green', 'black'])
input_set = set(['red', 'brown'])
print(input_set.intersection(valid))
### 輸出: set(['red'])
差集
你可以用差集(difference)找出無(wú)效的數(shù)據(jù)卧波,相當(dāng)于用一個(gè)集合減去另一個(gè)集合的數(shù)據(jù)时肿,例如:
valid = set(['yellow', 'red', 'blue', 'green', 'black'])
input_set = set(['red', 'brown'])
print(input_set.difference(valid))
### 輸出: set(['brown'])
6.0 三元運(yùn)算符
三元運(yùn)算符通常在Python里被稱為條件表達(dá)式,這些表達(dá)式基于真(true)/假(not)的條件判斷港粱,在Python 2.4以上才有了三元操作..
例子:
is_fat = True
state = "fat" if is_fat else "not fat"
或使用元組:
fat = True
fitness = ("skinny", "fat")[fat]
print("Ali is ", fitness)
#輸出: Ali is fa
7.0 裝飾器
裝飾器(Decorators)是Python的一個(gè)重要部分螃成。簡(jiǎn)單地說(shuō):他們是修改其他函數(shù)的功能的函數(shù)旦签。他們有助于讓我們的代碼更簡(jiǎn)短,也更Pythonic(Python范兒)寸宏。
一切皆對(duì)象
首先我們來(lái)理解下Python中的函數(shù)
def hi(name="yasoob"):
return "hi " + name
print(hi())
# output: 'hi yasoob'
# 我們甚至可以將一個(gè)函數(shù)賦值給一個(gè)變量宁炫,比如
greet = hi
# 我們這里沒有在使用小括號(hào),因?yàn)槲覀儾⒉皇窃谡{(diào)用hi函數(shù)
# 而是在將它放在greet變量里頭氮凝。我們嘗試運(yùn)行下這個(gè)
print(greet())
# output: 'hi yasoob'
# 如果我們刪掉舊的hi函數(shù)羔巢,看看會(huì)發(fā)生什么!
del hi
print(hi())
#outputs: NameError
print(greet())
#outputs: 'hi yasoob'
在函數(shù)中定義函數(shù)
剛才那些就是函數(shù)的基本知識(shí)了罩阵。我們來(lái)讓你的知識(shí)更進(jìn)一步竿秆。在Python中我們可以在一個(gè)函數(shù)中定義另一個(gè)函數(shù):
def hi(name="yasoob"):
print("now you are inside the hi() function")
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
print(greet())
print(welcome())
print("now you are back in the hi() function")
hi()
#output:now you are inside the hi() function
# now you are in the greet() function
# now you are in the welcome() function
# now you are back in the hi() function
# 上面展示了無(wú)論何時(shí)你調(diào)用hi(), greet()和welcome()將會(huì)同時(shí)被調(diào)用。
# 然后greet()和welcome()函數(shù)在hi()函數(shù)之外是不能訪問的稿壁,比如:
greet()
#outputs: NameError: name 'greet' is not defined
那現(xiàn)在我們知道了可以在函數(shù)中定義另外的函數(shù)幽钢。也就是說(shuō):我們可以創(chuàng)建嵌套的函數(shù)。現(xiàn)在你需要再多學(xué)一點(diǎn)常摧,就是函數(shù)也能返回函數(shù)搅吁。
從函數(shù)中返回函數(shù)
其實(shí)并不需要在一個(gè)函數(shù)里去執(zhí)行另一個(gè)函數(shù),我們也可以將其作為輸出返回出來(lái):
def hi(name="yasoob"):
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
if name == "yasoob":
return greet
else:
return welcome
a = hi()
print(a)
#outputs: <function greet at 0x7f2143c01500>
#上面清晰地展示了`a`現(xiàn)在指向到hi()函數(shù)中的greet()函數(shù)
#現(xiàn)在試試這個(gè)
print(a())
#outputs: now you are in the greet() function
再次看看這個(gè)代碼落午。在if/else語(yǔ)句中我們返回greet和welcome谎懦,而不是greet()和welcome()。為什么那樣溃斋?這是因?yàn)楫?dāng)你把一對(duì)小括號(hào)放在后面界拦,這個(gè)函數(shù)就會(huì)執(zhí)行;然而如果你不放括號(hào)在它后面梗劫,那它可以被到處傳遞享甸,并且可以賦值給別的變量而不去執(zhí)行它。
你明白了嗎梳侨?讓我再稍微多解釋點(diǎn)細(xì)節(jié)蛉威。
當(dāng)我們寫下a = hi(),hi()會(huì)被執(zhí)行走哺,而由于name參數(shù)默認(rèn)是yasoob蚯嫌,所以函數(shù)greet被返回了。如果我們把語(yǔ)句改為a = hi(name = "ali")丙躏,那么welcome函數(shù)將被返回择示。我們還可以打印出hi()(),這會(huì)輸出now you are in the greet() function晒旅。
將函數(shù)作為參數(shù)傳給另一個(gè)函數(shù)
def hi():
return "hi yasoob!"
def doSomethingBeforeHi(func):
print("I am doing some boring work before executing hi()")
print(func())
doSomethingBeforeHi(hi)
#outputs:I am doing some boring work before executing hi()
# hi yasoob!
現(xiàn)在你已經(jīng)具備所有必需知識(shí)栅盲,來(lái)進(jìn)一步學(xué)習(xí)裝飾器真正是什么了。裝飾器讓你在一個(gè)函數(shù)的前后去執(zhí)行代碼废恋。
你的第一個(gè)裝飾器
在上一個(gè)例子里谈秫,其實(shí)我們已經(jīng)創(chuàng)建了一個(gè)裝飾器扒寄!現(xiàn)在我們修改下上一個(gè)裝飾器,并編寫一個(gè)稍微更有用點(diǎn)的程序:
def a_new_decorator(a_func):
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
def a_function_requiring_decoration():
print("I am the function which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()
a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
我們剛剛應(yīng)用了之前學(xué)習(xí)到的原理拟烫。這正是python中裝飾器做的事情旗们!它們封裝一個(gè)函數(shù),并且用這樣或者那樣的方式來(lái)修改它的行為。現(xiàn)在你也許疑惑垃你,我們?cè)诖a里并沒有使用@符號(hào)辜羊?那只是一個(gè)簡(jiǎn)短的方式來(lái)生成一個(gè)被裝飾的函數(shù)。這里是我們?nèi)绾问褂聾來(lái)運(yùn)行之前的代碼:
@a_new_decorator
def a_function_requiring_decoration():
"""Hey you! Decorate me!"""
print("I am the function which needs some decoration to "
"remove my foul smell")
a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
希望你現(xiàn)在對(duì)Python裝飾器的工作原理有一個(gè)基本的理解掘鄙。如果我們運(yùn)行如下代碼會(huì)存在一個(gè)問題:
print(a_function_requiring_decoration.__name__)
# Output: wrapTheFunction
這并不是我們想要的!Ouput輸出應(yīng)該是“a_function_requiring_decoration”。這里的函數(shù)被warpTheFunction替代了半开。它重寫了我們函數(shù)的名字和注釋文檔(docstring)。幸運(yùn)的是Python提供給我們一個(gè)簡(jiǎn)單的函數(shù)來(lái)解決這個(gè)問題赃份,那就是functools.wraps寂拆。我們修改上一個(gè)例子來(lái)使用functools.wraps:
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
"""Hey yo! Decorate me!"""
print("I am the function which needs some decoration to "
"remove my foul smell")
print(a_function_requiring_decoration.__name__)
# Output: a_function_requiring_decoration
現(xiàn)在好多了。我們接下來(lái)學(xué)習(xí)裝飾器的一些常用場(chǎng)景抓韩。
藍(lán)本規(guī)范:
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated
@decorator_name
def func():
return("Function is running")
can_run = True
print(func())
# Output: Function is running
can_run = False
print(func())
# Output: Function will not run
注意:@wraps接受一個(gè)函數(shù)來(lái)進(jìn)行裝飾纠永,并加入了復(fù)制函數(shù)名稱、注釋文檔谒拴、參數(shù)列表等等的功能尝江。這可以讓我們?cè)谘b飾器里面訪問在裝飾之前的函數(shù)的屬性。
使用場(chǎng)景
- 授權(quán)
裝飾器能有助于檢查某個(gè)人是否被授權(quán)去使用一個(gè)web應(yīng)用的端點(diǎn)(endpoint)英上。它們被大量使用于Flask和Django web框架中炭序。這里是一個(gè)例子來(lái)使用基于裝飾器的授權(quán):
from functools import wraps
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
authenticate()
return f(*args, **kwargs)
return decorated
- 日志
日志是裝飾器運(yùn)用的另一個(gè)亮點(diǎn)。這是個(gè)例子:
from functools import wraps
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logit
def addition_func(x):
"""Do some math."""
return x + x
result = addition_func(4)
# Output: addition_func was called
帶參數(shù)的裝飾器
來(lái)想想這個(gè)問題苍日,難道@wraps不也是個(gè)裝飾器嗎惭聂?但是,它接收一個(gè)參數(shù)相恃,就像任何普通的函數(shù)能做的那樣辜纲。那么,為什么我們不也那樣做呢豆茫?
這是因?yàn)榍惹福?dāng)你使用@my_decorator語(yǔ)法時(shí),你是在應(yīng)用一個(gè)以單個(gè)函數(shù)作為參數(shù)的一個(gè)包裹函數(shù)揩魂。記住幽邓,Python里每個(gè)東西都是一個(gè)對(duì)象,而且這包括函數(shù)火脉!記住了這些牵舵,我們可以編寫一下能返回一個(gè)包裹函數(shù)的函數(shù)柒啤。
在函數(shù)中嵌入裝飾器
我們回到日志的例子,并創(chuàng)建一個(gè)包裹函數(shù)畸颅,能讓我們指定一個(gè)用于輸出的日志文件担巩。
from functools import wraps
def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打開logfile,并寫入內(nèi)容
with open(logfile, 'a') as opened_file:
# 現(xiàn)在將日志打到指定的logfile
opened_file.write(log_string + '\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
@logit()
def myfunc1():
pass
myfunc1()
# Output: myfunc1 was called
# 現(xiàn)在一個(gè)叫做 out.log 的文件出現(xiàn)了没炒,里面的內(nèi)容就是上面的字符串
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
# Output: myfunc2 was called
# 現(xiàn)在一個(gè)叫做 func2.log 的文件出現(xiàn)了涛癌,里面的內(nèi)容就是上面的字符串
裝飾器類
現(xiàn)在我們有了能用于正式環(huán)境的logit裝飾器,但當(dāng)我們的應(yīng)用的某些部分還比較脆弱時(shí)送火,異常也許是需要更緊急關(guān)注的事情拳话。比方說(shuō)有時(shí)你只想打日志到一個(gè)文件。而有時(shí)你想把引起你注意的問題發(fā)送到一個(gè)email种吸,同時(shí)也保留日志弃衍,留個(gè)記錄。這是一個(gè)使用繼承的場(chǎng)景坚俗,但目前為止我們只看到過用來(lái)構(gòu)建裝飾器的函數(shù)镜盯。
幸運(yùn)的是,類也可以用來(lái)構(gòu)建裝飾器猖败。那我們現(xiàn)在以一個(gè)類而不是一個(gè)函數(shù)的方式速缆,來(lái)重新構(gòu)建logit。
from functools import wraps
class logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile
def __call__(self, func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打開logfile并寫入
with open(self.logfile, 'a') as opened_file:
# 現(xiàn)在將日志打到指定的文件
opened_file.write(log_string + '\n')
# 現(xiàn)在恩闻,發(fā)送一個(gè)通知
self.notify()
return func(*args, **kwargs)
return wrapped_function
def notify(self):
# logit只打日志激涤,不做別的
pass
這個(gè)實(shí)現(xiàn)有一個(gè)附加優(yōu)勢(shì),在于比嵌套函數(shù)的方式更加整潔判呕,而且包裹一個(gè)函數(shù)還是使用跟以前一樣的語(yǔ)法:
@logit()
def myfunc1():
pass
現(xiàn)在倦踢,我們給logit創(chuàng)建子類,來(lái)添加email的功能(雖然email這個(gè)話題不會(huì)在這里展開)侠草。
class email_logit(logit):
'''
一個(gè)logit的實(shí)現(xiàn)版本辱挥,可以在函數(shù)調(diào)用時(shí)發(fā)送email給管理員
'''
def __init__(self, email='admin@myproject.com', *args, **kwargs):
self.email = email
super(logit, self).__init__(*args, **kwargs)
def notify(self):
# 發(fā)送一封email到self.email
# 這里就不做實(shí)現(xiàn)了
pass
從現(xiàn)在起,@email_logit將會(huì)和@logit產(chǎn)生同樣的效果边涕,但是在打日志的基礎(chǔ)上晤碘,還會(huì)多發(fā)送一封郵件給管理員。
8.0 slots魔法
在Python中功蜓,每個(gè)類都有實(shí)例屬性园爷。默認(rèn)情況下Python用一個(gè)字典來(lái)保存一個(gè)對(duì)象的實(shí)例屬性。這非常有用式撼,因?yàn)樗试S我們?cè)谶\(yùn)行時(shí)去設(shè)置任意的新屬性童社。
然而,對(duì)于有著已知屬性的小類來(lái)說(shuō)著隆,它可能是個(gè)瓶頸扰楼。這個(gè)字典浪費(fèi)了很多內(nèi)存呀癣。Python不能在對(duì)象創(chuàng)建時(shí)直接分配一個(gè)固定量的內(nèi)存來(lái)保存所有的屬性。因此如果你創(chuàng)建許多對(duì)象(我指的是成千上萬(wàn)個(gè))弦赖,它會(huì)消耗掉很多內(nèi)存项栏。
不過還是有一個(gè)方法來(lái)規(guī)避這個(gè)問題。這個(gè)方法需要使用slots來(lái)告訴Python不要使用字典蹬竖,而且只給一個(gè)固定集合的屬性分配空間沼沈。
這里是一個(gè)使用與不使用slots的例子:
不使用 __slots__:
class MyClass(object):
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
self.set_up()
# ...
使用 __slots__:
class MyClass(object):
__slots__ = ['name', 'identifier']
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
self.set_up()
# ...
第二段代碼會(huì)為你的內(nèi)存減輕負(fù)擔(dān)。通過這個(gè)技巧币厕,有些人已經(jīng)看到內(nèi)存占用率幾乎40%~50%的減少庆冕。
9.0 容器(Collections)
Python附帶一個(gè)模塊,它包含許多容器數(shù)據(jù)類型劈榨,名字叫作collections。我們將討論它的作用和用法晦嵌。
我們將討論的是:
defaultdict
counter
deque
namedtuple
enum.Enum (包含在Python 3.4以上)
defaultdict
與dict類型不同同辣,你不需要檢查key是否存在,所以我們能這樣做:
from collections import defaultdict
colours = (
('Yasoob', 'Yellow'),
('Ali', 'Blue'),
('Arham', 'Green'),
('Ali', 'Black'),
('Yasoob', 'Red'),
('Ahmed', 'Silver'),
)
favourite_colours = defaultdict(list)
for name, colour in colours:
favourite_colours[name].append(colour)
print(favourite_colours)
輸出:
# defaultdict(<type 'list'>,
# {'Arham': ['Green'],
# 'Yasoob': ['Yellow', 'Red'],
# 'Ahmed': ['Silver'],
# 'Ali': ['Blue', 'Black']
# })
另一種重要的是例子就是:當(dāng)你在一個(gè)字典中對(duì)一個(gè)鍵進(jìn)行嵌套賦值時(shí)惭载,如果這個(gè)鍵不存在旱函,會(huì)觸發(fā)keyError異常。 defaultdict允許我們用一個(gè)聰明的方式繞過這個(gè)問題描滔。 首先我分享一個(gè)使用dict觸發(fā)KeyError的例子棒妨,然后提供一個(gè)使用defaultdict的解決方案。
問題:
some_dict = {}
some_dict['colours']['favourite'] = "yellow"
異常輸出:KeyError: 'colours'
解決方案:
import collections
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['colours']['favourite'] = "yellow"
運(yùn)行正常
你可以用json.dumps打印出some_dict含长,例如:
import json
print(json.dumps(some_dict))
## 輸出: {"colours": {"favourite": "yellow"}}
counter
Counter是一個(gè)計(jì)數(shù)器券腔,它可以幫助我們針對(duì)某項(xiàng)數(shù)據(jù)進(jìn)行計(jì)數(shù)。比如它可以用來(lái)計(jì)算每個(gè)人喜歡多少種顏色:
from collections import Counter
colours = (
('Yasoob', 'Yellow'),
('Ali', 'Blue'),
('Arham', 'Green'),
('Ali', 'Black'),
('Yasoob', 'Red'),
('Ahmed', 'Silver'),
)
favs = Counter(name for name, colour in colours)
print(favs)
## 輸出:
## Counter({
## 'Yasoob': 2,
## 'Ali': 2,
## 'Arham': 1,
## 'Ahmed': 1
## })
我們也可以在利用它統(tǒng)計(jì)一個(gè)文件拘泞,例如:
with open('filename', 'rb') as f:
line_count = Counter(f)
print(line_count)
deque
deque提供了一個(gè)雙端隊(duì)列纷纫,你可以從頭/尾兩端添加或刪除元素。要想使用它陪腌,首先我們要從collections中導(dǎo)入deque模塊:
它的用法就像python的list辱魁,并且提供了類似的方法,例如:
from collections import deque
d = deque()
d.append('1')
d.append('2')
d.append('3')
print(len(d))
## 輸出: 3
print(d[0])
## 輸出: '1'
print(d[-1])
## 輸出: '3'
你可以從兩端取出(pop)數(shù)據(jù):
d = deque(range(5))
print(len(d))
## 輸出: 5
d.popleft()
## 輸出: 0
d.pop()
## 輸出: 4
print(d)
## 輸出: deque([1, 2, 3])
對(duì)象自省
自省(introspection)诗鸭,在計(jì)算機(jī)編程領(lǐng)域里染簇,是指在運(yùn)行時(shí)來(lái)判斷一個(gè)對(duì)象的類型的能力。它是Python的強(qiáng)項(xiàng)之一强岸。Python中所有一切都是一個(gè)對(duì)象锻弓,而且我們可以仔細(xì)勘察那些對(duì)象。Python還包含了許多內(nèi)置函數(shù)和模塊來(lái)幫助我們蝌箍。
- dir
返回一個(gè)列表弥咪,列出了一個(gè)對(duì)象所擁有的屬性和方法过蹂。這里是一個(gè)例子:
my_list = [1, 2, 3]
dir(my_list)
# Output: ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
# '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
# '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__',
# '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__',
# '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__',
# '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__',
# '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop',
# 'remove', 'reverse', 'sort']
上面的自省給了我們一個(gè)列表對(duì)象的所有方法的名字。當(dāng)你沒法回憶起一個(gè)方法的名字聚至,這會(huì)非常有幫助酷勺。如果我們運(yùn)行dir()而不傳入?yún)?shù),那么它會(huì)返回當(dāng)前作用域的所有名字扳躬。
- type和id
print(type(''))
# Output: <type 'str'>
print(type([]))
# Output: <type 'list'>
print(type({}))
# Output: <type 'dict'>
print(type(dict))
# Output: <type 'type'>
print(type(3))
# Output: <type 'int'>
id()函數(shù)返回任意不同種類對(duì)象的唯一ID脆诉,舉個(gè)例子:
name = "Yasoob"
print(id(name))
# Output: 139972439030304
- inspect模塊
inspect模塊也提供了許多有用的函數(shù),來(lái)獲取活躍對(duì)象的信息贷币。比方說(shuō)击胜,你可以查看一個(gè)對(duì)象的成員,只需運(yùn)行:
import inspect
print(inspect.getmembers(str))
# Output: [('__add__', <slot wrapper '__add__' of ... ...
10.0 列表推導(dǎo)室
推導(dǎo)式(又稱解析式)是Python的一種獨(dú)有特性役纹,如果我被迫離開了它偶摔,我會(huì)非常想念。推導(dǎo)式是可以從一個(gè)數(shù)據(jù)序列構(gòu)建另一個(gè)新的數(shù)據(jù)序列的結(jié)構(gòu)體促脉。 共有三種推導(dǎo)辰斋,在Python2和3中都有支持:
列表(list)推導(dǎo)式
字典(dict)推導(dǎo)式
集合(set)推導(dǎo)式
- 列表(list)推導(dǎo)式
列表推導(dǎo)式(又稱列表解析式)提供了一種簡(jiǎn)明扼要的方法來(lái)創(chuàng)建列表。
它的結(jié)構(gòu)是在一個(gè)中括號(hào)里包含一個(gè)表達(dá)式瘸味,然后是一個(gè)for語(yǔ)句宫仗,然后是0個(gè)或多個(gè)for或者if語(yǔ)句。那個(gè)表達(dá)式可以是任意的旁仿,意思是你可以在列表中放入任意類型的對(duì)象藕夫。返回結(jié)果將是一個(gè)新的列表,在這個(gè)以if和for語(yǔ)句為上下文的表達(dá)式運(yùn)行完成之后產(chǎn)生枯冈。
規(guī)范
這里是另外一個(gè)簡(jiǎn)明例子:variable = [out_exp for out_exp in input_list if out_exp == 2]
這將對(duì)快速生成列表非常有用毅贮。multiples = [i for i in range(30) if i % 3 is 0] print(multiples) # Output: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
有些人甚至更喜歡使用它而不是filter函數(shù)。
列表推導(dǎo)式在有些情況下超贊尘奏,特別是當(dāng)你需要使用for循環(huán)來(lái)生成一個(gè)新列表嫩码。舉個(gè)例 子,你通常會(huì)這樣做:
你可以使用列表推導(dǎo)式來(lái)簡(jiǎn)化它罪既,就像這樣:squared = [] for x in range(10): squared.append(x**2)
squared = [x**2 for x in range(10)]
- 字典推導(dǎo)式(dict comprehensions)
字典推導(dǎo)和列表推導(dǎo)的使用方法是類似的铸题。這里有個(gè)我最近發(fā)現(xiàn)的例子:
在上面的例子中我們把同一個(gè)字母但不同大小寫的值合并起來(lái)了。mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3} mcase_frequency = { k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase.keys() } # mcase_frequency == {'a': 17, 'z': 3, 'b': 34}
就我個(gè)人來(lái)說(shuō)沒有大量使用字典推導(dǎo)式琢感。你還可以快速對(duì)換一個(gè)字典的鍵和值:{v: k for k, v in some_dict.items()}
- 集合推導(dǎo)式(set comprehensions)
它們跟列表推導(dǎo)式也是類似的丢间。 唯一的區(qū)別在于它們使用大括號(hào){}。 舉個(gè)例子:
squared = {x**2 for x in [1, 1, 2]} print(squared) # Output: {1, 4}
- 集合推導(dǎo)式(set comprehensions)
11.0 異常
異常處理是一種藝術(shù)驹针,一旦你掌握烘挫,會(huì)授予你無(wú)窮的力量。我將要向你展示我們能處理 異常的一些方式。
最基本的術(shù)語(yǔ)里我們知道了try/except從句饮六∑渎ⅲ可能觸發(fā)異常產(chǎn)生的代碼會(huì)放到try語(yǔ)句塊里,而處理異常的代碼會(huì)在except語(yǔ)句塊里實(shí)現(xiàn)卤橄。這是一個(gè)簡(jiǎn)單的例子:
try:
file = open('test.txt', 'rb')
except IOError as e:
print('An IOError occurred. {}'.format(e.args[-1]))
上面的例子里绿满,我們僅僅在處理一個(gè)IOError的異常。大部分初學(xué)者還不知道的是窟扑,我們可以處理多個(gè)異常喇颁。
- 處理多個(gè)異常
我們可以使用三種方法來(lái)處理多個(gè)異常。
第一種方法需要把所有可能發(fā)生的異常放到一個(gè)元組里嚎货。像這樣:
try:
file = open('test.txt', 'rb')
except (IOError, EOFError) as e:
print("An error occurred. {}".format(e.args[-1]))
另外一種方式是對(duì)每個(gè)單獨(dú)的異常在單獨(dú)的except語(yǔ)句塊中處理橘霎。我們想要多少個(gè)except語(yǔ)句塊都可以。這里是個(gè)例子:
try:
file = open('test.txt', 'rb')
except EOFError as e:
print("An EOF error occurred.")
raise e
except IOError as e:
print("An error occurred.")
raise e
現(xiàn)在殖属,最后一種方式會(huì)捕獲所有異常:
try:
file = open('test.txt', 'rb')
except Exception:
# 打印一些異常日志姐叁,如果你想要的話
raise
- finally從句
我們把我們的主程序代碼包裹進(jìn)了try從句。然后我們把一些代碼包裹進(jìn)一個(gè)except從句洗显,它會(huì)在try從句中的代碼觸發(fā)異常時(shí)執(zhí)行外潜。
在下面的例子中,我們還會(huì)使用第三個(gè)從句墙懂,那就是finally從句。包裹到finally從句中的代碼不管異常是否觸發(fā)都將會(huì)被執(zhí)行扮念。這可以被用來(lái)在腳本執(zhí)行之后做清理工作损搬。這里是個(gè)簡(jiǎn)單的例子:try: file = open('test.txt', 'rb') except IOError as e: print('An IOError occurred. {}'.format(e.args[-1])) finally: print("This would be printed whether or not an exception occurred!") # Output: An IOError occurred. No such file or directory # This would be printed whether or not an exception occurred!
- try/else從句
try: print('I am sure no exception is going to occur!') except Exception: print('exception') else: # 這里的代碼只會(huì)在try語(yǔ)句里沒有觸發(fā)異常時(shí)運(yùn)行, # 但是這里的異常將 *不會(huì)* 被捕獲 print('This would only run if no exception occurs. And an error here ' 'would NOT be caught.') finally: print('This would be printed in every case.') # Output: I am sure no exception is going to occur! # This would only run if no exception occurs. # This would be printed in every case.
12.0 lambda表達(dá)式
lambda表達(dá)式是一行函數(shù)。
它們?cè)谄渌Z(yǔ)言中也被稱為匿名函數(shù)柜与。如果你不想在程序中對(duì)一個(gè)函數(shù)使用兩次巧勤,你也許會(huì)想用lambda表達(dá)式,它們和普通的函數(shù)完全一樣弄匕。
原型:
lambda 參數(shù):操作(參數(shù))
例子:
add = lambda x, y: x + y
print(add(3, 5))
# Output: 8
這還有一些lambda表達(dá)式的應(yīng)用案例颅悉,可以在一些特殊情況下使用:
列表排序:
a = [(1, 2), (4, 1), (9, 10), (13, -3)]
a.sort(key=lambda x: x[1])
print(a)
# Output: [(13, -3), (4, 1), (1, 2), (9, 10)]
列表并行排序:
data = zip(list1, list2)
data.sort()
list1, list2 = map(lambda t: list(t), zip(*data))
13.0 一行式
- 一行式
本章節(jié),我將向大家展示一些一行式的Python命令,這些程序?qū)?duì)你非常有幫助迁匠。
簡(jiǎn)易Web Server
你是否想過通過網(wǎng)絡(luò)快速共享文件剩瓶?好消息,Python為你提供了這樣的功能城丧。進(jìn)入到你要共享文件的目錄下并在命令行中運(yùn)行下面的代碼:
# Python 2
python -m SimpleHTTPServer
# Python 3
python -m http.server
漂亮的打印
你可以在Python REPL漂亮的打印出列表和字典延曙。這里是相關(guān)的代碼:
from pprint import pprint
my_dict = {'name': 'Yasoob', 'age': 'undefined', 'personality': 'awesome'}
pprint(my_dict)
這種方法在字典上更為有效。此外亡哄,如果你想快速漂亮的從文件打印出json數(shù)據(jù)枝缔,那么你可以這么做:
cat file.json | python -m json.tool
腳本性能分析 這可能在定位你的腳本中的性能瓶頸時(shí),會(huì)非常奏效:
python -m cProfile my_script.py
備注:cProfile是一個(gè)比profile更快的實(shí)現(xiàn)蚊惯,因?yàn)樗怯胏寫的
CSV轉(zhuǎn)換為json
在命令行執(zhí)行這條指令
python -c "import csv,json;print json.dumps(list(csv.reader(open('csv_file.csv'))))"
確保更換csv_file.csv為你想要轉(zhuǎn)換的csv文件
列表輾平
您可以通過使用itertools包中的itertools.chain.from_iterable輕松快速的輾平一個(gè)列表愿卸。下面是一個(gè)簡(jiǎn)單的例子:
a_list = [[1, 2], [3, 4], [5, 6]]
print(list(itertools.chain.from_iterable(a_list)))
# Output: [1, 2, 3, 4, 5, 6]
# or
print(list(itertools.chain(*a_list)))
# Output: [1, 2, 3, 4, 5, 6]
一行式的構(gòu)造器
避免類初始化時(shí)大量重復(fù)的賦值語(yǔ)句
class A(object):
def __init__(self, a, b, c, d, e, f):
self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})
14.0 For-Else
for循環(huán)還有一個(gè)else從句灵临,我們大多數(shù)人并不熟悉。這個(gè)else從句會(huì)在循環(huán)正常結(jié)束時(shí)執(zhí)行趴荸。這意味著儒溉,循環(huán)沒有遇到任何break. 一旦你掌握了何時(shí)何地使用它,它真的會(huì)非常有用赊舶。我自己對(duì)它真是相見恨晚睁搭。
有個(gè)常見的構(gòu)造是跑一個(gè)循環(huán),并查找一個(gè)元素笼平。如果這個(gè)元素被找到了园骆,我們使用break來(lái)中斷這個(gè)循環(huán)。有兩個(gè)場(chǎng)景會(huì)讓循環(huán)停下來(lái)寓调。 - 第一個(gè)是當(dāng)一個(gè)元素被找到锌唾,break被觸發(fā)。 - 第二個(gè)場(chǎng)景是循環(huán)結(jié)束夺英。
現(xiàn)在我們也許想知道其中哪一個(gè)晌涕,才是導(dǎo)致循環(huán)完成的原因。一個(gè)方法是先設(shè)置一個(gè)標(biāo)記痛悯,然后在循環(huán)結(jié)束時(shí)打上標(biāo)記余黎。另一個(gè)是使用else從句。
這就是for/else循環(huán)的基本結(jié)構(gòu):
for item in container:
if search_something(item):
# Found it!
process(item)
break
else:
# Didn't find anything..
not_found_in_container()
14.0 使用C擴(kuò)展
CPython還為開發(fā)者實(shí)現(xiàn)了一個(gè)有趣的特性载萌,使用Python可以輕松調(diào)用C代碼
開發(fā)者有三種方法可以在自己的Python代碼中來(lái)調(diào)用C編寫的函數(shù)-ctypes惧财,SWIG,Python/C API扭仁。每種方式也都有各自的利弊垮衷。
首先,我們要明確為什么要在Python中調(diào)用C乖坠?
常見原因如下: - 你要提升代碼的運(yùn)行速度搀突,而且你知道C要比Python快50倍以上 - C語(yǔ)言中有很多傳統(tǒng)類庫(kù),而且有些正是你想要的熊泵,但你又不想用Python去重寫它們 - 想對(duì)從內(nèi)存到文件接口這樣的底層資源進(jìn)行訪問 - 不需要理由仰迁,就是想這樣做
14.1 CTypes
Python中的ctypes模塊可能是Python調(diào)用C方法中最簡(jiǎn)單的一種。ctypes模塊提供了和C語(yǔ)言兼容的數(shù)據(jù)類型和函數(shù)來(lái)加載dll文件顽分,因此在調(diào)用時(shí)不需對(duì)源文件做任何的修改轩勘。也正是如此奠定了這種方法的簡(jiǎn)單性。
示例如下
實(shí)現(xiàn)兩數(shù)求和的C代碼怯邪,保存為add.c
//sample C file to add 2 numbers - int and floats
#include <stdio.h>
int add_int(int, int);
float add_float(float, float);
int add_int(int num1, int num2){
return num1 + num2;
}
float add_float(float num1, float num2){
return num1 + num2;
}
接下來(lái)將C文件編譯為.so文件(windows下為DLL)绊寻。下面操作會(huì)生成adder.so文件
#For Linux
$ gcc -shared -Wl,-soname,adder -o adder.so -fPIC add.c
#For Mac
$ gcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC add.c
現(xiàn)在在你的Python代碼中來(lái)調(diào)用它
from ctypes import *
#load the shared object file
adder = CDLL('./adder.so')
#Find sum of integers
res_int = adder.add_int(4,5)
print "Sum of 4 and 5 = " + str(res_int)
#Find sum of floats
a = c_float(5.5)
b = c_float(4.1)
add_float = adder.add_float
add_float.restype = c_float
print "Sum of 5.5 and 4.1 = ", str(add_float(a, b))
輸出如下:
Sum of 4 and 5 = 9
Sum of 5.5 and 4.1 = 9.60000038147
在這個(gè)例子中,C文件是自解釋的,它包含兩個(gè)函數(shù)澄步,分別實(shí)現(xiàn)了整形求和和浮點(diǎn)型求和冰蘑。
在Python文件中,一開始先導(dǎo)入ctypes模塊村缸,然后使用CDLL函數(shù)來(lái)加載我們創(chuàng)建的庫(kù)文件祠肥。這樣我們就可以通過變量adder來(lái)使用C類庫(kù)中的函數(shù)了。當(dāng)adder.add_int()被調(diào)用時(shí)梯皿,內(nèi)部將發(fā)起一個(gè)對(duì)C函數(shù)add_int的調(diào)用仇箱。ctypes接口允許我們?cè)谡{(diào)用C函數(shù)時(shí)使用原生Python中默認(rèn)的字符串型和整型。
而對(duì)于其他類似布爾型和浮點(diǎn)型這樣的類型东羹,必須要使用正確的ctype類型才可以剂桥。如向adder.add_float()函數(shù)傳參時(shí), 我們要先將Python中的十進(jìn)制值轉(zhuǎn)化為c_float類型,然后才能傳送給C函數(shù)属提。這種方法雖然簡(jiǎn)單权逗,清晰,但是卻很受限冤议。例如斟薇,并不能在C中對(duì)對(duì)象進(jìn)行操作。
14.2 SWIG
SWIG是Simplified Wrapper and Interface Generator的縮寫恕酸。是Python中調(diào)用C代碼的另一種方法堪滨。在這個(gè)方法中,開發(fā)人員必須編寫一個(gè)額外的接口文件來(lái)作為SWIG(終端工具)的入口蕊温。
Python開發(fā)者一般不會(huì)采用這種方法袱箱,因?yàn)榇蠖鄶?shù)情況它會(huì)帶來(lái)不必要的復(fù)雜。而當(dāng)你有一個(gè)C/C++代碼庫(kù)需要被多種語(yǔ)言調(diào)用時(shí)寿弱,這將是個(gè)非常不錯(cuò)的選擇犯眠。
示例如下(來(lái)自SWIG官網(wǎng)):
example.c文件中的C代碼包含了不同的變量和函數(shù):
#include <time.h>
double My_variable = 3.0;
int fact(int n) {
if (n <= 1) return 1;
else return n*fact(n-1);
}
int my_mod(int x, int y) {
return (x%y);
}
char *get_time()
{
time_t ltime;
time(<ime);
return ctime(<ime);
}
編譯它:
unix % swig -python example.i
unix % gcc -c example.c example_wrap.c \
-I/usr/local/include/python2.1
unix % ld -shared example.o example_wrap.o -o _example.so
最后按灶,Python的輸出:
>>> import example
>>> example.fact(5)
120
>>> example.my_mod(7,3)
1
>>> example.get_time()
'Sun Feb 11 23:01:07 1996'
>>>
我們可以看到症革,使用SWIG確實(shí)達(dá)到了同樣的效果,雖然下了更多的工夫鸯旁,但如果你的目標(biāo)是多語(yǔ)言還是很值得的噪矛。
14.3 Python/C API
Python/C API可能是被最廣泛使用的方法。它不僅簡(jiǎn)單铺罢,而且可以在C代碼中操作你的Python對(duì)象艇挨。
這種方法需要以特定的方式來(lái)編寫C代碼以供Python去調(diào)用它。所有的Python對(duì)象都被表示為一種叫做PyObject的結(jié)構(gòu)體韭赘,并且Python.h
頭文件中提供了各種操作它的函數(shù)缩滨。例如,如果PyObject表示為PyListType(列表類型)時(shí),那么我們便可以使用PyList_Size()
函數(shù)來(lái)獲取該結(jié)構(gòu)的長(zhǎng)度脉漏,類似Python中的len(list)
函數(shù)苞冯。大部分對(duì)Python原生對(duì)象的基礎(chǔ)函數(shù)和操作在Python.h
頭文件中都能找到。
示例
編寫一個(gè)C擴(kuò)展侧巨,添加所有元素到一個(gè)Python列表(所有元素都是數(shù)字)
來(lái)看一下我們要實(shí)現(xiàn)的效果舅锄,這里演示了用Python調(diào)用C擴(kuò)展的代碼
#Though it looks like an ordinary python import, the addList module is implemented in C
import addList
l = [1,2,3,4,5]
print "Sum of List - " + str(l) + " = " + str(addList.add(l))
上面的代碼和普通的Python文件并沒有什么分別,導(dǎo)入并使用了另一個(gè)叫做addList的Python模塊司忱。唯一差別就是這個(gè)模塊并不是用Python編寫的皇忿,而是C。
接下來(lái)我們看看如何用C編寫addList模塊坦仍,這可能看起來(lái)有點(diǎn)讓人難以接受鳍烁,但是一旦你了解了這之中的各種組成,你就可以一往無(wú)前了桨踪。
//Python.h has all the required function definitions to manipulate the Python objects
#include <Python.h>
//This is the function that is called from your python code
static PyObject* addList_add(PyObject* self, PyObject* args){
PyObject * listObj;
//The input arguments come as a tuple, we parse the args to get the various variables
//In this case it's only one list variable, which will now be referenced by listObj
if (! PyArg_ParseTuple( args, "O", &listObj ))
return NULL;
//length of the list
long length = PyList_Size(listObj);
//iterate over all the elements
int i, sum =0;
for (i = 0; i < length; i++) {
//get an element out of the list - the element is also a python objects
PyObject* temp = PyList_GetItem(listObj, i);
//we know that object represents an integer - so convert it into C long
long elem = PyInt_AsLong(temp);
sum += elem;
}
//value returned back to python code - another python object
//build value here converts the C long to a python integer
return Py_BuildValue("i", sum);
}
//This is the docstring that corresponds to our 'add' function.
static char addList_docs[] =
"add( ): add all elements of the list\n";
/* This table contains the relavent info mapping -
<function-name in python module>, <actual-function>,
<type-of-args the function expects>, <docstring associated with the function>
*/
static PyMethodDef addList_funcs[] = {
{"add", (PyCFunction)addList_add, METH_VARARGS, addList_docs},
{NULL, NULL, 0, NULL}
};
/*
addList is the module name, and this is the initialization block of the module.
<desired module name>, <the-info-table>, <module's-docstring>
*/
PyMODINIT_FUNC initaddList(void){
Py_InitModule3("addList", addList_funcs,
"Add all ze lists");
}
逐步解釋 - Python.h頭文件中包含了所有需要的類型(Python對(duì)象類型的表示)和函數(shù)定義(對(duì)Python對(duì)象的操作) - 接下來(lái)我們編寫將要在Python調(diào)用的函數(shù), 函數(shù)傳統(tǒng)的命名方式由{模塊名}_{函數(shù)名}組成老翘,所以我們將其命名為addList_add
- 然后填寫想在模塊內(nèi)實(shí)現(xiàn)函數(shù)的相關(guān)信息表,每行一個(gè)函數(shù)锻离,以空行作為結(jié)束 - 最后的模塊初始化塊簽名為PyMODINIT_FUNC init{模塊名}铺峭。
函數(shù)addList_add接受的參數(shù)類型為PyObject類型結(jié)構(gòu)(同時(shí)也表示為元組類型,因?yàn)镻ython中萬(wàn)物皆為對(duì)象汽纠,所以我們先用PyObject來(lái)定義)卫键。傳入的參數(shù)則通過PyArg_ParseTuple()來(lái)解析。第一個(gè)參數(shù)是被解析的參數(shù)變量虱朵。第二個(gè)參數(shù)是一個(gè)字符串莉炉,告訴我們?nèi)绾稳ソ馕鲈M中每一個(gè)元素。字符串的第n個(gè)字母正是代表著元組中第n個(gè)參數(shù)的類型碴犬。例如絮宁,"i"代表整形,"s"代表字符串類型, "O"則代表一個(gè)Python對(duì)象服协。接下來(lái)的參數(shù)都是你想要通過PyArg_ParseTuple()函數(shù)解析并保存的元素绍昂。這樣參數(shù)的數(shù)量和模塊中函數(shù)期待得到的參數(shù)數(shù)量就可以保持一致,并保證了位置的完整性偿荷。例如窘游,我們想傳入一個(gè)字符串,一個(gè)整數(shù)和一個(gè)Python列表跳纳,可以這樣去寫
int n;
char *s;
PyObject* list;
PyArg_ParseTuple(args, "siO", &n, &s, &list);
在這種情況下忍饰,我們只需要提取一個(gè)列表對(duì)象,并將它存儲(chǔ)在listObj變量中寺庄。然后用列表對(duì)象中的PyList_Size()函數(shù)來(lái)獲取它的長(zhǎng)度艾蓝。就像Python中調(diào)用len(list)力崇。
現(xiàn)在我們通過循環(huán)列表,使用PyList_GetItem(list, index)函數(shù)來(lái)獲取每個(gè)元素赢织。這將返回一個(gè)PyObject*對(duì)象餐曹。既然Python對(duì)象也能表示PyIntType,我們只要使用PyInt_AsLong(PyObj *)函數(shù)便可獲得我們所需要的值敌厘。我們對(duì)每個(gè)元素都這樣處理台猴,最后再得到它們的總和。
總和將被轉(zhuǎn)化為一個(gè)Python對(duì)象并通過Py_BuildValue()返回給Python代碼俱两,這里的i表示我們要返回一個(gè)Python整形對(duì)象饱狂。
現(xiàn)在我們已經(jīng)編寫完C模塊了。將下列代碼保存為setup.py
#build the modules
from distutils.core import setup, Extension
setup(name='addList', version='1.0', \
ext_modules=[Extension('addList', ['adder.c'])])
并且運(yùn)行
python setup.py install
現(xiàn)在應(yīng)該已經(jīng)將我們的C文件編譯安裝到我們的Python模塊中了宪彩。
在一番辛苦后休讳,讓我們來(lái)驗(yàn)證下我們的模塊是否有效
#module that talks to the C code
import addList
l = [1,2,3,4,5]
print "Sum of List - " + str(l) + " = " + str(addList.add(l))
輸出結(jié)果如下
Sum of List - [1, 2, 3, 4, 5] = 15
如你所見,我們已經(jīng)使用Python.h API成功開發(fā)出了我們第一個(gè)Python C擴(kuò)展尿孔。這種方法看似復(fù)雜俊柔,但你一旦習(xí)慣,它將變的非常有效活合。
Python調(diào)用C代碼的另一種方式便是使用Cython讓Python編譯的更快雏婶。但是Cython和傳統(tǒng)的Python比起來(lái)可以將它理解為另一種語(yǔ)言,所以我們就不在這里過多描述了白指。
15 open函數(shù)
open 函數(shù)可以打開一個(gè)文件留晚。超級(jí)簡(jiǎn)單吧?大多數(shù)時(shí)候告嘲,我們看到它這樣被使用:
f = open('photo.jpg', 'r+')
jpgdata = f.read()
f.close()
有三個(gè)錯(cuò)誤存在于上面的代碼中错维。
open的返回值是一個(gè)文件句柄,從操作系統(tǒng)托付給你的Python程序橄唬。一旦你處理完文件赋焕,你會(huì)想要?dú)w還這個(gè)文件句柄,只有這樣你的程序不會(huì)超出一次能打開的文件句柄的數(shù)量上限仰楚。
顯式地調(diào)用close關(guān)閉了這個(gè)文件句柄隆判,但前提是只有在read成功的情況下。如果有任意異常正好在f = open(...)之后產(chǎn)生缸血,f.close()將不會(huì)被調(diào)用(取決于Python解釋器的做法蜜氨,文件句柄可能還是會(huì)被歸還械筛,但那是另外的話題了)捎泻。為了確保不管異常是否觸發(fā),文件都能關(guān)閉埋哟,我們將其包裹成一個(gè)with語(yǔ)句:
with open('photo.jpg', 'r+') as f:
jpgdata = f.read()
open的第一個(gè)參數(shù)是文件名笆豁。第二個(gè)(mode 打開模式)決定了這個(gè)文件如何被打開郎汪。
如果你想讀取文件,傳入r
如果你想讀取并寫入文件闯狱,傳入r+
如果你想覆蓋寫入文件煞赢,傳入w
如果你想在文件末尾附加內(nèi)容,傳入a
jpg圖像文件一般不是人寫的(而且其實(shí)不是人直接可讀的)哄孤,因此你應(yīng)該以二進(jìn)制模式來(lái)打開它們照筑,方法是在mode字符串后加一個(gè)b(你可以看看開頭的例子里,正確的方式應(yīng)該是rb)瘦陈。
如果你以文本模式打開一些東西(比如凝危,加一個(gè)t,或者就用r/r+/w/a),你還必須知道要使用哪種編碼晨逝。對(duì)于計(jì)算機(jī)來(lái)說(shuō)蛾默,所有的文件都是字節(jié),而不是字符捉貌。
那你怎么找出正在讀的文件是用哪種編碼寫的呢支鸡?好吧,不幸的是趁窃,并沒有一個(gè)十分簡(jiǎn)單的方式來(lái)檢測(cè)編碼牧挣。在不同的編碼中,同樣的字節(jié)可以表示不同醒陆,但同樣有效的字符浸踩。因此,你必須依賴一個(gè)元數(shù)據(jù)(比如统求,在HTTP頭信息里)來(lái)找出編碼检碗。越來(lái)越多的是,文件格式將編碼定義成UTF-8码邻。
有了這些基礎(chǔ)知識(shí)折剃,我們來(lái)寫一個(gè)程序,讀取一個(gè)文件像屋,檢測(cè)它是否是JPG(提示:這些文件頭部以字節(jié)FF D8開始)怕犁,把對(duì)輸入文件的描述寫入一個(gè)文本文件。
import io
with open('photo.jpg', 'rb') as inf:
jpgdata = inf.read()
if jpgdata.startswith(b'\xff\xd8'):
text = u'This is a JPEG file (%d bytes long)\n'
else:
text = u'This is a random file (%d bytes long)\n'
with io.open('summary.txt', 'w', encoding='utf-8') as outf:
outf.write(text % len(jpgdata))
我敢肯定己莺,現(xiàn)在你會(huì)正確地使用open啦奏甫!
16 協(xié)程
Python中的協(xié)程和生成器很相似但又稍有不同。主要區(qū)別在于: 生成器是數(shù)據(jù)的生產(chǎn)者 協(xié)程則是數(shù)據(jù)的消費(fèi)者
首先我們先來(lái)回顧下生成器的創(chuàng)建過程凌受。我們可以這樣去創(chuàng)建一個(gè)生成器:
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a+b
然后我們經(jīng)常在for循環(huán)中這樣使用它:
for i in fib():
print i
這樣做不僅快而且不會(huì)給內(nèi)存帶來(lái)壓力阵子,因?yàn)槲覀兯枰闹刀际莿?dòng)態(tài)生成的而不是將他們存儲(chǔ)在一個(gè)列表中。更概括的說(shuō)如果現(xiàn)在我們?cè)谏厦娴睦又惺褂?code>yield便可獲得了一個(gè)協(xié)程胜蛉。協(xié)程會(huì)消費(fèi)掉發(fā)送給它的值挠进。Python實(shí)現(xiàn)的grep
就是個(gè)很好的例子:
def grep(pattern):
print("Searching for", pattern)
while True:
line = (yield)
if pattern in line:
print(line)
等等色乾!yield
返回了什么?啊哈领突,我們已經(jīng)把它變成了一個(gè)協(xié)程暖璧。它將不再包含任何初始值,相反要從外部傳值給它君旦。我們可以通過send()
方法向它傳值澎办。這有個(gè)例子:
search = grep('coroutine')
next(search)
#output: Searching for coroutine
search.send("I love you")
search.send("Don't you love me?")
search.send("I love coroutine instead!")
#output: I love coroutine instead!
發(fā)送的值會(huì)被yield
接收。我們?yōu)槭裁匆\(yùn)行next()
方法呢金砍?這樣做正是為了啟動(dòng)一個(gè)協(xié)程浮驳。就像協(xié)程中包含的生成器并不是立刻執(zhí)行,而是通過next()
方法來(lái)響應(yīng)send()
方法捞魁。因此至会,你必須通過next()
方法來(lái)執(zhí)行yield
表達(dá)式。
我們可以通過調(diào)用close()
方法來(lái)關(guān)閉一個(gè)協(xié)程谱俭。像這樣:
search = grep('coroutine')
search.close()
更多協(xié)程相關(guān)知識(shí)的學(xué)習(xí)大家可以參考David Beazley的這份精彩演講奉件。
17 函數(shù)緩存 (Function caching)
函數(shù)緩存允許我們將一個(gè)函數(shù)對(duì)于給定參數(shù)的返回值緩存起來(lái)。
當(dāng)一個(gè)I/O密集的函數(shù)被頻繁使用相同的參數(shù)調(diào)用的時(shí)候昆著,函數(shù)緩存可以節(jié)約時(shí)間县貌。
在Python 3.2版本以前我們只有寫一個(gè)自定義的實(shí)現(xiàn)。在Python 3.2以后版本凑懂,有個(gè)lru_cache的裝飾器煤痕,允許我們將一個(gè)函數(shù)的返回值快速地緩存或取消緩存。
我們來(lái)實(shí)現(xiàn)一個(gè)斐波那契計(jì)算器接谨,并使用lru_cache摆碉。
from functools import lru_cache
@lru_cache(maxsize=32)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
>>> print([fib(n) for n in range(10)])
# Output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
那個(gè)maxsize參數(shù)是告訴lru_cache,最多緩存最近多少個(gè)返回值脓豪。
我們也可以輕松地對(duì)返回值清空緩存巷帝,通過這樣:
fib.cache_clear()
18 上下文管理器(Context managers)
上下文管理器允許你在有需要的時(shí)候,精確地分配和釋放資源扫夜。
使用上下文管理器最廣泛的案例就是with語(yǔ)句了楞泼。
想象下你有兩個(gè)需要結(jié)對(duì)執(zhí)行的相關(guān)操作,然后還要在它們中間放置一段代碼笤闯。
上下文管理器就是專門讓你做這種事情的堕阔。舉個(gè)例子:
with open('some_file', 'w') as opened_file:
opened_file.write('Hola!')
上面這段代碼打開了一個(gè)文件,往里面寫入了一些數(shù)據(jù)颗味,然后關(guān)閉該文件超陆。如果在往文件寫數(shù)據(jù)時(shí)發(fā)生異常,它也會(huì)嘗試去關(guān)閉文件脱衙。上面那段代碼與這一段是等價(jià)的:
file = open('some_file', 'w')
try:
file.write('Hola!')
finally:
file.close()
當(dāng)與第一個(gè)例子對(duì)比時(shí)侥猬,我們可以看到,通過使用with捐韩,許多樣板代碼(boilerplate code)被消掉了退唠。 這就是with語(yǔ)句的主要優(yōu)勢(shì),它確保我們的文件會(huì)被關(guān)閉荤胁,而不用關(guān)注嵌套代碼如何退出瞧预。
上下文管理器的一個(gè)常見用例,是資源的加鎖和解鎖仅政,以及關(guān)閉已打開的文件(就像我已經(jīng)展示給你看的)垢油。
讓我們看看如何來(lái)實(shí)現(xiàn)我們自己的上下文管理器。這會(huì)讓我們更完全地理解在這些場(chǎng)景背后都發(fā)生著什么圆丹。
18.1 基于類的實(shí)現(xiàn)
一個(gè)上下文管理器的類滩愁,最起碼要定義_enter_和_exit_方法。
讓我們來(lái)構(gòu)造我們自己的開啟文件的上下文管理器辫封,并學(xué)習(xí)下基礎(chǔ)知識(shí)硝枉。
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
self.file_obj.close()
通過定義_enter_和_exit_方法显设,我們可以在with語(yǔ)句里使用它悄窃。我們來(lái)試試:
with File('demo.txt', 'w') as opened_file:
opened_file.write('Hola!')
我們的_exit_函數(shù)接受三個(gè)參數(shù)。這些參數(shù)對(duì)于每個(gè)上下文管理器類中的_exit_方法都是必須的瓷式。我們來(lái)談?wù)勗诘讓佣及l(fā)生了什么欣福。
- with語(yǔ)句先暫存了File類的_exit_方法
- 然后它調(diào)用File類的_enter_方法
- _enter_方法打開文件并返回給with語(yǔ)句
- 打開的文件句柄被傳遞給opened_file參數(shù)
- 我們使用.write()來(lái)寫文件
- with語(yǔ)句調(diào)用之前暫存的_exit_方法
- _exit_方法關(guān)閉了文件