iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 19
3

路遙知碼力,日久練成精-只要在程式之路鑽研的夠深,便能夠充分發揮程式碼的力量; 練習的日子夠久,便能夠練成寫出精簡代碼的能力。

大家好,我是心原一馬,內心原來一心喜歡打程式碼。
至今為止我們認識了許多python的內建函數,
你對於自定義函數了解多少呢?
讓小馬來一一解析吧。

不過還是要先討論一下昨天的課後習題,
有沒有發現小馬改用「討論」一詞,
不再說「公佈」昨天課後練習解答呢?
隨著課後練習難度上升,
做法也許有很多種,大家也可能會有自己的想法能夠解開問題,
大家也歡迎於留言區討論。
(還沒看過題目的朋友歡迎點昨日題目傳送門)

參考解法一: 向左旋轉90度即是向右旋轉90度做三次

首先昨天有教過如何將一個矩陣右旋轉90度:

def rotateRight(arr):
    A = arr[::-1] #利用切片上下翻轉
    return list(map(list,(zip(*A)))) #行列互換,再利用map函數將zip內的元組轉列表

而例題要我們向左旋轉90度。
或許你不知道直接的做法,
但或許你日常生活經驗會知道,
把一張照片向右旋轉三次即得到向左旋轉一次的效果,
因此可以這樣寫:

def rotateLeft(arr):
    for i in range(3):
        arr = list(map(list,(zip(*arr[::-1]))))
    return arr

參考解法二: 先水平翻轉,再行列互換

但也許我們不是很滿意參考解法一,
因為真的這樣做的話,
向左旋轉豈不比向右旋轉還慢三倍嗎?
有沒有更直接的方法呢?
這邊我們觀察到,
首先先把圖片水平翻轉,再做行列互換,
即可達到向左旋轉的效果的,
給個示意圖:

123 (水平翻轉)  321  (行列互換)    369
456     =>     654      =>       258
789            987               147

程式如下:

def rotateLeft(arr):
    A = [a[::-1] for a in arr] #水平翻轉
    return list(map(list,(zip(*A)))) #行列互換,再利用map函數將zip內的元組轉列表

自定義函數的參數

回到本日主題,你知道幾種python函數的定義方式呢?

位置參數(最常見的定義)

這是平時我們最常見的定義方式,
例如我們很普通的寫一個計算x乘以y的函數:

def mul(x,y):
    return x*y

那麼x,y便稱為「位置參數」,
因為當你調用函數時,
傳入的兩個值會按照順序賦值給x,y。
例如:

>>> mul(3, 4)
12

調用mul(3, 4)時,表示x=3, y=4。

默認參數

默認參數可以簡化我們調用函數,
比如說假設我們的程式需要常常計算x*2的值好了,
只有少數特例才會需要計算其它x*y的值,
我們需要每次都調用mul(x, 2)就顯得有點麻煩,
這時便可以將函數改寫成這樣:

def mul(x, y=2):
    return x*y

表示y這個參數默認為2,
未來調用函數時,
不論是傳入一個參數或兩個參數都可以囉。

>>> mul(5)
10
>>> mul(5,3)
15

注意同時用位置參數及默認參數時,
默認參數一定要放在位置參數之後。
我們也可以有多個默認參數,例如:

def mulThenPlus(x, y=2, z=1):
    return x*y+z

如果我們只想傳入xz這兩個參數維持默認y=2怎麼辦?
這時我們就可以給參數名字來傳值:

print(mulThenPlus(2,z=6))

結果為10(2*2+6的結果)。

可變參數 (單星語法)

我們來思考個問題,
如果我們想要定義一個函數,
計算兩個數的乘積,
你會怎麼寫呢?
很簡單,相信你很容易便寫的出來:

def mul2(x, y):
    return x*y

好,那如果你想定義一個函數,
計算三個數的乘積,
你可能會再多加一個參數進去,

def mul3(x, y, z):
    return x*y*z

那如果我想計算十個數的乘積呢?
你總不可能無限制的一直把參數寫下去。
還好python 裡面用個好用的特性,
可以接收任意數量的參數(甚至是零個參數),
小馬稱之為「單星語法」,
(補充說明:
若你從前學過C/C++,
可能會對這樣的語法困惑半天,
覺得python裡竟然也有*,是指標嗎?
事實上python的*跟指標是沒關係的哦)

舉例來說:

def test(*args):
    for a in args:
        print(a)

test(1,'A',3)

結果印出:
1
A
3

