Advanced Materials of Python

Table of Contents

Hits

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
  • 限制之一是預設為由小到大排序

heapq.png

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:

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.1. 下載 v.s. 安裝

5.2.2. 移除

  • 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.jpg

  • Scrapy,Python 開發的一個快速、高層次的 web 數據抓取框架,用於抓取 web 站點並從頁面中提取結構化的數據。Scrapy 用途廣泛,可以用於數據挖掘、監測和自動化測試4
  • Scrapy 吸引人的地方在於它是一個框架,任何人都可以根據需求方便的修改。它也提供了多種類型爬蟲的基類,如 BaseSpider、sitemap 爬蟲等。
beautifulsoup4:

bs.png

  • Beautiful Soup 是一個 Python 的函式庫模組,可以讓開發者僅須撰寫非常少量的程式
    碼,就可以快速解析網頁 HTML 碼,從中翠取出使用者有興趣的資料、去蕪存菁,降低網
    路爬蟲程式的開發門檻、加快程式撰寫速度。5
  • 而 Beautiful Soup 是基於 HTML DOM 的,會載入整個文檔,解析整個 DOM 樹,因此時間和內存開銷都會大很多,所以性能要低於 lxml。6
Selenium

selenium.jpg

  • 原為網頁測試工具,但由於可以直接以程式碼操控瀏覽器的特性,使其成為網路爬蟲必備
    的工具之一。7
  • Selenium 執行「真實的瀏覽器」來進行網站操作的自動化,它能夠直接獲取即時的內容,包括被 JavaScript 修改過的 DOM 內容,讓程式可以直接與網頁元素即時互動、執行 JavaScript 程式,因此也適用於前端採用 AJAX 技術的網站。8
  • Selenium 是許多 Web Testing 工具的核心,利用 Selenium 操作網頁表單資料、點選按鈕或連結、取得網頁內容並進行檢驗,可以滿足相當多測試的需求。

5.4.2. 網站

Django

django.jpg

  • 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.png

  • Flask 是一個使用 Python 撰寫的輕量級 Web 應用程式框架,由於其輕量特性,也稱為
    micro-framework(微框架)。10
  • Flask 和 Django 不同的地方在於 Flask 給予開發者非常大的彈性(當然你也可以說是
    需要思考更多事情),可以選用不同的用的 extension 來增加其功能。
  • 相比之下,Django 雖然完善但技術選擇相對不彈性,不論是 ORM、表單驗證或是模版引
    擎都有自己的作法。
  • 沒有最好的框架,只有合適的使用情境。

5.4.3. 資料處理科學計算

Numpy

numpy.jpg

  • Numpy 底層以 C 和 Fortran 語言實作,所以能快速操作多重維度的陣列。11
  • 當 Python 處理龐大資料時,其原生 list 效能表現並不理想(但可以動態存異質資料),而 Numpy 具備平行處理的能力,可以將操作動作一次套用在大型陣列上。
  • 此外 Python 其餘重量級的資料科學相關套件(例如:Pandas、SciPy、Scikit-learn 等)都幾乎是奠基在 Numpy 的基礎上。
Scipy

scipy.png

  • 科學計算神器
  • Numpy 是以矩陣基礎做數據的數學運算,SciPy 就是以 Numpy 為基礎做科學、工程的運算處理的 package,包含統計、優化、整合、線性代數、傅立葉轉換圖像等較高階的科學運算。12
Pandas

pandas.png

  • 建構在 NumPy 之上,提供資料結構與資料處理工具,讓資料清理與分析更為快速與方便
  • 適合處理表格或異質資料 (NumPy 適合處理同質之數值陣列資料)

5.4.4. 視覺化

matplotlib

matplotlib.jpg

  • Matplotlib 就是 MATLAB+Plot+Library 的簡稱,因為是模仿 MATLAB 建立的繪圖庫,所以繪
    圖風格會與 MATLAB 有點類似。13
  • 為了提高處理大量資料的性能,Matplotlib 大量使用了 NumPy 和其相關的擴展代碼。 為了方便快速繪圖, Matplotlib 通過 pyplot 模組提供了一套和 MATLAB 類似的繪圖 API,只需要調用 pyplot 模組所提供的函數,就可以實現快速繪圖及設置圖表的各種細節。
  • 另一方面 Matplotlib 也適合互動式繪製圖表,可以很方便地處理二維和三維的圖表。
seaborn

seaborn.png

seabornPlots.jpg

  • 散點圖矩陣神器
  • Seaborn 是 Python 一個著名的數據視覺化的 package, 以 matplotlib 為基底的一個擴
    展包, 它提供了易於理解的統計圖和便利的繪圖指令14
  • seaborn 庫是對 matplotlib 庫更高級別的封裝,相當於提供了各種統計圖的模板15
ggplot

ggplot.jpg

  • R 語言視覺化神器的 Python 版本
  • ggplot2 是一個十分強大的 R 語言可視化包。它的核心理念是將繪圖與數據分離,數據相關
    的繪圖與數據無關的繪圖分離。16
  • 它是按圖層作圖的,一個語句做一個僅包含基礎作圖單元的圖層,然後通過不同圖層的疊
    加最後成圖。
plotly

plotly.png

  • 這個神器是個 js 庫,不過也有各種流行的語言介面
  • plotly 是一個能讓你畫出互動式圖表的一個開源套件,其基本操作是免費的,如果你想要使用網頁的版本,可以使用更多 plotly 的進階功能的話,就必須付費

5.4.5. 機器學習

scikit-learn

scikit-learn.png

  • 幾乎所有機器學習演算法都囊括
  • scikit-learn,又寫作 sklearn,是一個開源的基於 python 語言的機器學習工具包。它通過 NumPy, SciPy 和 Matplotlib 等 python 數值計算的庫實現高效的算法應用,並且涵蓋了幾乎所有主流機器學習算法。17
  • sklearn 中常用的 module 有分類、回歸、聚類、降維、模型選擇、預處理。
NLTK

NLTK.png

  • NLTK 全文是 “Nature Language Tool Kit” (NLTK),是 Python 中一個經典的、專門用於進行自然語言處理的工具。18
  • 雖然也能進行部份中文的處理,但是對於中文的支援度自然沒有英文來得好
  • 目前而言,繁體中文有兩個套件可以使用,一個是中研院開發的斷詞系統,另一套系統為
    jieba(結巴)。
  • 少女詩人小冰
TensorFlow

tensorflow.jpg

  • Tensorflow 最初為 Google Brian 所開發。在 2015 時,Google 將之開源,為現今重要的深
    度學習框架之一,它支援各式不同的深度學習演算法,並已應用於各大企業服務上,Ex:
    Google, Youtube, Airbnb, Paypal … 等。19
  • 此外,Tensorflow 也支援在各式不同的
    device 上運行深度學習 Ex: Tensorflow Lite 、 Tensorflow.js 等等。
  • Tensorflow 為目
    前最受歡迎的機器學習、深度學習開源專案。不管是 github fork 的數量、論文的使用次
    數以及熱門程度,均比其他的框架來的多20

深度學習套件

Keras

keras.png

  • 在 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

pytorch.jpg

  • 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

6. TODO Multiprocessing

7. TODO Interactive Plots in Jupyter Notebook

Footnotes:

Author: Yung-Chin Yen

Created: 2024-03-12 Tue 21:02