詳細介紹Python函數中的預設參數_python

來源:互聯網
上載者:User
import datetime as dt def log_time(message, time=None):  if time is None:    time=dt.datetime.now()  print("{0}: {1}".format(time.isoformat(), message))

最近我在一段Python代碼中發現了一個因為錯誤的使用預設參數而產生的非常噁心的bug。如果您已經知道關於預設參數的全部內容了,只是想嘲笑一下我這可笑的錯誤,請直接跳到本文末尾。哎,這段代碼是我寫的,但是我非常確定那天我被惡魔附體了。你懂的,有時候就是這樣。

本文僅僅是總結一下關於Python函數的標準參數和預設參數的一些基本內容。提醒你注意你的代碼中可能存在的陷阱。如果你剛開始接觸Python,開始寫一些函數,我真心推薦你看一下Python官方手冊中關於函數的內容,連結如下:Defining Functions 以及 More on Defining Functions。
簡單複習一下函數

Python是一個強大的物件導向語言,它把這種編程範式推向了頂峰。但是,物件導向編程仍然需要依靠函數這一概念,你可以用它來處理資料。Python對於可調用對象有一個更寬泛的概念,即任何對象都可以被調用,調用的意思是對其應用資料。

函數在Python中是可調用對象,並且乍一看,它和其他語言中的函數有著類似的行為。它們擷取一些資料,這些資料被稱為參數,然後處理它們,接著返回結果(如果沒有return語句則是None)

參數被聲明為預留位置(在定義函數的時候),用以代表那些當函數調用時被實際傳入的對象。在Python中你不需要聲明參數的類型(例如,像你在C或Java中做的那樣)因為Python哲學依賴於多態。

記住,Python的變數是引用,即實際變數的記憶體位址。這意味著Python的函數永遠以“傳址”的方式工作(這裡使用了一個C/C++術語),當你調用一個函數的時候,並不是複製了一份參數的值來替換預留位置,而是把預留位置指向了變數本身。這導致了一個非常重要的結果:你可以在函數內部改變這個變數的值。這裡有一個很好可視化講解,關於引用機制。

引用在Python扮演著非常重要的角色,它是Python完全多態方式的骨幹。關於這個非常重要的主題,請點擊這個連結 查看更好的解釋。

為了檢查你是否理解了這門語言的這一基本特性,請跟隨這段簡單的代碼(變數ph代表的是“預留位置(placeholder)”)
 

>>> def print_id(ph):... print(hex(id(ph)))...>>> a = 5>>> print(hex(id(a)))0x84ab460>>> print_id(a)0x84ab460>>>>>> def alter_value(ph):... ph = ph + 1... return ph...>>> b = alter_value(a)>>> b6>>> a5>>> hex(id(a))'0x84ab460'>>> hex(id(b))'0x84ab470'>>>>>> def alter_value(ph):... ph.append(1)... return ph...>>> a = [1,2,3]>>> b = alter_value(a)>>> a[1, 2, 3, 1]>>> b[1, 2, 3, 1]>>> hex(id(a))'0xb701f72c'>>> hex(id(b))'0xb701f72c'>>>

如果你對這裡發生的事情並不感到吃驚,那說明你已經掌握了Python中最為重要的部分之一,你可以放心的跳過下面的解釋了。

print_id()函數顯示,函數內部的預留位置同運行時傳入的變數完全一樣(它們的記憶體位址一致)。

兩個版本的alter_value()意在改變傳入參數的值。正如你所看到的,第一個alter_value() 並沒有像第二個alter_value()一樣成功的改變變數a的值。這是為什麼呢?實際上兩者的行為是一樣的,都是嘗試修改傳入的原始變數的值,但是在Python中,有些變數是不可變的(immutable),整數就在此列。另一方面,列表並不是不可變的,所以函數得以完成它的名字所保證的工作。 在這裡,你可以找到關於不可變類型的更加詳細的介紹 。

關於Python中的函數,還有一些要說的,但是這些是關於標準的參數的基本知識。
預設參數值

有時候你需要定義一個函數,讓它接受一個參數,而且在這個參數出現或不出現時,函數有不同的行為。如果一門語言不支援這種情況,你就只有兩個選擇:第一種是定義兩個不同的函數,決定每次調用應該選擇調用哪個,第二種是 兩種方法都是可行的,但是都不是最佳的。

Python和其他語言一樣,支援預設參數值,即函數參數可以是調用時指定的,也可以留空,自動接受一個預定義的值。

一個關於預設值的非常簡單(也很沒用)的例子如下:
 

def log(message=None):  if message:    print("LOG: {0}".format(message))

這個函數可以帶一個參數運行(可以是None)

 >>> log("File closed")LOG: File closed>>> log(None)>>>

但是同樣也可以不帶參數運行,這種情況下它會接受一個函數原型中設定的預設值(本例中是None)
 

>>> log()>>>

你可以在標準庫中找到更多有趣的例子,比如在open()函數中(請查看官方文檔)
 

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

函數原型可以證明,例如 f = open('/etc/hosts')這樣的調用,通過傳入預設值隱藏了很多參數 (mode, buffering, encoding, 等),並且使這個函數的典型應用案例變得非常簡單易用。

正如你在內建的open()函數中看到的那樣,我們可以在函數中使用標準或者預設參數,但是兩者在函數中出現的次序是固定的:首先調用標準參數,然後調用預設參數。
 

def a_rich_function(a, b, c, d=None, e=0):  pass

原因是顯而易見的:如果我們可以在標準參數前面放置一個預設參數,語言就無法理解,預設參數是否已經被初始化。例如,考慮下面這個函數定義
 

def a_rich_function(a, b, d=None, c, e=0):  pass

當調用函數a_rich_function(1, 2, 4, 5)時,我們傳入了什麼參數? 是d=4, c=5 還是c=4, e=5?因為d有一個預設的值。因此這種順序的定義是被禁止的,如果你這樣做,Python會拋出一個SyntaxError
 

>>> def a_rich_function(a, b, d=None, c, e=0):... pass... File "<stdin>", line 1SyntaxError: non-default argument follows default argument>>>

預設參數求值

預設參數可以通過普通值或是函數調用結果來提高,但是後者這種技術需要一個特別的警示

一個普通的值是硬式編碼,因此除了編譯時間,其他時候是不需要求值的,但是函數調用期望在運行時執行求值。所以我們可以這樣寫
 

import datetime as dt def log_time(message, time=dt.datetime.now()):  print("{0}: {1}".format(time.isoformat(), message))

每次我們調用log_time()時都期望它能夠正確提供目前時間。悲劇的是並沒有成功:預設參數在定義時求值(比如說當你首次匯入模組時),調用的結果如下
 

>>> log_time("message 1")2015-02-10T21:20:32.998647: message 1>>> log_time("message 2")2015-02-10T21:20:32.998647: message 2>>> log_time("message 3")2015-02-10T21:20:32.998647: message 3

如果把預設值賦給一個類的執行個體,結果會更加奇怪,你可以在Hitchhiker's Guide to Python!中讀到相關內容。根據。。通常的解決方案是把預設參數替換為None,並且在函數內部檢查參數值。
 
結論

預設參數能夠極大的簡化API,你需要關注它唯一的“失敗點”,即求值的時機。令人驚奇的是,Python最基本的內容之一,函數的參數和引用,是最大的錯誤源之一,有時候對於有經驗的程式員也一樣。我建議抽時間學習一下引用和多態。
相關閱讀:

  •     OOP concepts in Python 2.x – Part 2
  •     Python 3 OOP Part 1 – Objects and types
  •     Digging up Django class-based views – 2
  •     Python Generators – From Iterators to Cooperative Multitasking – 2
  •     OOP concepts in Python 2.x – Part 1

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.