日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

讓你的 Python 代碼優(yōu)雅又地道

系統(tǒng) 1692 0

譯序

如果說(shuō)優(yōu)雅也有缺點(diǎn)的話,那就是你需要艱巨的工作才能得到它,需要良好的教育才能欣賞它。

—— Edsger Wybe Dijkstra


在Python社區(qū)文化的澆灌下,演化出了一種獨(dú)特的代碼風(fēng)格,去指導(dǎo)如何正確地使用Python,這就是常說(shuō)的pythonic。一般說(shuō)地道(idiomatic)的python代碼,就是指這份代碼很pythonic。Python的語(yǔ)法和標(biāo)準(zhǔn)庫(kù)設(shè)計(jì),處處契合著pythonic的思想。而且Python社區(qū)十分注重編碼風(fēng)格一的一致性,他們極力推行和處處實(shí)踐著pythonic。所以經(jīng)常能看到基于某份代碼P vs NP (pythonic vs non-pythonic)的討論。pythonic的代碼簡(jiǎn)練,明確,優(yōu)雅,絕大部分時(shí)候執(zhí)行效率高。閱讀pythonic的代碼能體會(huì)到“代碼是寫給人看的,只是順便讓機(jī)器能運(yùn)行”暢快。


然而什么是pythonic,就像什么是地道的漢語(yǔ)一樣,切實(shí)存在但標(biāo)準(zhǔn)模糊。import this可以看到Tim Peters提出的Python之禪,它提供了指導(dǎo)思想。許多初學(xué)者都看過(guò)它,深深贊同它的理念,但是實(shí)踐起來(lái)又無(wú)從下手。PEP 8給出的不過(guò)是編碼規(guī)范,對(duì)于實(shí)踐pythonic還遠(yuǎn)遠(yuǎn)不夠。如果你正被如何寫出pythonic的代碼而困擾,或許這份筆記能給你幫助。


Raymond Hettinger是Python核心開(kāi)發(fā)者,本文提到的許多特性都是他開(kāi)發(fā)的。同時(shí)他也是Python社區(qū)熱忱的布道師,不遺余力地傳授pythonic之道。這篇文章是網(wǎng)友Jeff Paine整理的他在2013年美國(guó)的PyCon的演講的筆記。


術(shù)語(yǔ)澄清:本文所說(shuō)的集合全都指collection,而不是set。


以下是正文。




本文是Raymond Hettinger在2013年美國(guó)PyCon演講的筆記(視頻, 幻燈片)。


示例代碼和引用的語(yǔ)錄都來(lái)自Raymond的演講。這是我按我的理解整理出來(lái)的,希望你們理解起來(lái)跟我一樣順暢!


遍歷一個(gè)范圍內(nèi)的數(shù)字


for ?i? in ?[0,?1,?2,?3,?4,?5]:

????print?i ** 2


for ?i? in ?range(6):

????print?i ** 2


更好的方法


for ?i? in ?xrange(6):

????print?i ** 2


xrange會(huì)返回一個(gè)迭代器,用來(lái)一次一個(gè)值地遍歷一個(gè)范圍。這種方式會(huì)比range更省內(nèi)存。xrange在Python 3中已經(jīng)改名為range。


遍歷一個(gè)集合


colors?= ['red',?'green',?'blue',?'yellow']


for ?i? in ?range(len(colors)):

????print colors[i]


更好的方法


for ?color in ?colors:

????print color


反向遍歷


colors?= ['red',?'green',?'blue',?'yellow']


for ?i? in ?range(len(colors)-1,?-1,?-1):

????print colors[i]


更好的方法


for ?color in ?reversed(colors):

????print color


遍歷一個(gè)集合及其下標(biāo)


colors?= ['red',?'green',?'blue',?'yellow']


for ?i? in ?range(len(colors)):

????print?i,?'--->',?colors[i]


更好的方法


for ?i,?color in ?enumerate(colors):

????print?i,?'--->',?color


這種寫法效率高,優(yōu)雅,而且?guī)湍闶∪ビH自創(chuàng)建和自增下標(biāo)。


當(dāng)你發(fā)現(xiàn)你在操作集合的下標(biāo)時(shí),你很有可能在做錯(cuò)事。



遍歷兩個(gè)集合


names?= ['raymond',?'rachel',?'matthew']

colors?= ['red',?'green',?'blue',?'yellow']


n?= min(len(names),?len(colors))

