DEV Community

codemee
codemee

Posted on • Edited on

2 2

你的參數預設值不是你想的預設值

撰寫函式的時候, 幫參數加上預設值是個便利的作法, 使用函式時就不一定要傳上一大堆的參數, 不過在使用參數的預設值時你可能沒有注意到預設值產生的時間點, 導致實際叫用函式時傳入了奇怪的參數值, 執行後得到異常的結果。

參數預設值只會在定義函式時產生 1 次

舉個例子來說, 如果我想要設計一個幫我以 hh:mm:ss 格式顯示時間的函式, 大概會這樣設計:

>>> def print_time(t):
...     t_struct = time.localtime(t)
...     print(F'{t_struct[3]:02d}:{t_struct[4]:02d}:{t_struct[5]:02d}')
>>> 
Enter fullscreen mode Exit fullscreen mode

實際使用時就像是這樣:

>>> print_time(time.time())
22:09:19
>>> print_time(time.time())
22:09:22
>>> print_time(time.time())
22:09:24
>>>
Enter fullscreen mode Exit fullscreen mode

但是我發現使用這個函式時最常傳入的就是現在的時間, 因此就幫參數 t 加上預設值如下:

>>> def print_time(t = time.time()):
...     t_struct = time.localtime(t)
...     print(F'{t_struct[3]:02d}:{t_struct[4]:02d}:{t_struct[5]:02d}')
>>>
Enter fullscreen mode Exit fullscreen mode

執行看看:

>>> print_time(time.time())
22:05:44
>>> print_time()
22:05:33
>>> print_time()
22:05:33
>>> print_time()
22:05:33
>>>
Enter fullscreen mode Exit fullscreen mode

直接傳入參數沒問題, 但是使用參數預設值時怎麼怪怪的, 雖然是在傳入參數叫用後才執行, 但時間點怎麼會比較早?而且不管叫用幾次, 參數的預設值都不會變?

這主要的問題就是參數的預設值是在定義函式的時候產生, 而且只會產生 1 次, 因此上例中雖然參數的預設值是叫用 time.time() 的傳回值, 但這只會執行 1 次, 因此之後不論在什麼時候叫用函式, 參數的預設值都會一樣。以剛剛的例子來說, t 的預設值就是定義函式時叫用 time.time() 傳回的時間點, 之後叫用函式時都不會再重新叫用 time.time(), 因此 t 的預設值都是同一個時間。

檢查是否有傳入參數再計算預設值

如果你的參數預設值需要隨叫用的時間點而變化, 那麼最好改成在函式中檢查是否真的有傳入參數, 然後再計算該時間點的預設值, 例如:

>>> def print_time(t = None):
...     if t == None:
...         t = time.time()
...     t_struct = time.localtime(t)
...     print(F'{t_struct[3]:02d}:{t_struct[4]:02d}:{t_struct[5]:02d}')
>>>
Enter fullscreen mode Exit fullscreen mode

執行後就會正確了:

>>> print_time()
22:16:52
>>> print_time()
22:16:54
>>> print_time(time.time())
22:17:03
>>>
Enter fullscreen mode Exit fullscreen mode

小心使用可變的資料當預設值

由於參數的預設值是在定義函式時產生, 若是以可變的資料當成預設值, 而且會在函式內變更資料內容, 下一次叫用時的預設值就會是變更後的內容, 例如:

>>> def add_list(l = []):
...     print(l)
...     sum = 0;
...     for i in l:
...         sum += i
...     print(sum)
...     l.append(1)
>>>
Enter fullscreen mode Exit fullscreen mode

這個函式會把傳入的串列內容印出後加總, 並在傳入的串列尾端加入新的項目 1, 所以執行結果會是這樣:

>>> add_list([1, 2, 3])
[1, 2, 3]
6
>>>
Enter fullscreen mode Exit fullscreen mode

如果不傳入參數使用預設值, 就會是這樣:

>>> add_list()
[]
0
>>> add_list()
[1]
1
>>> add_list()
[1, 1]
2
>>> add_list()
[1, 1, 1]
3
>>>
Enter fullscreen mode Exit fullscreen mode

你會發現每次叫用得到的加總值都會增加 1, 這是因為作為預設值的串列會在每次叫用時在尾端新增項目 1, 所以下次叫用時加總值就會多 1。

如果你希望每次叫用時預設值都要是空的串列, 那就一樣可以比照前面的作法, 先檢查是否有傳入參數, 再設定參數的預設值。

小結

Python 雖然有許多便利的語法, 但是如果沒有注意相關的細節, 往往會在遇到異常結果時不知所措, 但只要瞭解實際的運作方式, 就可以用正確的方式撰寫程式。

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (0)

Image of AssemblyAI

Automatic Speech Recognition with AssemblyAI

Experience near-human accuracy, low-latency performance, and advanced Speech AI capabilities with AssemblyAI's Speech-to-Text API. Sign up today and get $50 in API credit. No credit card required.

Try the API

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay