日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > python >内容正文

python

python动态语言双刃性_动态语言的灵活性是把双刃剑:以 Python 语言为例

發(fā)布時(shí)間:2023/12/19 python 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python动态语言双刃性_动态语言的灵活性是把双刃剑:以 Python 语言为例 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原標(biāo)題:動(dòng)態(tài)語(yǔ)言的靈活性是把雙刃劍:以 Python 語(yǔ)言為例

本文有些零碎,總題來(lái)說(shuō),包括兩個(gè)問(wèn)題:(1)可變對(duì)象(最常見(jiàn)的是list dict)被意外修改的問(wèn)題,(2)對(duì)參數(shù)(parameter)的檢查問(wèn)題。這兩個(gè)問(wèn)題,本質(zhì)都是因?yàn)閯?dòng)態(tài)語(yǔ)言(動(dòng)態(tài)類(lèi)型語(yǔ)言)的特性造成了,動(dòng)態(tài)語(yǔ)言的好處就不細(xì)說(shuō)了,本文是要討論因?yàn)閯?dòng)態(tài)--這種靈活性帶來(lái)的一些問(wèn)題。

什么是動(dòng)態(tài)語(yǔ)言(Dynamic Programming language)呢,是相對(duì)于靜態(tài)語(yǔ)言而言,將很多靜態(tài)語(yǔ)言編譯(compilation)時(shí)期所做的事情推遲到運(yùn)行時(shí),在運(yùn)行時(shí)修改代碼的行為,比如添加新的對(duì)象和函數(shù),修改既有代碼的功能,改變類(lèi)型。絕大多數(shù)動(dòng)態(tài)語(yǔ)言都是動(dòng)態(tài)類(lèi)型(Dynamic Typed),所謂動(dòng)態(tài)類(lèi)型,是在運(yùn)行時(shí)確定數(shù)據(jù)類(lèi)型,變量使用之前不需要類(lèi)型聲明,通常變量的類(lèi)型是被賦值的那個(gè)值的類(lèi)型。Python就是屬于典型的動(dòng)態(tài)語(yǔ)言。

動(dòng)態(tài)語(yǔ)言的魅力在于讓開(kāi)發(fā)人員更好的關(guān)注需要解決的問(wèn)題本身,而不是冗雜的語(yǔ)言規(guī)范,也不用干啥都得寫(xiě)個(gè)類(lèi)。運(yùn)行時(shí)改變代碼的行為也是非常有用,比如python的熱更新,可以做到不關(guān)服務(wù)器就替換代碼的邏輯,而靜態(tài)語(yǔ)言如C++就很難做到這一點(diǎn)。筆者使用得最多的就是C++和Python,C++中的一些復(fù)雜的點(diǎn),比如模板(泛型編程)、設(shè)計(jì)模式(比如template method),在Python中使用起來(lái)非常自然。我也看到過(guò)有一些文章指出,設(shè)計(jì)模式往往是特定靜態(tài)語(yǔ)言的補(bǔ)丁 — 為了彌補(bǔ)語(yǔ)言的缺陷或者限制。

以筆者的知識(shí)水平,遠(yuǎn)遠(yuǎn)不足以評(píng)價(jià)動(dòng)態(tài)語(yǔ)言與靜態(tài)語(yǔ)言的優(yōu)劣。本文也只是記錄在我使用Python這門(mén)動(dòng)態(tài)語(yǔ)言的時(shí)候,由于語(yǔ)言的靈活性,由于動(dòng)態(tài)類(lèi)型,踩過(guò)的坑,一點(diǎn)思考,以及困惑。

第一個(gè)問(wèn)題:Mutable對(duì)象被誤改

這個(gè)是在線(xiàn)上環(huán)境出現(xiàn)過(guò)的一個(gè)BUG

事后說(shuō)起來(lái)很簡(jiǎn)單,服務(wù)端數(shù)據(jù)(放在dict里面的)被意外修改了,但查證的時(shí)候也花了許多時(shí)間,偽代碼如下:

def routine(dct):

ifhigh_propability:

sub_routine_no_change_dct(dct)

else:

sub_routine_will_change_dct(dct)

上述的代碼很簡(jiǎn)單,dct是一個(gè)dict,極大概率會(huì)調(diào)用一個(gè)不用修改dct的子函數(shù),極小概率出會(huì)調(diào)用到可能修改dct的子函數(shù)。問(wèn)題就在于,調(diào)用routine函數(shù)的參數(shù)是服務(wù)端全局變量,理論上是不能被修改的。當(dāng)然,上述的代碼簡(jiǎn)單到一眼就能看出問(wèn)題,但在實(shí)際環(huán)境中,調(diào)用鏈有七八層,而且,在routine這個(gè)函數(shù)的doc里面,聲明不會(huì)修改dct,該函數(shù)本身確實(shí)沒(méi)有修改dct,但調(diào)用的子函數(shù)或者子函數(shù)的子函數(shù)沒(méi)有遵守這個(gè)約定。

從python語(yǔ)言特性看這個(gè)問(wèn)題

本小節(jié)解釋上面的代碼為什么會(huì)出問(wèn)題,簡(jiǎn)單來(lái)說(shuō)兩點(diǎn):dict是mutable對(duì)象; dict實(shí)例作為參數(shù)傳入函數(shù),然后被函數(shù)修改了。

Python中一切都是對(duì)象(evething is object),不管是int str dict 還是類(lèi)。比如 a =5, 5是一個(gè)整數(shù)類(lèi)型的對(duì)象(實(shí)例);那么a是什么,a是5這個(gè)對(duì)象嗎? 不是的,a只是一個(gè)名字,這個(gè)名字暫時(shí)指向(綁定、映射)到5這個(gè)對(duì)象。b = a 是什么意思呢, 是b指向a指向的對(duì)象,即a, b都指向整數(shù)5這個(gè)對(duì)象

那么什么是mutable 什么是immutable呢,mutable是說(shuō)這個(gè)對(duì)象是可以修改的,immutable是說(shuō)這個(gè)對(duì)象是不可修改的(廢話(huà))。還是看Python官方怎么說(shuō)的吧

Mutable objects can change their value but keep their id().

Immutable:An object with a fixed value. Immutable objects include numbers, strings and tuples. Such an object cannot be altered. A new object has to be created if a different value has to be stored. They play an important role in places where a constant hash value is needed, for example as a key in a dictionary.

承接上面的例子(a = 5),int類(lèi)型就是immutable,你可能說(shuō)不對(duì)啊,比如對(duì)a賦值, a=6, 現(xiàn)在a不是變成6了嗎?是的,a現(xiàn)在”變成”6了,但本質(zhì)是a指向了6這個(gè)對(duì)象 — a不再指向5了

檢驗(yàn)對(duì)象的唯一標(biāo)準(zhǔn)是id,id函數(shù)返回對(duì)象的地址,每個(gè)對(duì)象在都有唯一的地址。看下面兩個(gè)例子就知道了

>>>a=5;id(a)

35170056

>>>a=6;id(a)

35170044

>>>lst=[1,2,3];id(lst)

39117168

>>>lst.append(4);id(lst)

39117168

或者這么說(shuō),對(duì)于非可變對(duì)象,在對(duì)象的生命周期內(nèi),沒(méi)有辦法改變對(duì)象所在內(nèi)存地址上的值。

python中,不可變對(duì)象包括:int, long, float, bool, str, tuple, frozenset;而其他的dict list 自定義的對(duì)象等屬于可變對(duì)象。注意: str也是不可變對(duì)象,這也是為什么在多個(gè)字符串連接操作的時(shí)候,推薦使用join而不是+

而且python沒(méi)有機(jī)制,讓一個(gè)可變對(duì)象不可被修改(此處類(lèi)比的是C++中的const)

dict是可變對(duì)象!

那在python中,調(diào)用函數(shù)時(shí)的參數(shù)傳遞是什么意思呢,是傳值、傳引用?事實(shí)上都不正確,我不清楚有沒(méi)有專(zhuān)業(yè)而統(tǒng)一的說(shuō)法,但簡(jiǎn)單理解,就是形參(parameter)和實(shí)參(argument)都指向同一個(gè)對(duì)象,僅此而已。來(lái)看一下面的代碼:

defdouble(v):

print'argument before',id(v)

v *=2

print'argument after',id(v)

returnv

def test_double(a):

print'parameter bdfore',id(a),a

double(a)

print'parameter after',id(a),a

if__name__=='__main__':

print'test_double with int'

test_double(1)

print'test_double with list'

test_double([1])

運(yùn)行結(jié)果:

test_doublewithint

parameterbdfore305169361

argumentbefore30516936

argumentafter30516924

parameterafter305169361

test_doublewithlist

parameterbdfore37758256[1]

argumentbefore37758256

argumentafter37758256

parameterafter37758256[1,1]

可以看到,剛進(jìn)入子函數(shù)double的時(shí)候,a,v指向的同一個(gè)對(duì)象(相同的id)。對(duì)于test int的例子,v因?yàn)関*=2,指向了另外一個(gè)對(duì)象,但對(duì)實(shí)參a是沒(méi)有任何影響的。對(duì)于testlst的時(shí)候,v*=2是通過(guò)v修改了v指向的對(duì)象(也是a指向的對(duì)象),因此函數(shù)調(diào)用完之后,a指向的對(duì)象內(nèi)容發(fā)生了變化。

如何防止mutable對(duì)象被函數(shù)誤改:

為了防止傳入到子函數(shù)中的可變對(duì)象被修改,最簡(jiǎn)單的就是使用copy模塊拷貝一份數(shù)據(jù)。具體來(lái)說(shuō),包括copy.copy, copy.deepcopy, 前者是淺拷貝,后者是深拷貝。二者的區(qū)別在于:

The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances):

A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.

A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.

簡(jiǎn)單來(lái)說(shuō),深拷貝會(huì)遞歸拷貝,遍歷任何compound object然后拷貝,例如:

>>>lst=[1,[2]]

>>>importcopy

>>>lst1=copy.copy(lst)

>>>lst2=copy.deepcopy(lst)

>>>printid(lst[1]),id(lst1[1]),id(lst2[1])

440282526444028252644402988816

>>>lst[1].append(3)

>>>printlst,lst1,lst2

[1,[2,3]][1,[2,3]][1,[2]]

從例子可以看出淺拷貝的局限性,Python中,對(duì)象的基本構(gòu)造也是淺拷貝,例如

dct = {1: [1]}; dct1 = dict(dct)

正是由于淺拷貝與深拷貝本質(zhì)上的區(qū)別,二者性能代價(jià)差異非常之大,即使對(duì)于被拷貝的對(duì)象來(lái)說(shuō)毫無(wú)差異:

import copy

def test_copy(inv):

returncopy.copy(inv)

def test_deepcopy(inv):

returncopy.deepcopy(inv)

dct={str(i):iforiinxrange(100)}

def timeit_copy():

import timeit

printtimeit.Timer('test_copy(dct)','from __main__ import test_copy, dct').timeit(100000)

printtimeit.Timer('test_deepcopy(dct)','from __main__ import test_deepcopy, dct').timeit(100000)

if__name__=='__main__':

timeit_copy()

運(yùn)行結(jié)果:

1.19009837668

113.11954377

在上面的示例中,dct這個(gè)dict的values都是int類(lèi)型,immutable對(duì)象,因?yàn)闊o(wú)論淺拷貝 深拷貝效果都是一樣的,但是耗時(shí)差異巨大。如果在dct中存在自定義的對(duì)象,差異會(huì)更大

