国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Python > 正文

深入討論Python函數的參數的默認值所引發的問題的原因

2019-11-25 17:53:47
字體:
來源:轉載
供稿:網友

本文將介紹使用mutable對象作為Python函數參數默認值潛在的危害,以及其實現原理和設計目的
陷阱重現

我們就用實際的舉例來演示我們今天所要討論的主要內容。

下面一段代碼定義了一個名為 generate_new_list_with 的函數。該函數的本意是在每次調用時都新建一個包含有給定 element 值的list。而實際運行結果如下:
 

Python 2.7.9 (default, Dec 19 2014, 06:05:48)[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwinType "help", "copyright", "credits" or "license" for more information.>>> def generate_new_list_with(my_list=[], element=None):...   my_list.append(element)...   return my_list...>>> list_1 = generate_new_list_with(element=1)>>> list_1[1]>>> list_2 = generate_new_list_with(element=2)>>> list_2[1, 2]>>>

可見代碼運行結果并不和我們預期的一樣。list_2在函數的第二次調用時并沒有得到一個新的list并填入2,而是在第一次調用結果的基礎上append了一個2。為什么會發生這樣在其他編程語言中簡直就是設計bug一樣的問題呢?
準備知識:Python變量的實質

要了解這個問題的原因我們先需要一個準備知識,那就是:Python變量到底是如何實現的?

Python變量區別于其他編程語言的申明&賦值方式,采用的是創建&指向的類似于指針的方式實現的。即Python中的變量實際上是對值或者對象的一個指針(簡單的說他們是值得一個名字)。我們來看一個例子。
 

p = 1p = p+1

對于傳統語言,上面這段代碼的執行方式將會是,先在內存中申明一個p的變量,然后將1存入變量p所在內存。執行加法操作的時候得到2的結果,將2這個數值再次存入到p所在內存地址中。可見整個執行過程中,變化的是變量p所在內存地址上的值

面這段代碼中,Python實際上是現在執行內存中創建了一個1的對象,并將p指向了它。在執行加法操作的時候,實際上通過加法操作得到了一個2的新對象,并將p指向這個新的對象。可見整個執行過程中,變化的是p指向的內存地址
函數參數默認值陷阱的根本原因

一句話來解釋:Python函數的參數默認值,是在編譯階段就綁定的。

現在,我們先從一段摘錄來詳細分析這個陷阱的原因。下面是一段從Python Common Gotchas中摘錄的原因解釋:

Python's default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.

可見如果參數默認值是在函數編譯compile階段就已經被確定。之后所有的函數調用時,如果參數不顯示的給予賦值,那么所謂的參數默認值不過是一個指向那個在compile階段就已經存在的對象的指針。如果調用函數時,沒有顯示指定傳入參數值得話。那么所有這種情況下的該參數都會作為編譯時創建的那個對象的一種別名存在。

如果參數的默認值是一個不可變(Imuttable)數值,那么在函數體內如果修改了該參數,那么參數就會重新指向另一個新的不可變值。而如果參數默認值是和本文最開始的舉例一樣,是一個可變對象(Muttable),那么情況就比較糟糕了。所有函數體內對于該參數的修改,實際上都是對compile階段就已經確定的那個對象的修改。

對于這么一個陷阱在 Python官方文檔中也有特別提示:

Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. For example, the following function accumulates the arguments passed to it on subsequent calls:
如何避免這個陷阱帶來不必要麻煩

當然最好的方式是不要使用可變對象作為函數默認值。如果非要這么用的話,下面是一種解決方案。還是以文章開頭的需求為例:
 

def generate_new_list_with(my_list=None, element=None):  if my_list is None:    my_list = []  my_list.append(element)  return my_list

為什么Python要這么設計

這個問題的答案在StackOverflow 上可以找到答案。這里將得票數最多的答案最重要的部分摘錄如下:

Actually, this is not a design flaw, and it is not because of internals, or performance.

It comes simply from the fact that functions in Python are first-class objects, and not only a piece of code.

As soon as you get to think into this way, then it completely makes sense: a function is an object being evaluated on its definition; default parameters are kind of “member data” and therefore their state may change from one call to the other

主站蜘蛛池模板: 合作市| 上杭县| 海伦市| 永新县| 宁远县| 莱芜市| 微博| 宁乡县| 惠东县| 徐州市| 万山特区| 苏尼特左旗| 安庆市| 蛟河市| 托克托县| 聂荣县| 富顺县| 绥棱县| 望城县| 古蔺县| 普宁市| 堆龙德庆县| 遵义县| 赤峰市| 马边| 曲松县| 云南省| 会泽县| 巨鹿县| 台东市| 青河县| 五台县| 兰坪| 富顺县| 皮山县| 信宜市| 江津市| 临澧县| 靖西县| 安陆市| 临沭县|