for ?i? in ?range(n):

????print names[i],?'--->',?colors[i]


for ?name,?color in ?zip(names,?colors):

????print name,?'--->',?color


更好的方法


for ?name,?color in ?izip(names,?colors):

????print name,?'--->',?color


zip在內(nèi)存中生成一個(gè)新的列表,需要更多的內(nèi)存。izip比zip效率更高。


注意:在Python 3中,izip改名為zip,并替換了原來(lái)的zip成為內(nèi)置函數(shù)。


有序地遍歷


colors?= ['red',?'green',?'blue',?'yellow']


# 正序

for ?color in ?sorted(colors):

????print colors


# 倒序

for ?color in ?sorted(colors,?reverse= True ):

????print colors


自定義排序順序


colors?= ['red',?'green',?'blue',?'yellow']


def compare_length(c1,?c2):

???? if ?len(c1)?< len(c2): return ?-1

???? if ?len(c1)?> len(c2): return ?1

???? return ?0


print sorted(colors,?cmp=compare_length)


更好的方法


print sorted(colors, key=len)


第一種方法效率低而且寫起來(lái)很不爽。另外,Python 3已經(jīng)不支持比較函數(shù)了。


調(diào)用一個(gè)函數(shù)直到遇到標(biāo)記值


blocks?= []

while ? True :

????block?= f.read(32)

???? if ?block?== '':

???????? break

????blocks.append(block)


更好的方法


blocks?= []

for ?block in ?iter(partial(f.read,?32),?''):

????blocks.append(block)


iter接受兩個(gè)參數(shù)。第一個(gè)是你反復(fù)調(diào)用的函數(shù),第二個(gè)是標(biāo)記值。


譯注:這個(gè)例子里不太能看出來(lái)方法二的優(yōu)勢(shì),甚至覺(jué)得partial讓代碼可讀性更差了。方法二的優(yōu)勢(shì)在于iter的返回值是個(gè)迭代器,迭代器能用在各種地方,set,sorted,min,max,heapq,sum……


在循環(huán)內(nèi)識(shí)別多個(gè)退出點(diǎn)


def find(seq,?target):

????found?= False

???? for ?i,?value in ?enumerate(seq):

???????? if ?value?== target:

????????????found?= True

???????????? break

???? if ? not ?found:

???????? return ?-1

???? return ?i


更好的方法


def find(seq,?target):

???? for ?i,?value in ?enumerate(seq):

???????? if ?value?== target:

???????????? break

???? else :

???????? return ?-1

???? return ?i


for執(zhí)行完所有的循環(huán)后就會(huì)執(zhí)行else。


譯注:剛了解for-else語(yǔ)法時(shí)會(huì)困惑,什么情況下會(huì)執(zhí)行到else里。有兩種方法去理解else。傳統(tǒng)的方法是把for看作if,當(dāng)for后面的條件為False時(shí)執(zhí)行else。其實(shí)條件為False時(shí),就是for循環(huán)沒(méi)被break出去,把所有循環(huán)都跑完的時(shí)候。所以另一種方法就是把else記成nobreak,當(dāng)for沒(méi)有被break,那么循環(huán)結(jié)束時(shí)會(huì)進(jìn)入到else。


遍歷字典的 key


d?= {'matthew': 'blue',?'rachel': 'green',?'raymond': 'red'}


for ?k? in ?d:

????print?k


for ?k? in ?d.keys():

???? if ?k.startswith('r'):

????????del?d[k]


什么時(shí)候應(yīng)該使用第二種而不是第一種方法?當(dāng)你需要修改字典的時(shí)候。


如果你在迭代一個(gè)東西的時(shí)候修改它,那就是在冒天下之大不韙,接下來(lái)發(fā)生什么都活該。


d.keys()把字典里所有的key都復(fù)制到一個(gè)列表里。然后你就可以修改字典了。


注意:如果在Python 3里迭代一個(gè)字典你得顯示地寫:list(d.keys()),因?yàn)閐.keys()返回的是一個(gè)“字典視圖”(一個(gè)提供字典key的動(dòng)態(tài)視圖的迭代器)。詳情請(qǐng)看文檔。


遍歷一個(gè)字典的 key value


# 并不快,每次必須要重新哈希并做一次查找

for ?k? in ?d:

????print?k,?'--->',?d[k]


# 產(chǎn)生一個(gè)很大的列表

for ?k,?v? in ?d.items():

????print?k,?'--->',?v


更好的方法


for ?k,?v? in ?d.iteritems():

????print?k,?'--->',?v


iteritems()更好是因?yàn)樗祷亓艘粋€(gè)迭代器。


注意:Python 3已經(jīng)沒(méi)有iteritems()了,items()的行為和iteritems()很接近。詳情請(qǐng)看文檔。


key-value 對(duì)構(gòu)建字典


names?= ['raymond',?'rachel',?'matthew']

colors?= ['red',?'green',?'blue']


d?= dict(izip(names,?colors))

# {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}


Python 3: d = dict(zip(names, colors))


用字典計(jì)數(shù)


colors?= ['red',?'green',?'red',?'blue',?'green',?'red']


# 簡(jiǎn)單,基本的計(jì)數(shù)方法。適合初學(xué)者起步時(shí)學(xué)習(xí)。

d?= {}

for ?color in ?colors:

???? if ?color not ? in ?d:

????????d[color]?= 0

????d[color]?+= 1


# {'blue': 1, 'green': 2, 'red': 3}


更好的方法


d?= {}

for ?color in ?colors:

????d[color]?= d.get(color,?0)?+ 1


# 稍微潮點(diǎn)的方法,但有些坑需要注意,適合熟練的老手。

d?= defaultdict( int )

for ?color in ?colors:

????d[color]?+= 1


用字典分組 ?— I 部分和第 II 部分


names?= ['raymond',?'rachel',?'matthew',?'roger',

???????? 'betty',?'melissa',?'judith',?'charlie']


# 在這個(gè)例子,我們按name的長(zhǎng)度分組

d?= {}

for ?name in ?names:

????key?= len(name)

???? if ?key not ? in ?d:

????????d[key]?= []

????d[key].append(name)


# {5: ['roger', 'betty'], 6: ['rachel', 'judith'], 7: ['raymond', 'matthew', 'melissa', 'charlie']}


d?= {}

for ?name in ?names:

????key?= len(name)

????d.setdefault(key,?[]).append(name)


更好的方法


d?= defaultdict(list)

for ?name in ?names:

????key?= len(name)

????d[key].append(name)


字典的 popitem() 是原子的嗎?


d?= {'matthew': 'blue',?'rachel': 'green',?'raymond': 'red'}


while ?d:

????key,?value?= d.popitem()

????print key,?'-->',?value


popitem是原子的,所以多線程的時(shí)候沒(méi)必要用鎖包著它。


連接字典


defaults?= {'color': 'red',?'user': 'guest'}

parser?= argparse.ArgumentParser()

parser.add_argument('-u',?'--user')

parser.add_argument('-c',?'--color')

namespace ?= parser.parse_args([])

command_line_args?= {k: v? for ?k,?v? in ?vars( namespace ).items()? if ?v}


# 下面是通常的作法,默認(rèn)使用第一個(gè)字典,接著用環(huán)境變量覆蓋它,最后用命令行參數(shù)覆蓋它。

# 然而不幸的是,這種方法拷貝數(shù)據(jù)太瘋狂。

d?= defaults.copy()

d.update(os.environ)

d.update(command_line_args)


更好的方法


d = ChainMap(command_line_args, os.environ, defaults)


ChainMap在Python 3中加入。高效而優(yōu)雅。


提高可讀性


[if !supportLists]·?[endif]位置參數(shù)和下標(biāo)很漂亮

[if !supportLists]·?[endif]但關(guān)鍵字和名稱更好

[if !supportLists]·?[endif]第一種方法對(duì)計(jì)算機(jī)來(lái)說(shuō)很便利

[if !supportLists]·?[endif]第二種方法和人類思考方式一致


用關(guān)鍵字參數(shù)提高函數(shù)調(diào)用的可讀性


twitter_search('@obama', False, 20, True)


更好的方法


twitter_search('@obama', retweets=False, numtweets=20, popular=True)


第二種方法稍微(微秒級(jí))慢一點(diǎn),但為了代碼的可讀性和開(kāi)發(fā)時(shí)間,值得。


namedtuple 提高多個(gè)返回值的可讀性


# 老的testmod返回值

doctest.testmod()

# (0, 4)

# 測(cè)試結(jié)果是好是壞?你看不出來(lái),因?yàn)榉祷刂挡磺逦?


更好的方法


# 新的testmod返回值, 一個(gè)namedtuple

doctest.testmod()

# TestResults(failed=0, attempted=4)


namedtuple是tuple的子類,所以仍適用正常的元組操作,但它更友好。


創(chuàng)建一個(gè)nametuple


TestResults = namedTuple('TestResults', ['failed', 'attempted'])


unpack 序列


p?= 'Raymond',?'Hettinger',?0x30,?'python@example.com'


# 其它語(yǔ)言的常用方法/習(xí)慣

fname?= p[0]

lname?= p[1]

age?= p[2]

email?= p[3]


更好的方法


fname, lname, age, email = p


第二種方法用了unpack元組,更快,可讀性更好。


更新多個(gè)變量的狀態(tài)


def fibonacci(n):

????x?= 0

????y?= 1

???? for ?i? in ?range(n):

????????print?x

????????t?= y

????????y?= x?+ y

????????x?= t


更好的方法


def fibonacci(n):

????x,?y?= 0,?1

???? for ?i? in ?range(n):

????????print?x

????????x,?y?= y,?x?+ y


第一種方法的問(wèn)題


[if !supportLists]·?[endif]x和y是狀態(tài),狀態(tài)應(yīng)該在一次操作中更新,分幾行的話狀態(tài)會(huì)互相對(duì)不上,這經(jīng)常是bug的源頭。

[if !supportLists]·?[endif]操作有順序要求

[if !supportLists]·?[endif]太底層太細(xì)節(jié)


第二種方法抽象層級(jí)更高,沒(méi)有操作順序出錯(cuò)的風(fēng)險(xiǎn)而且更效率更高。


同時(shí)狀態(tài)更新


tmp_x?= x?+ dx *?t

tmp_y?= y?+ dy *?t

tmp_dx?= influence(m,?x,?y,?dx,?dy,?partial='x')

tmp_dy?= influence(m,?x,?y,?dx,?dy,?partial='y')

x?= tmp_x

y?= tmp_y

dx?= tmp_dx

dy?= tmp_dy


更好的方法


x,?y,?dx,?dy?= (x?+ dx *?t,

????????????????y?+ dy *?t,

????????????????influence(m,?x,?y,?dx,?dy,?partial='x'),

????????????????influence(m,?x,?y,?dx,?dy,?partial='y'))


效率


[if !supportLists]·?[endif]優(yōu)化的基本原則

[if !supportLists]·?[endif]除非必要,別無(wú)故移動(dòng)數(shù)據(jù)

[if !supportLists]·?[endif]稍微注意一下用線性的操作取代O(n**2)的操作


總的來(lái)說(shuō),不要無(wú)故移動(dòng)數(shù)據(jù)


連接字符串


names?= ['raymond',?'rachel',?'matthew',?'roger',

???????? 'betty',?'melissa',?'judith',?'charlie']


s?= names[0]

for ?name in ?names[1:]:

????s?+= ', '?+ name

print?s


更好的方法


print ', '.join(names)


更新序列


names?= ['raymond',?'rachel',?'matthew',?'roger',

???????? 'betty',?'melissa',?'judith',?'charlie']


del names[0]

# 下面的代碼標(biāo)志著你用錯(cuò)了數(shù)據(jù)結(jié)構(gòu)

names.pop(0)

names.insert(0,?'mark')


更好的方法


names?= deque(['raymond',?'rachel',?'matthew',?'roger',

?????????????? 'betty',?'melissa',?'judith',?'charlie'])


# 用deque更有效率

del names[0]

names.popleft()

names.appendleft('mark')


裝飾器和上下文管理


[if !supportLists]·?[endif]用于把業(yè)務(wù)和管理的邏輯分開(kāi)

[if !supportLists]·?[endif]分解代碼和提高代碼重用性的干凈優(yōu)雅的好工具

[if !supportLists]·?[endif]起個(gè)好名字很關(guān)鍵

[if !supportLists]·?[endif]記住蜘蛛俠的格言:能力越大,責(zé)任越大


使用裝飾器分離出管理邏輯


# 混著業(yè)務(wù)和管理邏輯,無(wú)法重用

def web_lookup(url,?saved={}):

???? if ?url in ?saved:

???????? return ?saved[url]

????page?= urllib.urlopen(url).read()

????saved[url]?= page

???? return ?page


更好的方法


@cache

def web_lookup(url):

???? return ?urllib.urlopen(url).read()


注意:Python 3.2開(kāi)始加入了functools.lru_cache解決這個(gè)問(wèn)題。


分離臨時(shí)上下文


# 保存舊的,創(chuàng)建新的

old_context?= getcontext().copy()

getcontext().prec?= 50

print Decimal(355)?/ Decimal(113)

setcontext(old_context)


更好的方法


with localcontext(Context(prec=50)):

????print Decimal(355)?/ Decimal(113)


譯注:示例代碼在使用標(biāo)準(zhǔn)庫(kù)decimal,這個(gè)庫(kù)已經(jīng)實(shí)現(xiàn)好了localcontext。


如何打開(kāi)關(guān)閉文件


f?= open('data.txt')

try :

????data?= f.read()

finally :

????f.close()


更好的方法


with open('data.txt')? as ?f:

????data?= f.read()


如何使用鎖


# 創(chuàng)建鎖

lock?= threading.Lock()


# 使用鎖的老方法

lock.acquire()

try :

????print?'Critical section 1'

????print?'Critical section 2'

finally :

????lock.release()


更好的方法


# 使用鎖的新方法

with lock:

????print?'Critical section 1'

????print?'Critical section 2'


分離出臨時(shí)的上下文


try :

????os.remove('somefile.tmp')

except OSError:

????pass


更好的方法


with ignored(OSError):

????os.remove('somefile.tmp')


ignored是Python 3.4加入的, 文檔。


注意:ignored 實(shí)際上在標(biāo)準(zhǔn)庫(kù)叫suppress(譯注:contextlib.supress).


試試創(chuàng)建你自己的 ignored 上下文管理器。


@contextmanager

def ignored(*exceptions):

???? try :

????????yield

????except exceptions:

????????pass


把它放在你的工具目錄,你也可以忽略異常


譯注:contextmanager在標(biāo)準(zhǔn)庫(kù)contextlib中,通過(guò)裝飾生成器函數(shù),省去用__enter__和__exit__寫上下文管理器。詳情請(qǐng)看文檔。


分離臨時(shí)上下文


# 臨時(shí)把標(biāo)準(zhǔn)輸出重定向到一個(gè)文件,然后再恢復(fù)正常

with open('help.txt',?'w')? as ?f:

????oldstdout?= sys.stdout

????sys.stdout?= f

???? try :

????????help(pow)

???? finally :

????????sys.stdout?= oldstdout


更好的寫法


with open('help.txt',?'w')? as ?f:

????with redirect_stdout(f):

????????help(pow)


redirect_stdout在Python 3.4加入(譯注:contextlib.redirect_stdout),?bug反饋。


實(shí)現(xiàn)你自己的 redirect_stdout 上下文管理器。


@contextmanager

def redirect_stdout(fileobj):

????oldstdout?= sys.stdout

????sys.stdout?= fileobj

???? try :

????????yield fieldobj

???? finally :

????????sys.stdout?= oldstdout


簡(jiǎn)潔的單句表達(dá)


兩個(gè)沖突的原則:


[if !supportLists]·?[endif]一行不要有太多邏輯

[if !supportLists]·?[endif]不要把單一的想法拆分成多個(gè)部分


Raymond 的原則:


[if !supportLists]·?[endif]一行代碼的邏輯等價(jià)于一句自然語(yǔ)言


列表解析和生成器


result?= []

for ?i? in ?range(10):

s?= i ** 2

????result.append(s)

print sum(result)


更好的方法

print sum(i**2 for i in xrange(10))


第一種方法說(shuō)的是你在做什么,第二種方法說(shuō)的是你想要什么。


編譯:0xFEE1C001?

www.lightxue.com/transforming-code-into-beautiful-idiomatic-python

來(lái)源:Python開(kāi)發(fā)者


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 乌审旗| 南开区| 凤山市| 大厂| 登封市| 怀安县| 寿光市| 安塞县| 洛南县| 九寨沟县| 安宁市| 衡东县| 平原县| 东宁县| 巴彦县| 抚顺市| 皮山县| 巴南区| 梅河口市| 屯昌县| 穆棱市| 城市| 江西省| 尉犁县| 彩票| 滨州市| 抚顺县| 嘉善县| 长丰县| 梓潼县| 雅江县| 彰化县| 额尔古纳市| 林芝县| 墨江| 泌阳县| 南平市| 茂名市| 临沂市| 顺昌县| 巴青县|