那么為了安全起見(jiàn),應(yīng)該使用深拷貝;為了性能,應(yīng)該使用淺拷貝。如果compound object包含的元素都是immutable,那么淺拷貝既安全又高效,but,對(duì)于python這種靈活性極強(qiáng)的語(yǔ)言,很可能某天某人就加入了一個(gè)mutable元素。

好的API

好的API應(yīng)該是easy to use right; hard to use wrong。API應(yīng)該提供一種契約,約定如果使用者按照特定的方式調(diào)用,那么API就能實(shí)現(xiàn)預(yù)期的效果。

在靜態(tài)語(yǔ)言如C++中,函數(shù)簽名就是最好的契約。

在C++中,參數(shù)傳遞大約有三種形式,傳值、傳指針、傳引用(這里不考慮右值引用)。指針和引用雖然表現(xiàn)形式上差異,但效果上是差不多的,因此這里主要考慮傳值和傳引用。比如下面四個(gè)函數(shù)簽名:

intfunc(inta)

intfunc(const inta)

intfunc(int&a)

intfunc(const int&a)

對(duì)于第1、2個(gè)函數(shù),對(duì)于調(diào)用者來(lái)說(shuō)都是一樣的,因?yàn)槎紩?huì)進(jìn)行拷貝(深拷貝),無(wú)論func函數(shù)內(nèi)部怎么操作,都不會(huì)影響到實(shí)參。二者的區(qū)別在于函數(shù)中能否對(duì)a進(jìn)行修改,比如能否寫(xiě) a *= 2。

第3個(gè)函數(shù),非const引用,任何對(duì)a的修改都會(huì)影響到實(shí)參。調(diào)用者看到這個(gè)API就知道預(yù)期的行為:函數(shù)會(huì)改變實(shí)參的值。

第4個(gè)函數(shù),const引用,函數(shù)承諾絕對(duì)不會(huì)修改實(shí)參,因此調(diào)用者可以放心大膽的傳引用,無(wú)需拷貝。

從上面幾個(gè)API,可以看到,通過(guò)函數(shù)簽名,調(diào)用者就能知道函數(shù)調(diào)用對(duì)傳入的參數(shù)有沒(méi)有影響。

python是動(dòng)態(tài)類(lèi)型檢查,除了運(yùn)行時(shí),沒(méi)法做參數(shù)做任何檢查。有人說(shuō),那就通過(guò)python doc或者變量名來(lái)實(shí)現(xiàn)契約吧,比如:

deffunc(dct_only_read):

“”“param:dct_only_read will be onlyread,neverupate”“”

但是人是靠不住的,也是不可靠的,也許在這個(gè)函數(shù)的子函數(shù)(子函數(shù)的子函數(shù),。。。)就會(huì)修改這個(gè)dict。怎么辦,對(duì)可變類(lèi)型強(qiáng)制copy(deepcopy),但拷貝又非常耗時(shí)。。。

第二個(gè)問(wèn)題:參數(shù)檢查

上一節(jié)說(shuō)明沒(méi)有簽名 對(duì) 函數(shù)調(diào)用者是多么不爽,而本章節(jié)則說(shuō)明沒(méi)有簽名對(duì)函數(shù)提供者有多么不爽。沒(méi)有類(lèi)型檢查真的蛋疼,我也遇到過(guò)有人為了方便,給一個(gè)約定是int類(lèi)型的形參傳入了一個(gè)int的list,而可怕的是代碼不報(bào)錯(cuò),只是表現(xiàn)不正常。

來(lái)看一個(gè)例子:

def func(arg):

ifarg:

print'do lots of things here'

else:

print'do anothers'