在參數的定義前面加一顆*
即可把傳入的參數打包成一個元組(tuple)傳進函數中。
因此,實作接受任意參數數量的數字乘積則可以這樣改寫:

def mul(*args):
    result = 1
    for a in args:
        result *= a
    return result

實際測試調用函數看看:

>>> mul(5,6,3)
90

那如果你原本的資料是個列表或元組怎麼辦呢?
譬如:

nums = [5,6,3]

想要把nums裡的數字當做參數傳進函數裡。

我們在昨天教zip函數時其實已經示範過一次了,
方法還是在列表或元組前面加一顆*
它會把nums裡的數拆解開參做參數5, 6, 3傳進函數中。
範例如下:

>>> nums = [5,6,3]
>>> mul(*nums)
90

關鍵字參數 (雙星語法)

相較於「單星語法」是可以傳入任意數量不具名字的參數,
「雙星語法」便是可以傳入任意數量帶有名字的參數了,
在參數的定義前面加兩顆*
即可把傳入的參數打包成一個字典(dict)傳進函數中。
舉例如下:


def test(**kargs):
    for n in kargs:
        print(f'kargs: {n} -> {kargs[n]}')

# 底下測試結果
D = {'apple': '蘋果', 'banana': '香蕉'}
test(**D) #若要直接傳入字典,也是用兩個*

結果會印出
kargs: apple -> 蘋果
kargs: banana -> 香蕉

亦可直接打關鍵字傳參數:

test(apple='蘋果', banana='香蕉')

結果一樣可以印出
kargs: apple -> 蘋果
kargs: banana -> 香蕉

混用的參數順序?

好了,介紹完python的自定義函數參數了,
python在自定義函數是非常自由的,
上述提到的這些參數都是可以混著使用的,
但是在定義參數上,
必須遵守以下的順序:
必選參數(位置參數)、默認參數、可變參數、關鍵字參數
舉例來說:

def test(a, b, c=0, *args, **kargs):
    print(a)
    print(b)
    print("c=", c)
    for a in args:
        print(a)
    for n in kargs:
        print(f'kargs: {n} -> {kargs[n]}')


test(10,20,30,40,apple='蘋果', banana='香蕉')

會印出
10
20
c= 30
40
kargs: apple -> 蘋果
kargs: banana -> 香蕉
雖然python在函數定義上如此自由,
但其實是不建議全部一起混著用啦,
這樣會讓函數變的不是那麼好理解。
那麼今天就先學到這裡啦。

參考資料

  1. Python雙星號vs單星號用法
  2. Python 的可變參數

進階課後學習(初學者建議直接略過)

由於這一個寫法可能沒是像前述函數參數定義那麼常見,
但不失為一個python經典例子,
放在課後學習供有興趣的人看。
想像我們現在要定義多個功能相似的函數,
例如函數1用來計算1的倍數,
函數2用來計算2的倍數,…
函數5用來計算5的倍數。

這時可以怎麼做呢?
一個純樸的方式就是乖乖的寫五次函數定義,例如:

def func1(x):
    return x
def func2(x):
    return x*2
def func3(x):
    return x*3
def func4(x):
    return x*4
def func5(x):
    return x*5

於是我們便定義了五個函數,
但是這樣重複的程式實在很多又不簡潔,
於是有人會想到說,我們不是在Day14有學過叫匿名函數的東西嗎?
於是他用匿名函數lambda加上列表生成式的效果,
這不就生成了五個函數嗎?如下:

mulFuncs = [lambda x: x*i for i in range(1,6)]

那麼問題來了,
你覺得下面這段程式碼的執行結果會印出什麼?
(你可以試著真的執行程式看看,可能會讓你驚訝)

mulFuncs = [lambda x: x*i for i in range(1,6)]
for func in mulFuncs:
    print(func(2))

歡迎於留言區討論想法。


上一篇
Day18 - 當我們「鏈」在一起,認識zip()函數
下一篇
Day20- 可變變數的災難,太自由如脫疆野馬?
系列文
活用python- 路遙知碼力,日久練成精30

1 則留言

0
Austin
iT邦新手 5 級 ‧ 2019-09-24 18:08:35

感覺結尾問題跟js的閉包超像的XD

心原一馬 iT邦研究生 5 級 ‧ 2019-09-24 19:39:48 檢舉

哈哈~ 其實小馬對js沒有像python那麼熟悉,不過原來js也有類似的問題啊?好酷~

我要留言

立即登入留言