Advanced Materials of Python
Table of Contents
1. Function II
1.1. Function的特性
1.1.1. Fnction為物件
1: def yell(text): 2: return text.upper() + '!' 3: 4: print(yell('hello')) 5: bark = yell # assign給其他變數 6: print(bark('woof'))
HELLO! WOOF!
1.1.2. Function可被放在資料結構中
1: def yell(text): 2: return text.upper() + '!' 3: 4: bark = yell 5: 6: funcs = [bark, str.lower, str.capitalize] 7: 8: for func in funcs: 9: print(func('hey there!'))
HEY THERE!! hey there! Hey there!
1.1.3. Function可做為參數傳給其他function
Function是物件,所以除了能指定給變數,更可以當成其他function的參數。
1: def yell(text): 2: return text.upper() + '!' 3: 4: bark = yell 5: 6: def greet(func): 7: greeting = func('Hi, I am a Python Program') 8: print(greeting) 9: 10: greet(bark)
HI, I AM A PYTHON PROGRAM!
這種能接續其他function做為參數的格式也稱為高階function(higher-order function)。Python內建的map function即為higher-order的經典範例,它接受一個function object與一個可走訪物件(iterable)做為參數,然後會套用該function到iterable object內的每一元素,產出一系列結果:
1: list = map(function, iterable object)
例如:
1: def yellp(text): 2: return text.upper() + '!' 3: 4: bark = yell 5: 6: list1 = ['hello', 'hey', 'hi'] 7: print(list(map(bark, list1)))
1.1.4. Function可構成巢狀結構
在function內定義function,稱之為巢狀function(nested function)或內部function(inner function)
1: def speak(text): 2: def whisper(t): 3: return t.lower() + '...' 4: return whisper(text) 5: 6: print(speak('Hello, world'))
hello, world...
每次呼叫speak時,它會先定義內部function,而這個inner function在speak外並不存在。若一定要在speak之外存取內部function whisper,則要先把內部function傳回給父function的呼叫者:
1: def get_speak_func(volume): 2: def whisper(text): 3: return text.lower() + '...' 4: def yell(text): 5: return text.upper() + '!' 6: if volume > 0.5: 7: return yell 8: else: 9: return whisper 10: 11: print(get_speak_func(0.3)) 12: print(get_speak_func(0.7))
<function get_speak_func.<locals>.whisper at 0x101986af0> <function get_speak_func.<locals>.yell at 0x101986a60>
1.1.5. Inner function可記住父function的參數狀態
Function還可「捕捉並保留父function的部份狀態」:
1: def get_speak_func(text, volume): 2: def whisper(): 3: return text.lower() + '...' 4: def yell(): 5: return text.upper() + '!' 6: if volume > 0.5: 7: return yell 8: else: 9: return whisper 10: 11: func = get_speak_func('Hello, world', 0.7) 12: print(func())
HELLO, WORLD!
這種會捕捉並記住外部參數的function,稱之為詞法閉包(lexical closuer),或簡稱閉包(closure)。閉包會記住外圍程式範圍裡的變數值,即便其程式流程已經離開該變數所在的範圍也一樣。
1.1.6. 物件也能像function一樣被呼叫
在Python中,function皆為object,反之object不見得是function,不過,我們能讓不是function的object變成可呼叫(callable),許多情況下,甚至可以把callable object當成function,在後面加上小括號來呼叫它,甚至能傳入參數。要使object變為callable,方法是在類別加入__call__:
1: class Addr: 2: def __init__(self, n): 3: self.n = n 4: def __call__(self, x): 5: return self.n + x 6: 7: plus_3 = Addr(3) #建立Addr物件,屬性n=3 8: print(plus_3(4))
7
1.2. lambda
lambda提供了可用來宣告小型匿㢱function的快速宣告方式:
函式名稱 = lambda 參數:運算式
這種語法也稱為function expression,這與用def宣告的function一樣
1: add = lambda x, y : x + y 2: 3: print(add(5, 3))
8
那麼,以lambda定義function的優勢為何?
1: print((lambda x, y : x + y)(5, 3))
8
在許多場合中,使用lambda來定義臨時性的function就比較方便,但labmda function有個語法上的限制:只能含有一條運算式。
lambda function使用時機: 任何需要快速用上function object的地方,例如sorted():
1: sorted(iterable object, key=function)
其中參數key可輸入一個function, sorted()會依據function的傳回值來排序物件的元素,如果忽略key參數,則會依據原始元素(或者元素的第一個值)來排序。
1: tuples_list = [(1, 'd'), (2, 'b'), (4, 'a'), (3, 'c')] 2: print(sorted(tuples_list)) 3: 4: print(sorted(tuples_list, key=lambda x: x[1])) #指定key以tuple元素的第二子元素來排序 5: print(sorted(range(-5, 6), key=lambda x: x**2)) #指定依平方值來排序 6: # 上述方式只是為展示lambda用法,更精簡的方式為: 7: print(sorted(range(-5, 6), key=abs))
[(1, 'd'), (2, 'b'), (3, 'c'), (4, 'a')] [(4, 'a'), (2, 'b'), (3, 'c'), (1, 'd')] [0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5] [0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5]
使用lambda的缺點:不易閱讀
1.3. decorator
Python的修飾器(decorator)可用來間接修改callable object(function, method, class)的行為,但不直接改動object本身,其用途包括:
- 加入日誌(logging)
- 存取權限管控與身份驗證
- 加入監測function及衡量執行時間
- 限制function執行頻率
- 用快取暫存function的執行結果
1.3.1. 修飾無參數function
1: def uppercase(func): 2: def wrapper(): 3: original_result = func() 4: modified_result = original_result.upper() 5: return modified_result 6: return wrapper 7: 8: @uppercase #以uppercase修飾greet 9: def greet(): 10: return 'Hello' 11: 12: print(greet())
HELLO
1.3.2. 多重修飾
1: def strong(func): 2: def wrapper(): 3: return f'<strong> { func() } </strong>' 4: return wrapper 5: 6: def emphasis(func): 7: def wrapper(): 8: return f'<em> { func() } </em>' 9: return wrapper 10: 11: @strong 12: @emphasis 13: def greet(): 14: return 'Hello!' 15: 16: print(greet) 17: print(greet())
<function strong.<locals>.wrapper at 0x10fd3ab80> <strong> <em> Hello! </em> </strong>
唯一要注意的是:若套用修飾器的層級過多會影響程式執行效能。
1.3.3. 修飾有傳入參數的function
若要修飾的function有帶參數,則wrapper()要改為wrapper(*args, **kwargs),這分別用來收集所有傳入的positional與keyword類參數,以tuple及dict形式儲存在arfs與kwargs變數裡,然後wrapper利用*與**將收集到的參數unpack後傳給原始func,如此就不會限制到參數個數。
1: def trace(func): 2: def wrapper(*args, **kwargs): 3: print(f'trace: callable function {func.__name__}, with arguments: {args}, {kwargs}') 4: original_results = func(*args, **kwargs) 5: print(f'trace: function {func.__name__} reutrn results: {original_results}!') 6: return original_results 7: return wrapper 8: 9: @trace 10: def say(name, line): 11: return f'{name}, {line}' 12: 13: print(say('James', 'hi'))
trace: callable function say, with arguments: ('James', 'hi'), {} trace: function say reutrn results: James, hi! James, hi
幫function加上效能監測功能
1: import time 2: def eval_time(func): 3: def wrapper(*args, **kwargs): 4: start = time.perf_counter() 5: res = func(*args, **kwargs) 6: finish = time.perf_counter() 7: print(f'Finished in {round(finish-start, 2)} second(s)') 8: return res 9: return wrapper 10: 11: @eval_time 12: def sigma(n): 13: sum = 0 14: for i in range(n): 15: sum = sum + i 16: return sum 17: 18: print(sigma(100000000))
Finished in 5.3 second(s) 4999999950000000
1.4. *args and **kwargs
*與**能讓function接受數量不定的額外參數,讓module與class能提供更有彈性的存取介面:
1: def foo(required, *args, **kwargs): 2: print(required) 3: if args: 4: print(f'args: {args}') 5: if kwargs: 6: print(f'kwargs: {kwargs}') 7: 8: foo('1. Hi') 9: foo('2. Hi', 1, 2, 3) 10: foo('3. Hi', 1, 2, 3, Key1='value1', key2=456)
1. Hi 2. Hi args: (1, 2, 3) 3. Hi args: (1, 2, 3) kwargs: {'Key1': 'value1', 'key2': 456}
- *args接收額外的位置型參數(positional paramenters)(沒有鍵的參數),將之放入一個tuple。
- **kwargs接收額外的關鍵字參數(keyword parameters)(有鍵的參數),將之放入一個dict。
1.5. Unpack function parameters
*與**也可用來unpack function的args與kwargs,在呼叫function時,若在iterable object前加上*,就會unpack這個物件,將元素當成個別的positional parameters傳入參數;若加上**,則可unpack keyword parameters。
1: def print_vector(x, y, z): 2: print(f'<{x}, {y}, {z}>') 3: 4: tuple_vector = (1, 0, 1) 5: print_vector(tuple_vector[0], tuple_vector[1], tuple_vector[2]) 6: print_vector(*tuple_vector) 7: 8: dict_vector = { 'y': 4, 'z': 5, 'x': 3} 9: print_vector(**dict_vector)
<1, 0, 1> <1, 0, 1> <3, 4, 5>
1.6. Return or not
- return 與return None效果一樣
- 如果沒有傳回值,則可省略return,例如,一個只負責print的function並無任值可傳回,寫return會很奇怪。
2. Class v.s. Object
2.1. TODO 什麼是class? 什麼是object?
把這個看完: https://youtu.be/JeznW_7DlB0:
Everything in Python is object.
1: a = 2022 2: b = 'TNFSH' 3: 4: def c(): 5: print('TNFSH') 6: 7: print(type(a)) 8: print(type(b)) 9: print(type(c))
<class 'int'> <class 'str'> <class 'function'>
由上面的例子,我們可以發現:在Python裡,幾乎所有我們用到的變數、函數都是class。
當我們寫下
1: a = 2022
其實是在說,我們有一個變數,它是一種int class,它的值是2022。這個變數就是一個object: 某種屬於某一類class的實體。
一種常見的說法是:class是一張藍圖,依照這張藍圖蓋出的房子有同樣的架構,有同樣的功能,但是可能在外觀或顏色上會有些許差異,就好比一樣是整數物件,每個變數的值可能會有所不同。
不同的class有其特定的功能與限制,例如:
1: x = 1 2: y = 'TNFSH' 3: print(x+y)
執行上述程式會得到以下錯誤訊息:
File "<stdin>", line 3, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str'
這裡告訴我們:int與str這兩種class的object不支援+這個運算。我們再來看另一個例子:
1: x = 'tnfsh' 2: y = 1 3: print(x.upper()) 4: print(y.upper())
TNFSH
上述程式會產生如下錯誤訊息:
#+begin_src shell -r -n :results output :exports both
Traceback (most recent call last):
File “<stdin>”, line 4, in <module>
AttributeError: ’int’ object has no attribute ’upper’
#+end_src<<sh
意思是:int這種class的object沒有一種叫做upper的屬性。這就是我們上面所說的,不同的class有其特定的功能與限制。
2.2. 建立自己的class
1: class Dog: 2: def bark(self): 3: print('汪汪汪') 4: 5: a = Dog() 6: print(type(a)) 7: a.bark()
<class '__main__.Dog'> 汪汪汪
在上述範例中,我們建立了一個叫Dog的class,然後再建立一個屬於Dog這個class的object。
從這個object的type,可以看出這是一個叫<class ’main.Dog’>的類別,這裡的__main__意思是這個class被定義(或撰寫)在目前這支Python程式中,當然我們也可以另外開一個新的.py檔專問來儲存這個class。
2.3. == v.s. is
- ==: equal
- is: identical
1: a = [1, 2, 3] 2: b = [1, 2, 3] 3: c = a 4: print(a == b) 5: print(a is b) 6: print(a == c) 7: print(a is c)
True False True True
2.4. Shallow copy v.s. Deep copy
簡單來說,淺與深的區別1:
- 淺複製僅複製容器中元素的地址
- 深複製完全複製了一份副本,容器與容器中的元素地址都不一樣
2.4.1. shallow copy
1: xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 2: ys = list(xs) # shallow copy 3: print(xs == ys) # 二者內容相同 4: print(xs is ys) # 但為不同物件 5: 6: xs.append([10, 11, 12]) 7: print(xs) 8: print(ys) 9: 10: # 但若是修改二者相同的部份,如 11: xs[1][0] = 'X' 12: print(xs) 13: print(ys)
True False [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]] [[1, 2, 3], [4, 5, 6], [7, 8, 9]] [[1, 2, 3], ['X', 5, 6], [7, 8, 9], [10, 11, 12]] [[1, 2, 3], ['X', 5, 6], [7, 8, 9]]
由上述例子中可發現,xs與ys其實共享同一份資料。
2.4.2. deep copy
1: import copy 2: xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 3: zs = copy.deepcopy(xs) 4: 5: print(xs == zs) 6: print(xs is zs) 7: 8: xs[1][0] = 'X' 9: print(xs) 10: print(zs)
True False [[1, 2, 3], ['X', 5, 6], [7, 8, 9]] [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
2.4.3. copy any object
不管想copy什麼object,包括自訂物件,解決之道亦是用copy module的copy()與deepcopy()。
1: import copy 2: 3: class Point: 4: def __init__(self, x, y): 5: self.x = x 6: self.y = y 7: def __repr__(self): 8: return f'Point({self.x}, {self.y})' 9: 10: a = Point(23, 42) 11: b = Point(23, 42) 12: #b = copy.copy(a) 13: print(a) 14: print(b) 15: print(a == b) 16: print(a is b)
Point(23, 42) Point(23, 42) False False
2.5. ABC: Abstract Base Class
C++、Java等語言都有「介面」(interface)這種本身無法建立object、卻能當成建立object template的機制。Python則可藉由ABC來實作。藉由ABC來建立class,便是可確保繼承之sub-class均有正確的特定method。
1: from abc import ABC, abstractmethod 2: 3: class Base(ABC): 4: 5: @abstractmethod 6: def foo(self): 7: pass 8: 9: @abstractmethod 10: def bar(self): 11: pass 12: 13: class Concrete(Base): 14: def foo(self): 15: pass 16: 17: print(issubclass(Concrete, Base))
True
2.6. object/class/static method
1: class MyClass: 2: 3: def objmethod(self): #object method 4: print(f'呼叫object method: {self}') 5: 6: @classmethod 7: def clsmethod(cls): 8: print(f'呼叫class method: {cls}') 9: 10: @staticmethod 11: def stcmethod(): 12: print(f'呼叫static method') 13: 14: obj = MyClass() 15: obj.objmethod() 16: obj.clsmethod() 17: obj.stcmethod()
呼叫object method: <__main__.MyClass object at 0x1081f7940> 呼叫class method: <class '__main__.MyClass'> 呼叫static method
- objmethod為一般object method,必須有self參數,該參數指向object本身
- csmethod為class method,必項有cls參數,指向class本身
- stcmethod為static method,沒有參數,無法修改物件或類別狀態,只能處理user傳入的參數
2.6.1. DEMO
1: class Drink: 2: def __init__(self, name, price, counts): 3: self.name = name 4: self.price = price 5: self.counts = counts 6: 7: def costs(self): 8: return self.drink_costs(self.price, self.counts) 9: 10: @classmethod 11: def blacktea(cls): 12: return cls('紅茶', 1, 25) 13: 14: @classmethod 15: def coffee(cls): 16: return cls('熱美式', 1, 50) 17: 18: @staticmethod 19: def drink_costs(price, counts): 20: return price * counts 21: 22: drink1 = Drink.blacktea() 23: drink2 = Drink.coffee() 24: drink3 = Drink('義式', 60, 3) 25: print(f'{drink1.name}') 26: print(f'{drink2.price}') 27: print(f'{drink3.name!r} * {drink3.counts} = {drink3.costs()}')
紅茶 1 '義式' * 3 = 180
2.7. class variable v.s. instance variable
- class variable: 宣告在classs定義裡,但宣告位置在所有method之外
- instance variable: 屬於由class所建立的某個instance,其內容並非存在class中,而是由各別instance負責
2.7.1. 差異
1: class Dog: 2: num_dogs = 0 #Demo class variable的適用時機 3: num_leg = 4 4: 5: def __init__(self, name): 6: self.name = name 7: self.__class__.num_dogs += 1 8: 9: dd = Dog('DaiDai') 10: bd = Dog('Bad luck') 11: lk = Dog('Lucky') 12: bd.num_leg = 3 13: print(dd.num_leg) 14: print(bd.num_leg) 15: print(f'目前共有{Dog.num_dogs}隻狗')
4 3 目前共有3隻狗
由class variable num_dogs的示範也可看出,善用class variable可以在各個instance object間取得某程程度的連繫。
3. 資料型別 II
3.1. Dict
3.1.1. collections.ChainMap
collections.ChainMap可以把多個dict集結串成單一個dict,如
1: import collections 2: 3: dict1 = {'one':1, 'two': 2} 4: dict2 = {'two':'貳', 'three': 'III', 'four': 4} 5: chain = collections.ChainMap(dict1, dict2) 6: 7: print(chain) 8: print(chain['two']) 9: chain['five'] = 5 10: print(chain)
ChainMap({'one': 1, 'two': 2}, {'two': '貳', 'three': 'III', 'four': 4}) 2 ChainMap({'one': 1, 'two': 2, 'five': 5}, {'two': '貳', 'three': 'III', 'four': 4})
要留意的是:若對ChainMap做新增、修改、刪除,則只會影響裡面的第一個dict。
3.2. types.MappingProxyType
透過MappingProxyType class可以把dict變為唯讀。
1: from types import MappingProxyType 2: 3: writable = {'one': 1, 'two': 2} 4: writable['three'] = 3 5: readOnly = MappingProxyType(writable) 6: readOnly['three'] = 4 #TypeError: 'mappingproxy' object does not support item assignment 7: readOnly['four'] = 4 #TypeError: 'mappingproxy' object does not support item assignment
3.3. array.array
若要和C語言程式交換資料,或是只需要一個儲存數值型態資料的空間,則可用array.array,優點是比list或tuple省空間。array.array的method與list大致相同,許多情況下甚至可直接交換二者的資料型別而無需修改相關程式碼。
1: import array 2: arr = array.array('f', (1.0, 3.5, 2.0, 6.1)) 3: print(arr) 4: print(arr[1]) 5: arr.append(2.1) 6: del arr[1] 7: print(arr)
array('f', [1.0, 3.5, 2.0, 6.099999904632568]) 3.5 array('f', [1.0, 2.0, 6.099999904632568, 2.0999999046325684])
3.4. Record
同樣以儲存car record(color, mileage, automatic)來比較
3.4.1. list/tuple
- 建立速度快、執行效率高,
- 但沒有欄位名稱,建立資料時可能弄錯順序
- 很難檢查兩個資料物件是否有一致的欄位
1: car1 = ['red', 3812, True] #list, 可變 2: car2 = ('blue', 3123, False) #tuple, 不可變 3: car3 = (343, 'black', True, 'James') #順序錯誤、欄位也不一致
3.4.2. dict
- 可透過key/value來當成欄位名稱
- 仍缺乏欄位監控機制,防止使用者在建立object時打錯或漏打欄位
1: Car = { 2: 'color': 'red', 3: 'mileage': 3213, 4: 'automatic': True 5: } 6: print(Car) 7: print(Car['color']) 8: Car['windshield'] = 'broken' 9: print(Car)
{'color': 'red', 'mileage': 3213, 'automatic': True} red {'color': 'red', 'mileage': 3213, 'automatic': True, 'windshield': 'broken'}
3.4.3. 自訂類別
- 麻煩,在要__init__建構子內設定每個欄位,還要自己寫__repr__ method
1: class Car: 2: def __init__(self, color, mileage): 3: self.color = color 4: self.mileage = mileage 5: 6: car = Car('red', 1234) 7: car.mileage = 4343 #可修改欄位內容 8: car.automatic = True #可新增欄位
3.4.4. typing.NamedTuple
- readonly
- 欄位型別無強制性
1: from typing import NamedTuple 2: 3: class Car(NamedTuple): 4: color: str 5: mileage: float 6: automatic: bool 7: 8: car = Car('red', 3123, True) 9: print(car) 10: #car.mileage = 3123 # 不能變更欄位內容 11: #car.windshield = 'broken' #不能新增欄位 12: car2 = Car('blue', 'test', 1234) 13: print(car2)
Car(color='red', mileage=3123, automatic=True) Car(color='blue', mileage='test', automatic=1234)
3.4.5. types.SimpleNamespace
- 本質上是dict,把dict的key變成class attribute
- 可以新增、修改、刪除attribute
- 不若想動用到class,此為簡易替代品
- 仍缺乏結構
1: from types import SimpleNamespace 2: 3: car = SimpleNamespace(color = 'red', 4: mileage = 1341, 5: automatic = True) 6: 7: car.color = 'blue' 8: print(car)
namespace(color='blue', mileage=1341, automatic=True)
3.4.6. dataclass (Python 3.7+)
- 能自動建立__init__、__repr__等method
- 可加入自訂method
- 可繼承
- 可設為read only(在@dataclass後加入參數 frozen=True)
1: from dataclasses import dataclass 2: 3: @dataclass 4: class Car: 5: color: str 6: mileage: float 7: automatic: bool 8: 9: def mileage_km(self): #可自訂method 10: return self.mileage * 1.609 11: 12: car = Car('red', 3123, True) 13: print(dir(car)) 14: print(car.mileage_km) 15: print(car.__repr__) 16: 17: @dataclass 18: class ElectronicCar(Car): #繼承 19: charge: float = 0.0 20: 21: car2 = ElectronicCar('white', 3123, True, 1000) 22: print(car2) 23:
['__annotations__', '__class__', '__dataclass_fields__', '__dataclass_params__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'automatic', 'color', 'mileage', 'mileage_km'] <bound method Car.mileage_km of Car(color='red', mileage=3123, automatic=True)> <bound method __create_fn__.<locals>.__repr__ of Car(color='red', mileage=3123, automatic=True)> ElectronicCar(color='white', mileage=3123, automatic=True, charge=1000)
slots
如果你的程式會建立同一類別的大量object,但不會增刪object attribute,則可以考慮使用__slots__來節省object占用的記憶體,同時加快存取速度。
1: from dataclasses import dataclass 2: 3: @dataclass 4: class Car: 5: 6: __slots__ = ['color', 'mileage', 'automatic'] 7: 8: color: str 9: mileage: float 10: automatic: bool
__slots__不限於dataclass,在任何class均可使用
3.4.7. struct.struct
- 與C的struct交換資料的管道
1: from struct import Struct 2: 3: MyStruct = struct('1?f') #指定資料格式 4: data = MyStruct.pack(23, False, 42.0) #資料打包成二進位資料 5: print(data) 6: x = MyStruct.unpack(data) 7: print(x)
3.5. Set
- Python建立set,除了用{}、也可以用set comprehension
- 若要建立empty set,一定要call set()建構子,因為空的{}會被當成dict
1: vowels = {'a', 'e', 'i', 'o', 'u'} 2: squares = { x ** 2 for x in range(10) } 3: empty = set() 4: 5: print(vowels) 6: print(squares) 7: print(empty)
{'a', 'o', 'i', 'u', 'e'} {0, 1, 64, 4, 36, 9, 16, 49, 81, 25} set()
3.5.1. frozenset
- frozenset class為set的不可變版本,一旦建立就無法增刪修,只能查詢
- 但frozenset為靜態iterable object,故為hashable object,能當成dict object的key或其他set的element,這是一般set無法做到的事
1: vowels = frozenset({'a', 'e', 'i', 'o', 'u'}) 2: d = {vowels: 'hello'} # set變為dict的key 3: print(d) 4:
{frozenset({'e', 'o', 'u', 'a', 'i'}): 'hello'}
3.5.2. collections.Counter
- collections module底下的Counter實作了「多重集合」(multisets),能輸入多個iterable object(set, dict, string)並統整各元素的出現次數
- Counter實際為dict的subclass,當我們把新的iterable object輸入Counter object時,該iterable object的element會新增為dict的key,而該element出現次數則為dict的value
1: from collections import Counter 2: 3: inventory = Counter() 4: loot = {'sward': 1, 'bread': 3} #裝備品名及數量 5: inventory.update(loot) 6: print(inventory) 7: 8: more_loot = ['sward', 'coins'] 9: inventory.update(more_loot) 10: 11: yet_more_loot = ['sward', 'bread', 'drink'] 12: inventory.update(yet_more_loot) 13: print(inventory)
Counter({'bread': 3, 'sward': 1}) Counter({'bread': 4, 'sward': 3, 'coins': 1, 'drink': 1})
3.6. Stack
幾種實作Stack的結構
3.6.1. list
- python內部以動態陣列來實作list,所以會配置比element所需更大的空間,視需要增減。故push與pop不見會需要調整大小,平均效率可達O(1)
- 但為了達到這種效率,element必須從tail做push, pop
- 若改由list的head做push,pop,則list內所有element均需位移,效率會降為O(n)
1: a = [] 2: a.append('eating') 3: a.append('sleeping') 4: a.append('coding') 5: print(a) 6: a.pop() 7: print(a) 8: 9: #自head做push, pop 10: a.insert(0, 'drinking') 11: a.insert(0, 'playing') 12: a.pop(0) 13: print(a)
['eating', 'sleeping', 'coding'] ['eating', 'sleeping'] ['drinking', 'eating', 'sleeping']
3.6.2. collections.deque
- 快速穩定的stack
- 支援從head,tail做push,pop,效率均為O(1)
- 若要取出中間element,效率為O(n)
1: from collections import deque 2: 3: a = deque() 4: 5: a.append('eating') 6: a.append('sleeping') 7: a.append('coding') 8: print(a) 9: a.pop() 10: print(a) 11: 12: #自中間取值,效能差 13: print(a[1]) 14:
deque(['eating', 'sleeping', 'coding']) deque(['eating', 'sleeping']) sleeping
3.6.3. queue.LifoQueue
- 可用於multithreadin進行parallelism平行運算時的共享資料
- 這些資料結構還提供了上鎖機制,好讓同一時間只有一個thread能從queue取出資料
- queue module裡的LifoQueue class雖名為queue,但採Last In, First Out(LIFO),運作上與Stack相同
1: from queue import LifoQueue 2: s = LifoQueue() 3: s.put('eating') 4: s.put('sleeping') 5: s.put('coding') 6: print(s.get()) 7: print(s.get())
coding sleeping
3.7. Queue
幾種實作queue的資料結構
3.7.1. list
- 非常慢,沒有效率
3.7.2. collections.deque
- 快速穩定
1: from collections import deque 2: q = deque() 3: q.append('eating') 4: q.append('sleeping') 5: q.append('coding') 6: q.append('writing') 7: print(q) 8: print(q.popleft()) 9: print(q.popleft()) 10: print(q) 11:
deque(['eating', 'sleeping', 'coding', 'writing']) eating sleeping deque(['coding', 'writing'])
3.7.3. queue.Queue
- 和queue.LifoQueue一樣,內建上鎖機制,可以用來讓multithreading共享資料或任務
3.7.4. multiprocessing.Queue
- multi-thread在python中其實不算真正的平行運算,各thread實際上是在同一個interpreter下執行,只用到一個core,藉由不斷切換的方式來達到平行運算的效果。
3.7.5. priority queue
- 不遵循FIFO原則,而是以priority為順序考量
- priority由totally-ordered key來決定
3.7.6. heapq
- 借用list實作的binary tree(heap)結構
- 可實作priority
- 可於O(log n)的時間完成插入元素或取出最小元素
- heapq的各note實際上是存在一個list中;node在list中是依binary tree的順序儲存
- 借由binary tree所依據的sort key即可實作出priority queue
- 限制之一是預設為由小到大排序
Figure 1: heapq
1: import heapq 2: 3: q = [] 4: heapq.heappush(q, (2, 'coding')) 5: heapq.heappush(q, (1, 'eating')) 6: heapq.heappush(q, (3, 'sleeping')) 7: while q: 8: next_item = heapq.heappop(q) 9: print(next_item)
(1, 'eating') (2, 'coding') (3, 'sleeping')
3.7.7. queue.PriorityQueue
- 可用於multi-thread的heapq
**
4. LOOP
4.1. enumerate()
- 如果想保留for-each的寫法,但同時又希望能取得每個element的index
1: my_items = ['a', 'b', 'c'] 2: print(list(enumerate(my_items))) 3: for i, item in enumerate(my_items): 4: print(f'{i}: {item}')
[(0, 'a'), (1, 'b'), (2, 'c')] 0: a 1: b 2: c
4.2. zip()
- 可用來同時走訪多個container
- 可用來旋轉二維list
1: my_items = ['a', 'b', 'c'] 2: my_no = [1, 2, 3] 3: print(list(zip(my_no, my_items))) 4: 5: for no, item in zip(my_no, my_items): 6: print(f'{no}: {item}')
[(1, 'a'), (2, 'b'), (3, 'c')] 1: a 2: b 3: c
4.2.1. 旋轉list
1: a = [1, 2, 3] 2: b = [4, 5, 6] 3: c = [7, 8, 9] 4: matrix = [a, b, c] 5: print(matrix) 6: print(list(zip(*matrix))) 7: print(list(zip(*reversed(matrix))))
[[1, 2, 3], [4, 5, 6], [7, 8, 9]] [(1, 4, 7), (2, 5, 8), (3, 6, 9)] [(7, 4, 1), (8, 5, 2), (9, 6, 3)]
4.3. comprehension
- 即簡單的單行for-loop
- 可以讓我們用單行程式產生出包含特定元素的list, set, dict
4.3.1. 語法
1: list = [運算式 for 變數 in iterable object]
4.3.2. list comprehension
1: squares = [x ** 2 for x in range(10)] 2: print(squares)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
4.3.3. comprehension過濾條件
1: list = [x ** 2 for x in range(10) if x % 2 == 0] 2: print(list)
[0, 4, 16, 36, 64]
4.3.4. set與dict comprehension
1: setA = [x ** 2 for x in range(10)] 2: print(setA) 3: dicA = {x: x ** 2 for x in range(10)} 4: print(dicA)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
4.4. list
4.4.1. slice
語法
1: list[index start: index end: step]
1: nums = [1, 2, 3, 4, 5, 6, 7, 8] 2: print(nums[::]) 3: print(nums[1:6:2]) 4: nums[1:6:2] = [10, 20, 30] 5: print(nums) 6:
[1, 2, 3, 4, 5, 6, 7, 8] [2, 4, 6] [1, 10, 3, 20, 5, 30, 7, 8]
4.4.2. copy
1: l = list(range(6)) 2: print(l) 3: cl = l # 指向同一list 4: dl = l[::] # shallow copy 5: l[3] = 10 6: print(cl) 7: print(dl)
[0, 1, 2, 3, 4, 5] [0, 1, 2, 10, 4, 5] [0, 1, 2, 3, 4, 5]
5. Python 套件管理
5.1. pip v.s. conda
5.1.1. pip or conda
- Python 的一大優勢之一便是龐大的第三方函式庫,讓使用 python 的程式設計師可以方便的呼叫、進行如網路資料下載解析、資料的視覺化、甚或是大數據的複雜分析與人工智慧的相關套件。,
- 目前用來管理這些龐大套件的工具主要有二:pip 與 Conda。
5.1.2. pip
- Pip是Python Packaging Authority推薦、用於從Python Package Index安裝套件的工具,提供了對 Python 套件的搜㝷、下載、安裝、卸載的功能。
- 若在 python.org 下載最新版本的 python,則已內建 pip 安裝套件。 Python 3.4+ 以上版本均已包括 pip
- 該工具類似 Linux 下的 apt/yum 或 MAC 下的Homebrew。
5.1.3. conda
- Conda 是一個開源的跨平台工具軟體,它被設計作為 Python、R、Lua、Scala、C/C++、FORTRAN/ 與 Java 等任何程式語言的套件、依賴性以及工作環境管理員,特別受到以 Python 作為主要程式語言的資料科學團隊所喜愛。
- 適用平台:Windows, macOS, Linux
- 傳統 Python 使用者以 pip 作為套件管理員(package manager)、以 venv 作為工作環境管理員(environment manager),而 conda 則達成了「兩個願望、一次滿足」既可以管理套件亦能夠管理工作環境。2
5.1.4. In both cases:
- Written in Python
- Open source (Conda is BSD and pip is MIT)
- 五種開源授權規範的比較 (BSD, Apache, GPL, LGPL, MIT)
5.1.5. difference between conda, anaconda, and miniconda
- conda is both a command line tool, and a python package. 3
- Anaconda 發行版會預裝很多套件,而 Miniconda 是最小的 conda 安裝環境, 一個乾淨的 conda 環境。
- pip 只是運與安裝 python package,而 conda 用來安裝管理任何語言的包。
不一定要安裝 Anaconda 或 Miniconda,也可透過 pip 直接安裝 conda
pip install conda
5.2. conda 安裝與使用
5.2.2. 安裝conda
for macOS
1: brew install --cask anaconda 2: export PATH="/usr/local/anaconda3/bin:$PATH"
5.2.3. 移除
- Windows: uninstall
- Linux/macOS:
rm -rf ~/anaconda
5.3. python package 安裝(conda)
安裝 package:
conda install packageName
conda install pandas
移除 package:
conda remove packageName
conda remove pandas
安裝特定版本 python
conda install python=version
conda install python=3.5
了解目前系統可用套件
conda list
5.4. Python 常用函式庫
5.4.1. 爬蟲
Scrapy:
- Scrapy,Python 開發的一個快速、高層次的 web 數據抓取框架,用於抓取 web 站點並從頁面中提取結構化的數據。Scrapy 用途廣泛,可以用於數據挖掘、監測和自動化測試4。
- Scrapy 吸引人的地方在於它是一個框架,任何人都可以根據需求方便的修改。它也提供了多種類型爬蟲的基類,如 BaseSpider、sitemap 爬蟲等。
beautifulsoup4:
5.4.2. 網站
Django
- Django (ˈdʒæŋɡoʊ jang-goh) 可以說是 Python 最著名的 Web Framework,一些知名的網站如 Pinterest, Instagram, Disqus 等等都使用過它來開發。9
- 免費開放原始碼
- 著重快速開發、高效能
- 遵從 DRY ( Don’t Repeat Yourself ) 守則,致力於淺顯易懂和優雅的程式碼
- 使用類似 Model–view–controller (MVC) pattern 的架構
- 10 Popular Websites Built With Django
Flask
- Flask 是一個使用 Python 撰寫的輕量級 Web 應用程式框架,由於其輕量特性,也稱為
micro-framework(微框架)。10 - Flask 和 Django 不同的地方在於 Flask 給予開發者非常大的彈性(當然你也可以說是
需要思考更多事情),可以選用不同的用的 extension 來增加其功能。 - 相比之下,Django 雖然完善但技術選擇相對不彈性,不論是 ORM、表單驗證或是模版引
擎都有自己的作法。 - 沒有最好的框架,只有合適的使用情境。
5.4.3. 資料處理科學計算
Numpy
- Numpy 底層以 C 和 Fortran 語言實作,所以能快速操作多重維度的陣列。11
- 當 Python 處理龐大資料時,其原生 list 效能表現並不理想(但可以動態存異質資料),而 Numpy 具備平行處理的能力,可以將操作動作一次套用在大型陣列上。
- 此外 Python 其餘重量級的資料科學相關套件(例如:Pandas、SciPy、Scikit-learn 等)都幾乎是奠基在 Numpy 的基礎上。
Scipy
- 科學計算神器
- Numpy 是以矩陣基礎做數據的數學運算,SciPy 就是以 Numpy 為基礎做科學、工程的運算處理的 package,包含統計、優化、整合、線性代數、傅立葉轉換圖像等較高階的科學運算。12
Pandas
- 建構在 NumPy 之上,提供資料結構與資料處理工具,讓資料清理與分析更為快速與方便
- 適合處理表格或異質資料 (NumPy 適合處理同質之數值陣列資料)
5.4.4. 視覺化
matplotlib
- Matplotlib 就是 MATLAB+Plot+Library 的簡稱,因為是模仿 MATLAB 建立的繪圖庫,所以繪
圖風格會與 MATLAB 有點類似。13 - 為了提高處理大量資料的性能,Matplotlib 大量使用了 NumPy 和其相關的擴展代碼。 為了方便快速繪圖, Matplotlib 通過 pyplot 模組提供了一套和 MATLAB 類似的繪圖 API,只需要調用 pyplot 模組所提供的函數,就可以實現快速繪圖及設置圖表的各種細節。
- 另一方面 Matplotlib 也適合互動式繪製圖表,可以很方便地處理二維和三維的圖表。
seaborn
ggplot
- R 語言視覺化神器的 Python 版本
- ggplot2 是一個十分強大的 R 語言可視化包。它的核心理念是將繪圖與數據分離,數據相關
的繪圖與數據無關的繪圖分離。16 - 它是按圖層作圖的,一個語句做一個僅包含基礎作圖單元的圖層,然後通過不同圖層的疊
加最後成圖。
plotly
- 這個神器是個 js 庫,不過也有各種流行的語言介面
- plotly 是一個能讓你畫出互動式圖表的一個開源套件,其基本操作是免費的,如果你想要使用網頁的版本,可以使用更多 plotly 的進階功能的話,就必須付費
5.4.5. 機器學習
scikit-learn
- 幾乎所有機器學習演算法都囊括
- scikit-learn,又寫作 sklearn,是一個開源的基於 python 語言的機器學習工具包。它通過 NumPy, SciPy 和 Matplotlib 等 python 數值計算的庫實現高效的算法應用,並且涵蓋了幾乎所有主流機器學習算法。17
- sklearn 中常用的 module 有分類、回歸、聚類、降維、模型選擇、預處理。
NLTK
TensorFlow
- Tensorflow 最初為 Google Brian 所開發。在 2015 時,Google 將之開源,為現今重要的深
度學習框架之一,它支援各式不同的深度學習演算法,並已應用於各大企業服務上,Ex:
Google, Youtube, Airbnb, Paypal … 等。19 - 此外,Tensorflow 也支援在各式不同的
device 上運行深度學習 Ex: Tensorflow Lite 、 Tensorflow.js 等等。 - Tensorflow 為目
前最受歡迎的機器學習、深度學習開源專案。不管是 github fork 的數量、論文的使用次
數以及熱門程度,均比其他的框架來的多20。
深度學習套件
Keras
- 在 2015 年 TensorFlow 推出的同時,美國麻省理工學院(又是它,這學校開門就能賺錢)
也推出一套能很容易被使用者透過 Python 寫 Deep learning 的應用程式介面 API ,叫
做 Keras 。 - Keras 只有介面喔! Keras 本身還是得透過 TensorFlow ,或者其
他像是微軟的 CNTK 這類引擎當作底層,才能執行。21 - Keras 使用上比較接近人類的想法( TensorFlow 設計上沒有錯,只不過比較是針對電腦
系統跟網路通訊的想法),所以透過 Python 呼叫 Keras 能夠很簡單地描述我們人類想
要電腦達到 Deep Learning 要做的事情。目前在教學上, Keras 的普及率相當高 - Keras 可以快速有方便運算的主要原因是,它已經將訓練模型的輸入層、隱藏層、輸出層,做好架構,使用者只需要加入並且填寫正確的參數 ex.神經元個數、activation function 的函式…等。22
PyTorch
- Numpy 的 GPU 版
- PyTorch 為 Facebook 在 2017 年初開源的深度學習框架,其建立在 Torch 之上,且標
榜 Python First ,為量身替 Python 語言所打造,使用起來就跟寫一般 Python 專案沒
兩樣,也能和其他 Python 套件無痛整合。PyTorch 的優勢在於其概念相當直觀且語法簡
潔優雅,因此視為新手入門的一個好選項;再來其輕量架構讓模型得以快速訓練且有效運
用資源。23 - 於 PyTorch 框架中,資料類型定義為張量(Tensor),張量可以是純量、向量或是更高維度的矩陣,而 torch 函式庫負責張量在 CPU/GPU 的運算。
5.5. python 執行環境建立與維護
建立24
conda create -n envName
啟用
conda activate envName
退出
conda deactiveate
刪除
conda env remove -n envName
列出目前系統中所有的虛擬環境
conda env list
5.6. 將執行環境匯入 jupyter
- 於終端機(for windows: Anaconda prompt)下建立、啟用所需虛擬環境
- 將環境滙至 jupyter kernel
python -m ipykernel install --user --name 虛擬環境名稱 --display-name "在jupyter中的名稱"
- 啟動 jupyter
5.7. poetry
pip最主要的缺點在於套件的相依性。pip 在解決套-件之間的版本衝突時很容會遇到困難。它不具備先進的依賴解析算法,這可能導致不穩定的環境和不可預知的錯誤。更重要的是,移除套件時,pip 只會移除一個該套件,而不會移除其他相依的套件。也因此發展出針對套件相依性更好的工具,例poetry25。
Poetry 是一個現代化的套件管理工具,它不僅可以幫助我們管理套件的依賴,還提供了一個虛擬環境的解決方案。Poetry 使用一個 toml 檔 pyproject.toml 文件來管理專案配置和依賴,這符合 – Specifying Minimum Build System Requirements for Python Projects 所提倡的現代 Python 專案的依賴項管理25。
5.7.1. 安裝
for Python 3.8+
curl -sSL https://install.python-poetry.org | python3 -
5.7.2. 結合虛擬環境與專案
poetry config virtualenvs.in-project true
5.7.3. 建立新專案
假設專案名稱為 tnfshbot
poetry new tnfshbot
cd tnfshbot
poetry init
5.7.4. 修改pyproject.toml
1: [tool.poetry.dependencies] 2: python = "^3.12"
5.7.5. 安裝
當你手動添加了新的套件後(poetry add XXX),使用 poetry install 將會:
- 安裝 pyproject.toml 中列出的所有套件,包括新添加的與其依賴項。
- 如果 poetry.lock 文件存在,它將依照該文件中固定的版本來安裝依賴項。
- 如果 poetry.lock 文件不存在,它將創建一個新的,其中固定了依賴的版本。
1: poetry install
會生成一個.venv資料夾
1: poetry run python --version
5.7.6. 新增tnfshbot/main.py
安裝新套件
1: poetry add pendulum
執行python
1: import pendulum 2: import sys 3: 4: print("Hello World!") 5: print(sys.version) 6: print(pendulum.now())
執行方式為
1: poetry run python tnfshbot/main.py
5.7.7. 整個資料夾架構如下
. ├── README.md ├── poetry.lock ├── pyproject.toml ├── tests │ └── __init__.py └── tnfshbot ├── __init__.py └── main.py
5.7.8. pyproject.toml
精確版本
指定一個精確的版本,意味著只有該特定版本會被接受。例如:
1: [tool.poetry.dependencies] 2: numpy = "1.21.0"
版本範圍
可以使用比較運算符來指定一個版本範圍:
1: [tool.poetry.dependencies] 2: numpy = ">=1.21.0, <2.0.0"
在上面的範例中,任何版本從 1.21.0 (含) 到 2.0.0 (不含) 都是可以接受的。
星字號
使用星號(*)是允許任何兼容的版本:
1: [tool.poetry.dependencies] 2: numpy = "1.21.*"
在上面的範例中,任何 1.21 系列的版本都是可以接受的。如
Caret
使用Caret(^)可以指定一個允許任何相容的版本,但不會改變最左邊的非零數字:
1: [tool.poetry.dependencies] 2: numpy = "^1.21.0"
這將允許 1.21.0 以及任何更高但低於 2.0.0 的版本。
波浪號版本
使用波浪號(~)可以指定一個允許在特定範圍內的版本:
1: [tool.poetry.dependencies] numpy = "~1.21.0"
這會允許 1.21.0 以及任何更高但低於 1.22.0 的版本。
多版本指定
你也可以指定多個版本,並且只要符合其中一個條件就可以:
1: [tool.poetry.dependencies] 2: numpy = [ ">=1.20.0, <1.21.0", ">=1.22.0, <1.23.0" ]
5.7.9. poetry update
在添加新的依賴後使用 poetry update 將會26:
- 更新所有依賴到最新可用版本,並更新 poetry.lock 文件。
- 它會安裝新添加的套件和更新所有其他依賴。
- 這個指令超級強大,更新套件版本時也解析並更新依賴。有更新過 Python 套件的人常常都會受到依賴項衝突所困,用這個指令就能簡單解決了。
- 可以指定要更新什麼套件,例如說 poetry update pendulum
5.7.10. poetry to jupyter kenrel
1: poetry run ipython kernel install --name=tnfshbot --user
6. TODO Multiprocessing
6.1. Futher Reading
Figure 2: Multiprocessing v.s. Multithreading