上述的代碼很糟糕,根本沒(méi)法“望名知意”,也看不出有關(guān)形參 arg的任何信息。但事實(shí)上這樣的代碼是存在的,而且還有比這更嚴(yán)重的,比如掛羊頭賣(mài)狗肉。

這里有一個(gè)問(wèn)題,函數(shù)期望arg是某種類(lèi)型,是否應(yīng)該寫(xiě)代碼判斷呢,比如:isinstance(arg, str)。因?yàn)闆](méi)有編譯器靜態(tài)來(lái)做參數(shù)檢查,那么要不要檢查,如何檢查就完全是函數(shù)提供者的事情。如果檢查,那么影響性能,也容易違背python的靈活性 — duck typing; 不檢查,又容易被誤用。

但在這里,考慮的是另一個(gè)問(wèn)題,看代碼的第二行: if arg。python中,幾乎是一切對(duì)象都可以當(dāng)作布爾表達(dá)式求值,即這里的arg可以是一切python對(duì)象,可以是bool、int、dict、list以及任何自定義對(duì)象。不同的類(lèi)型為“真”的條件不一樣,比如數(shù)值類(lèi)型(int float)非0即為真;序列類(lèi)型(str、list、dict)非空即為真;而對(duì)于自定義對(duì)象,在python2.7種則是看是否定義了__nonzero__ 、__len__,如果這兩個(gè)函數(shù)都沒(méi)有定義,那么實(shí)例的布爾求值一定返回真。

在PEP8,由以下關(guān)于對(duì)序列布爾求值的規(guī)范:

Forsequences,(strings,lists,tuples),use the fact that empty sequences arefalse.

Yes:ifnotseq:

ifseq:

No:iflen(seq):

ifnotlen(seq):

在google python styleguide中也有一節(jié)專(zhuān)門(mén)關(guān)于bool表達(dá)式,指出“盡可能使用隱式的false”。 對(duì)于序列,推薦的判斷方法與pep8相同,另外還由兩點(diǎn)比較有意思:

如果你需要區(qū)分false和None, 你應(yīng)該用像 if not x and x is not None: 這樣的語(yǔ)句.

處理整數(shù)時(shí), 使用隱式false可能會(huì)得不償失(即不小心將None當(dāng)做0來(lái)處理). 你可以將一個(gè)已知是整型(且不是len()的返回結(jié)果)的值與0比較.

第二點(diǎn)我個(gè)人很贊同;但第一點(diǎn)就覺(jué)得很別扭,因?yàn)檫@樣的語(yǔ)句一點(diǎn)不直觀,難以表達(dá)其真實(shí)目的。

在pep20 the zen of python中,指出:

Explicit is better than implicit.

這句話(huà)簡(jiǎn)單但實(shí)用!代碼是寫(xiě)給人讀的,清晰的表達(dá)代碼的意圖比什么都重要。也許有的人覺(jué)得代碼寫(xiě)得復(fù)雜隱晦就顯得牛逼,比如python中嵌套幾層的list comprehension,且不知這樣害人又害己。

回到布爾表達(dá)式求值這個(gè)問(wèn)題,我覺(jué)得很多時(shí)候直接使用if arg:這種形式都不是好主意,因?yàn)椴恢庇^而且容易出錯(cuò)。比如參數(shù)是int類(lèi)型的情況,

def handle_age(age):

ifnotage:

return

# do lots with age

很難說(shuō)當(dāng)age=0時(shí)是不是一個(gè)合理的輸入,上面的代碼對(duì)None、0一視同仁,看代碼的人也搞不清傳入0是否正確。

另外一個(gè)具有爭(zhēng)議性的例子就是對(duì)序列進(jìn)行布爾求值,推薦的都是直接使用if seq: 的形式,但這種形式違背了”Explicit is better than implicit.“,因?yàn)檫@樣寫(xiě)根本無(wú)法區(qū)分None和空序列,而這二者往往是由區(qū)別的,很多時(shí)候,空序列是一個(gè)合理的輸入,而None不是。這個(gè)問(wèn)題,stackoverflow上也有相關(guān)的討論“如何檢查列表為空”,誠(chéng)然,如果寫(xiě)成 seq == [] 是不那么好的代碼, 因?yàn)椴荒敲挫`活 — 如果seq是tuple類(lèi)型代碼就不能工作了。python語(yǔ)言是典型的duck typing,不管你傳入什么類(lèi)型,只要具備相應(yīng)的函數(shù),那么代碼就可以工作,但是否正確地工作就完完全全取決于使用者。個(gè)人覺(jué)得存在寬泛的約束比較好,比如Python中的ABC(abstract base class), 既滿(mǎn)足了靈活性需求,后能做一些規(guī)范檢查。

總結(jié)

以上兩個(gè)問(wèn)題,是我使用Python語(yǔ)言以來(lái)遇到的諸多問(wèn)題之二,也是我在同一個(gè)地方跌倒過(guò)兩次的問(wèn)題。Python語(yǔ)言以開(kāi)發(fā)效率見(jiàn)長(zhǎng),但是我覺(jué)得需要良好的規(guī)范才能保證在大型線(xiàn)上項(xiàng)目中使用。而且,我也傾向于假設(shè):人是不可靠的,不會(huì)永遠(yuǎn)遵守?cái)M定的規(guī)范,不會(huì)每次修改代碼之后更新docstring …

因此,為了保證代碼的可持續(xù)發(fā)展,需要做到以下幾點(diǎn)

第一:擬定并遵守代碼規(guī)范

代碼規(guī)范最好在項(xiàng)目啟動(dòng)時(shí)就應(yīng)該擬定好,可以參照PEP8和google python styleguild。很多時(shí)候風(fēng)格沒(méi)有優(yōu)劣之說(shuō),但是保證項(xiàng)目?jī)?nèi)的一致性很重要。并保持定期review、對(duì)新人review!

第二:靜態(tài)代碼分析

只要能靜態(tài)發(fā)現(xiàn)的bug不要放到線(xiàn)上,比如對(duì)參數(shù)、返回值的檢查,在python3.x中可以使用注解(Function Annotations),python2.x也可以自行封裝decorator來(lái)做檢查。對(duì)代碼行為,既可以使用Coverity這種高大上的商業(yè)軟件,或者王垠大神的Pysonar2,也可以使用ast編寫(xiě)簡(jiǎn)單的檢查代碼。

第三:單元測(cè)試

單元測(cè)試的重要性想必大家都知道,在python中出了官方自帶的doctest、unittest,還有許多更強(qiáng)大的框架,比如nose、mock。

第四:100%的覆蓋率測(cè)試

對(duì)于python這種動(dòng)態(tài)語(yǔ)言,出了執(zhí)行代碼,幾乎沒(méi)有其他比較好的檢查代碼錯(cuò)誤的手段,所以覆蓋率測(cè)試是非常重要的。可以使用python原生的sys.settrace、sys.gettrace,也可以使用coverage等跟更高級(jí)的工具。

雖然我已經(jīng)寫(xiě)了幾年P(guān)ython了,但是在Python使用規(guī)范上還是很欠缺。我也不知道在其他公司、項(xiàng)目中,是如何使用好Python的,如何揚(yáng)長(zhǎng)避短的。歡迎pythoner留言指導(dǎo)!

references:

Dynamic Programming language

instagram-pycon-2017

https://www.python.org/dev/peps/pep-0008/

google python styleguide

the zen of python

best-way-to-check-if-a-list-is-empty

來(lái)自:xybaby

www.cnblogs.com/xybaby/p/7208496.html

程序員大咖整理發(fā)布,轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)返回搜狐,查看更多

責(zé)任編輯:

總結(jié)

以上是生活随笔為你收集整理的python动态语言双刃性_动态语言的灵活性是把双刃剑:以 Python 语言为例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。