python怎么写出来的_如何写出优雅又地道的Python代码?【转载】
在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ì)到“代碼是寫(xiě)給人看的,只是順便讓機(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)不夠。如果你正被如何寫(xiě)出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ù)字
foriin[0,1,2,3,4,5]:
printi **?2
foriinrange(6):
printi **?2
更好的方法
foriinxrange(6):
printi **?2
xrange會(huì)返回一個(gè)迭代器,用來(lái)一次一個(gè)值地遍歷一個(gè)范圍。這種方式會(huì)比range更省內(nèi)存。xrange在Python 3中已經(jīng)改名為range。
遍歷一個(gè)集合
colors=?['red','green','blue','yellow']
foriinrange(len(colors)):
print?colors[i]
更好的方法
forcolor?incolors:
print?color
反向遍歷
colors=?['red','green','blue','yellow']
foriinrange(len(colors)-1,-1,-1):
print?colors[i]
更好的方法
forcolor?inreversed(colors):
print?color
遍歷一個(gè)集合及其下標(biāo)
colors=?['red','green','blue','yellow']
foriinrange(len(colors)):
printi,'--->',colors[i]
更好的方法
fori,color?inenumerate(colors):
printi,'--->',color
這種寫(xiě)法效率高,優(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))
foriinrange(n):
print?names[i],'--->',colors[i]
forname,color?inzip(names,colors):
print?name,'--->',color
更好的方法
forname,color?inizip(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']
# 正序
forcolor?insorted(colors):
print?colors
# 倒序
forcolor?insorted(colors,reverse=True):
print?colors
自定義排序順序
colors=?['red','green','blue','yellow']
def compare_length(c1,c2):
iflen(c1)
iflen(c1)>?len(c2):?return1
return0
print sorted(colors,cmp=compare_length)
更好的方法
print sorted(colors, key=len)
第一種方法效率低而且寫(xiě)起來(lái)很不爽。另外,Python 3已經(jīng)不支持比較函數(shù)了。
調(diào)用一個(gè)函數(shù)直到遇到標(biāo)記值
blocks=?[]
whileTrue:
block=?f.read(32)
ifblock==?'':
break
blocks.append(block)
更好的方法
blocks=?[]
forblock?initer(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
fori,value?inenumerate(seq):
ifvalue==?target:
found=?True
break
ifnotfound:
return-1
returni
更好的方法
def find(seq,target):
fori,value?inenumerate(seq):
ifvalue==?target:
break
else:
return-1
returni
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'}
forkind:
printk
forkind.keys():
ifk.startswith('r'):
deld[k]
什么時(shí)候應(yīng)該使用第二種而不是第一種方法?當(dāng)你需要修改字典的時(shí)候。
如果你在迭代一個(gè)東西的時(shí)候修改它,那就是在冒天下之大不韙,接下來(lái)發(fā)生什么都活該。
d.keys()把字典里所有的key都復(fù)制到一個(gè)列表里。然后你就可以修改字典了。
注意:如果在Python 3里迭代一個(gè)字典你得顯示地寫(xiě):list(d.keys()),因?yàn)閐.keys()返回的是一個(gè)“字典視圖”(一個(gè)提供字典key的動(dòng)態(tài)視圖的迭代器)。詳情請(qǐng)看文檔。
遍歷一個(gè)字典的key和value
# 并不快,每次必須要重新哈希并做一次查找
forkind:
printk,'--->',d[k]
# 產(chǎn)生一個(gè)很大的列表
fork,vind.items():
printk,'--->',v
更好的方法
fork,vind.iteritems():
printk,'--->',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=?{}
forcolor?incolors:
ifcolor?notind:
d[color]=?0
d[color]+=?1
# {'blue': 1, 'green': 2, 'red': 3}
更好的方法
d=?{}
forcolor?incolors:
d[color]=?d.get(color,0)+?1
# 稍微潮點(diǎn)的方法,但有些坑需要注意,適合熟練的老手。
d=?defaultdict(int)
forcolor?incolors:
d[color]+=?1
用字典分組 — 第I部分和第II部分
names=?['raymond','rachel','matthew','roger',
'betty','melissa','judith','charlie']
# 在這個(gè)例子,我們按name的長(zhǎng)度分組
d=?{}
forname?innames:
key=?len(name)
ifkey?notind:
d[key]=?[]
d[key].append(name)
# {5: ['roger', 'betty'], 6: ['rachel', 'judith'], 7: ['raymond', 'matthew', 'melissa', 'charlie']}
d=?{}
forname?innames:
key=?len(name)
d.setdefault(key,[]).append(name)
更好的方法
d=?defaultdict(list)
forname?innames:
key=?len(name)
d[key].append(name)
字典的popitem()是原子的嗎?
d=?{'matthew':?'blue','rachel':?'green','raymond':?'red'}
whiled:
key,value=?d.popitem()
print?key,'-->',value
popitem是原子的,所以多線(xiàn)程的時(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:?vfork,vinvars(namespace).items()ifv}
# 下面是通常的作法,默認(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)雅。
提高可讀性
位置參數(shù)和下標(biāo)很漂亮
但關(guān)鍵字和名稱(chēng)更好
第一種方法對(duì)計(jì)算機(jī)來(lái)說(shuō)很便利
第二種方法和人類(lèi)思考方式一致
用關(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)榉祷刂挡磺逦?/p>
更好的方法
# 新的testmod返回值, 一個(gè)namedtuple
doctest.testmod()
# TestResults(failed=0, attempted=4)
namedtuple是tuple的子類(lèi),所以仍適用正常的元組操作,但它更友好。
創(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
foriinrange(n):
printx
t=?y
y=?x+?y
x=?t
更好的方法
def fibonacci(n):
x,y=?0,1
foriinrange(n):
printx
x,y=?y,x+?y
第一種方法的問(wèn)題
x和y是狀態(tài),狀態(tài)應(yīng)該在一次操作中更新,分幾行的話(huà)狀態(tài)會(huì)互相對(duì)不上,這經(jīng)常是bug的源頭。
操作有順序要求
太底層太細(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'))
效率
優(yōu)化的基本原則
除非必要,別無(wú)故移動(dòng)數(shù)據(jù)
稍微注意一下用線(xiàn)性的操作取代O(n**2)的操作
總的來(lái)說(shuō),不要無(wú)故移動(dòng)數(shù)據(jù)
連接字符串
names=?['raymond','rachel','matthew','roger',
'betty','melissa','judith','charlie']
s=?names[0]
forname?innames[1:]:
s+=?', '+?name
prints
更好的方法
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')
裝飾器和上下文管理
用于把業(yè)務(wù)和管理的邏輯分開(kāi)
分解代碼和提高代碼重用性的干凈優(yōu)雅的好工具
起個(gè)好名字很關(guān)鍵
記住蜘蛛俠的格言:能力越大,責(zé)任越大
使用裝飾器分離出管理邏輯
# 混著業(yè)務(wù)和管理邏輯,無(wú)法重用
def web_lookup(url,saved={}):
ifurl?insaved:
returnsaved[url]
page=?urllib.urlopen(url).read()
saved[url]=?page
returnpage
更好的方法
@cache
def web_lookup(url):
returnurllib.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')asf:
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__寫(xiě)上下文管理器。詳情請(qǐng)看文檔。
分離臨時(shí)上下文
# 臨時(shí)把標(biāo)準(zhǔn)輸出重定向到一個(gè)文件,然后再恢復(fù)正常
with open('help.txt','w')asf:
oldstdout=?sys.stdout
sys.stdout=?f
try:
help(pow)
finally:
sys.stdout=?oldstdout
更好的寫(xiě)法
with open('help.txt','w')asf:
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è)沖突的原則:
一行不要有太多邏輯
不要把單一的想法拆分成多個(gè)部分
Raymond的原則:
一行代碼的邏輯等價(jià)于一句自然語(yǔ)言
列表解析和生成器
result=?[]
foriinrange(10):
s=?i **?2
result.append(s)
print sum(result)
更好的方法
print sum(i**2 for i in xrange(10))
第一種方法說(shuō)的是你在做什么,第二種方法說(shuō)的是你想要什么。
來(lái)源:www.lightxue.com/transforming-code-into-beautiful-idiomatic-python 侵刪。
總結(jié)
以上是生活随笔為你收集整理的python怎么写出来的_如何写出优雅又地道的Python代码?【转载】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux 带缓存的fwrite,文件I
- 下一篇: python怎么退出调试模式_pytho