進階方法

      在〈進階方法〉中有 1 則留言

KPI

首先, 聊一下一件以前曾發生在本人身上的真實故事.
本人曾在仁寶擔任主管階級的工作. 在一次不景氣中, 公司沒事作, 就開始推動一些五四三的檢討計畫(Review). 有一項目是如何考核程式設計員的KPI(績效). 最終的結論是, 要我們主管階級統計底下屬員所撰寫的程式行數, 作為考核的依據.
仁寶其實也不是什麼高科技公司, 充其量只是系統組裝廠而以. 但在台灣算是個組織龐大的公司, 而這種大公司竟使用代碼行數當作KPI, 不僅是個國際大笑話, 更顯示台灣資訊產業的落後.
程式是抽象的, 不是硬体作愈多愈好. 所以不能將製造業的管理方式套用在軟体產業.

程式碼不是越多越好, 而是越少越好. 程式碼也不是越複雜越好, 而是越簡單越好. 在Python中就有許多好用的進階功能, 能用一行完成的, 絕不寫二行.

本篇最主要目的, 在講解List的簡化使用方式

Slice

假設有一List, 裏面有五個元件, 如果要印出第1~3個元素, 就可以使用切片的方式, 如下

L=['Mercury','Venus','Earth','Mars','Jupiter']
print(L[1:4])
結果:
['Venus', 'Earth', 'Mars']

1. 第一個參數為開始截取位置(索引編號由0開始), 第二個參數是結束位置, 但不包含結束位置
2. 如果第一個參數為0, 則可以省略. 如  L[:4]
3. 支援倒數方式. 注意一下, 倒數最後一個元素的索引為 -1

print(L[:-3]) 結果為 ['Mercury', 'Venus']
print(L[-3:]) 結果為 ['Earth', 'Mars', 'Jupiter']
print(L[-3:-1]) 結果為 ['Earth', 'Mars']

4. 前4個, 每二個取一次

print(L[:4:2])
結果 : ['Mercury', 'Earth']

插入

如下所示,可以在最後加入[7,8,9], 最前加入[-3,-2,-1]

在 1及2之間加入 “a”,”b”,”c”, 但因為1會被取代, 所以1必需再寫一次

l=[1,2,3,4,5,6]
l[len(l):]=[7,8,9]
l[:0]=[-3,-2,-1]
l[0:1]=[1,'a','b','c']
print(l)

字串

可以把字串當成List來看待, 所以用法同上, 傳回值亦為字串

s="ABCDEFGHIJK"
print(s[:5])
結果 : ABCDE

Tuple

Tuple為內容不可變的List,  用法也跟上述相同, 傳回值亦為Tuple

迭(ㄉˊ)代器(Iterator)

迭代器, 遍訪集合的一個指標, 但這裏指的是 foreach. 而Python的for 其實只有foreach的功能而以. 比如下面代碼

str="ABCDEFGHIJK"
for s in str:
    print(s, end='')

dict, list, tuple, set, 都可以使用Iterator進行遍訪, 如下所示. 在dict中, 預設取得key的值, 可使用d.values()取得值, d.items()取得key及value

d={'a':1, 'b':2, 'c':3,'d':4,'e':5}
for k in d:
    print(k, end='')
print()
for v in d.values():
    print(v, end='')
print()
for k,v in d.items():
    print(k,":",v)

可迭代的類型

那些類型可以使用for in呢, 必需使用isinstance判斷. 但使用isinstance之前, 需import Iterable類別. 而import在3.7及3.8的版本有點不一樣

3.7 : from collections import Iterable
3.8 : from collections.abc import Iterable

from collections.abc import Iterable
l=['Mercury','Venus','Earth','Mars','Jupiter']
print(isinstance(l, Iterable))

enumerate

enumerate可將list 索引化, 如下

l=['Mercury','Venus','Earth','Mars','Jupiter']
for i, v in enumerate(l):
    print(i, v)
結果 :
0 Mercury
1 Venus
2 Earth
3 Mars
4 Jupiter

list()函數

如果要產生 l=[1,2,3,4,5,6,7,8,9,10] 的list, 一個一個key in就有點笨了. 如果要1~100呢?
此時可以使用如下方式
l=list(range(1,11))

加入for

如果要產生的是 [1*1, 2*2, 3*3, 4*4]的list, 可使用如下

k=[x*x for x in range(1,11)]
for x in k:
    print(x);

加入if

上述寫法, 還可以變型加上 if 判斷式, 如下所示

[x * x for x in range(1, 11) if x % 2 == 0]
結果 : [4, 16, 36, 64, 100]

雙層 for

k=[m+n for m in "ABC" for n in "123"]
for x in k:
    print(x);
結果 : A1  A2  A3  B1  B2  B3  C1  C2  C3

轉成小寫

x = ['Car', 'Airplan', 'Bicycle', 'Scooter']
l=[s.lower() for s in x]
for s in l:
    print(s);
結果 : car
       airplan
       bicycle
       scooter

產生器(Generator)

先看一下下面的代碼

l=[x for x in range(1,1000001)]

這個 l 為一個包含了1,000,000個元素的List, 這還得了, 電腦裏再多的記憶体就這一行全被吃光光了。
但有時就是需要這麼大的List時, 該怎麼辦呢? 那就要另外想辦法了, 目前最有效的方式就是產生器(Generator)

g=(x for x in range(1,1000001))
print(next(g))
for x in g:
    print(x)

這個g, 並不是一下子就產生出1,000,000個元素,  而是根本就不會產出. 直到next, 或使用foreach時,經過計算時才會產生. 這種只有在真正需要結果元素時, 才會進行計算取值, 稱為惰性求值(Lazy evaluation)
如果一直使用next(), 到最後會產生StopIteration的錯誤. 所以大致上我們是不使用next的.

Generator所保存的, 只是演算法而以. 他的功能是非常強大的. 先看一下底下的費波那西數(Fibonacci), 除了前二個是0及1之外, 下一個數是前二個數的加總, 如下
0 1 1 2 3 5 8 13 21……

若以程式碼表示, 可寫成如下

def fib(max):
    i, a, b = 0, 0, 1
    while i < max:
        print(b)
        a, b = b, a + b
        i+=1
    return 'done'
fib(10)

比較詭異的寫法是 a, b = b, a+b 這行, 此行相當於如下代碼

t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

yield

在上述的函數中, 如果把print(b) 改成 yield b, 則此函數就會變成了產生器, 如下

def fib(max):
    i, a, b = 0, 0, 1
    while i < max:
        yield b
        a, b = b, a + b
        i+=1
    return 'done'
f=fib(100)
for x in f:
    print(x)

初學者一開始會覺的 yield是什麼鬼東西. 照字面解釋, yield是退讓的意思.
如果是一般的函數裏, 在迴圈裏會重複一直跑, 直到迴圈跑完了, 再返回調用端.
但yield則不一樣, 迴圈跑第一圈遇到yield後, 就退讓不再執行. 直到下一次 next(), 才會再由 yield的下一行繼續執行, 然後再次回到 yield後又退出. 

讓我們再看看下面的代碼

def odd():
    print('第一次')
    yield 1
    print('第二次')
    yield(3)
    print('第三次')
    yield(5)
for x in odd:
    print(x)
結果 : 
第一次
1
第二次
3
第三次
5

但如果寫成下面的方式, 就會沒完沒了喔

def odd():
    i=1;
    while(True):
        yield i
        i+=1
for x in odd():
    print(x)

返回值

上面的 return ‘done’, 要如何取得呢? 此時就必需讓他發生StopIteration才行. 所以要用 try..except來捕獲例外. 什麼是例外呢, 這是另一個story, 下回分曉.

g = fib(6)
while True:
    try:
        x = next(g)
        print('g:', x)
    except StopIteration as e:
        print('Generator return value:', e.value)
        break

1 thought on “進階方法

  1. Guadalupe Been

    Your blog articles spark a brilliance that illuminates my day. Thank you for that!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *