# :memo: Python
###### tags: `Python`
## Reserved keyword

## Naming Rules and Conventions
```python=
MACRO_CASE # constants
current_salary # variables snake_case
```
## Value and Variable reference in memory

```python=
>>> a = 2 # 建立 2 value 並將 a variable 關聯過去
>>> id(a)
4343310672
>>> a += 1 # 得到新的 value 並將 a variable 重新關聯
>>> id(a)
4343310704
>>> id(3)
4343310704
>>> b = 2 # 2 value 已經建立過,直接將 b variable 關聯過去
>>> id(2)
4343310672
>>> id(b)
4343310672
```
```python=
>>> def a(): # 定義 function object 也有位址,以 a 進行參考
... pass
...
>>> b = a # b 也參考到 a 的對象,即 function object
>>> b()
>>> id(a) # a, b 參考到的 object 是同一個
4340704736
>>> id(b)
4340704736
```
## Scope
> - 在 function 中有 local names
> - 在 module 有 global names
> - 在最外面有 built-in names
```python=
def outer_function():
a = 20
def inner_function():
a = 30
print('inner_function, a =', a) # a is local a in nested function
inner_function()
print('outer_function, a =', a) # a is local a in function
a = 10
outer_function()
print('module, a =', a) # a is global in model
```
> 發生 reference before assignment 的問題
>
> - 該變數在該 scope 只 read-only 時,會 lookup global variable
> - 該變數在該 scope 有 assignment 時,視為 local variable
> - 該 local variable 沒任何資料下是不能被參考使用的
```python=
x = 10
def foo():
x = x * 2 # reference before assignment
print(x)
def bar():
y = x * 2 # reference before assignment
x = y
# python 掃到 x 被 assign,視 x 為 local variable
# x * 2 一個不存在的 local variable 被讀取
# UnboundLocalError: local variable 'x' referenced before assignment
print(x)
def qoo():
y = x * 2 # ok, treat x as global x
# python 沒找到 x ,視 x 為 global variable
# 將算完的結果放到 y
print(y)
#foo()
#bar()
qoo()
```
> 使用 gloabl keyword 來定義該變數為 gloabl
```python=
x = 10
def foo():
global x # 告訴 python 這個參考到 global x
x = x * 2
print(x) # 20
print(x) # 10
foo()
print(x) # 20
```
## Conversion and Casting
```python=
>>> float(5)
5.0
>>> int(10.6)
10
>>> int(-10.6)
-10
>>> float('2.5')
2.5
>>> str(25)
'25'
>>> set([1,2,3,3]) # list to set
{1, 2, 3}
>>> tuple({1,3,3,4}) # set to tuple
(1, 3, 4)
>>> list('hello') # string to list
['h', 'e', 'l', 'l', 'o']
>>> dict([[1,2], [3,4]]) # list of list to dict
{1: 2, 3: 4}
>>> dict([(1,2), (3,4)]) # tuple of list to dict
{1: 2, 3: 4}
```
> - Implicit Type Conversion
> - python 自動轉型
> - ex: int to float (123 + 123.4 = 246.4)
> - 數字和字串不同型別,不能自動轉型,發生 **TypeError**
> - Explicit Type Conversion
> - 使用 <required_datatype>(expression) 指示轉型
> - int()
> - str()
> - float()
> - bool()
| + | int | float | bool | string |
| - | - | - | - | - |
| int | int | float | int | X |
| float | float | float | float | X |
| bool | int | float | int | X |
| string | X | X | X | string |
## Syntax
### Operator
- Arithmetic operators 
- Comparison operators 
- Logical operators 
- Bitwise operators 
- Identity operators 
- Membership operators 
```python=
>>> (1+100) * 100/2
5050.0
>>> (1+100) * 100//2 # 雙斜線取整數
5050
```
```python=
>>> 10**2 # 次方
100
```
```python=
# type 可以顯示 object 為那種類型
>>> type(10.0)
<class 'float'>
>>> type('hello')
<class 'str'>
>>> type('bool')
<class 'str'>
>>> type(None)
<class 'NoneType'>
```
```python=
# id 可以顯示變數的位址
>>> a = 10
>>> id(a)
4338297424
>>> b = 20
>>> id(b)
4338297744
>>> b = a # b 也參考到 a 的位址
>>> id(b)
4338297424
```
### Assignment
```python=
>>> a = 10
>>> b = 20
>>> a, b = b, a # swap, unpack assign
>>> a
20
>>> b
10
>>>
```
```python=
>>> a, b, c = 5, 3.2, "Hello"
>>> x = y = z = "same"
```
### Constants
```python=
PI = 3.14
GRAVITY = 9.8
```
### Docstrings
```python=
def double(num):
"""Function to double the value"""
return 2*num
```
### Flow
#### if
```python=
if num > 0:
print(num, "is a positive number.")
```
```python=
if num >= 0:
print("Positive or Zero")
else:
print("Negative number")
```
```python=
if num > 0:
print("Positive number")
elif num == 0:
print("Zero")
else:
print("Negative number")
```
#### for
```python=
>>> count = 0
>>> for i in range(1, 101):
... count += i
...
>>> count
5050
```
> for 可以搭配 else
> - 當條件為 False 時執行 else
> - else 會執行是 for loop 沒有任何 break 發生時
> - 當 break 發生時 else 不執行
```python=
student_name = 'James'
marks = {'James': 90, 'Jules': 55, 'Arthur': 77}
for student in marks:
if student == student_name:
print(marks[student]) # result: 90
break
else:
# 不會執行,因為發生了 break。只有當 break 沒發生時才會執行
print('No entry with that name found.')
```
#### while
```python=
n = 10
# initialize sum and counter
sum = 0
i = 1
while i <= n:
sum = sum + i
i = i+1 # update counter
# print the sum
print("The sum is", sum)
```
> while 可以搭配 else
> - 當條件為 False 時執行 else
> - else 會執行是 while loop 沒有任何 break 發生時
> - 當 break 發生時 else 不執行
```python=
counter = 0
while counter < 3:
print("Inside loop")
counter = counter + 1
else:
print(f"counter = {counter}") # 一路跑到終止條件,執行 else
```
#### break & continue
```python=
for val in "string":
if val == "i":
break
print(val)
print("The end")
```
```python=
for val in "string":
if val == "i":
continue
print(val)
print("The end")
```
## DataTypes
### Numbers
```python=
# 10 進制轉 2, 8, 16 進制
>>> bin(100)
'0b1100100'
>>> oct(100)
'0o144'
>>> hex(100)
'0x64'
```
```python=
# 2, 8, 16 進制轉 10 進制
>>> print(0b1100100)
100
>>> print(0o144)
100
>>> print(0x64)
100
>>> int('1100100', 2)
100
>>> int('144', 8)
100
>>> int('64', 16)
100
```
### List

```python=
# Index
my_list = ['p', 'r', 'o', 'b', 'e']
print(my_list[2])
print(my_list[4])
print(my_list[-1])
n_list = ["Happy", [2, 0, 1, 5]]
print(n_list[0][1])
print(n_list[1][3])
# Slice
my_list = ['p','r','o','g','r','a','m','i','z']
print(my_list[2:5]) # 從 idx = 2 取到 idx = 4 (不含5) = o g r
print(my_list[5:]) # 從 5 到最後 = r a m i z
print(my_list[:]) # dup 一個 list , 若 y = my_list 是相同物件不同參考,不是 copy
# 等同於 my_list.copy()
# list methods
# 'append',
# 'clear',
# 'copy',
# 'count',
# 'extend',
# 'index',
# 'insert',
# 'pop',
# 'remove',
# 'reverse',
# 'sort'
# add, change, del
odd = [2, 4, 6, 8]
odd[1:4] = [3, 5, 7] # 相當於把 4, 6, 8 (4-1=3)洗掉,在塞 3, 5, 7 result: 2, 3, 5, 7
print(odd)
odd[:-1] = [100] # 留最後一個全清掉塞 100, result: 100, 7
odd[:] = [99] # 清空整個 list 塞 99 result: 99
print(odd)
odd.append('ok') # append 一個元素在最後面
print(odd) # result: [99, 'ok']
odd.extend([1,2,3,4]) # 等同於 +=
print(odd) # result: [99, 'ok', 1, 2, 3, 4]
odd += [5,8] # 等同於 extend
print(odd) # result: [99, 'ok', 1, 2, 3, 4, 5, 8]
odd.insert(1, 98) # 98 insert at index 1
print(odd) # result: [99, 98, 'ok', 1, 2, 3, 4, 5, 8]
odd[2:2] = [97] # 等同於 .insert(2, 97)
print(odd) # result: [99, 98, 97, 'ok', 1, 2, 3, 4, 5, 8]
del odd[3] # delete 3
print(odd)
del odd[:3] # delete 0 ~ 2
print(odd)
odd.append(8)
print(odd) # result: [1, 2, 3, 4, 5, 8, 8]
odd.remove(8) # 刪除第一個出現的 8
print(odd) # result: [1, 2, 3, 4, 5, 8]
# 如果要刪除某個在 list 出現多次的 item 的話,用 del, remove 都不行,因為 index 會變
# 當第一次刪除時,後面的元素 index 會向前補
# 所以只能創造另外一個 list 把要刪除的元件排除
odd.append(8)
odd.append(8)
odd.append(8)
odd.append(8)
new_odd_without8 = [i for i in odd if i != 8]
print(new_odd_without8)
print(odd) # result: [1, 2, 3, 4, 5, 8, 8, 8, 8, 8]
print(odd.pop(), odd) # result: 8 [1, 2, 3, 4, 5, 8, 8, 8, 8]
print(odd.pop(), odd) # result: 8 [1, 2, 3, 4, 5, 8, 8, 8]
print(odd.pop(), odd) # result: 8 [1, 2, 3, 4, 5, 8, 8] 從最後面一個 pop 出來
print(odd.pop(4), odd) # result: 5 [1, 2, 3, 4, 8, 8] 從 index 4 pop 一個元素
odd[4:6] = [] # 透過 assign 來刪除元素
print(odd) # result: [1, 2, 3, 4]
odd = [99, 4] + odd
print(odd) # result: [99, 4, 1, 2, 3, 4]
print(odd.index(3), odd) # result: 4 [99, 4, 1, 2, 3, 4] 元素 3 在什麼位置
print(odd.count(4), odd) # result: 2 [99, 4, 1, 2, 3, 4] 元素 4 有幾個
odd.sort() # 對 list 元件 sort() 和使用 sorted(odd) 不同,sorted 不會改變原 list
print(odd) # result: [1, 2, 3, 4, 4, 99]
odd.reverse() # 對 list 元件 reverse element 和使用 reversed(odd) 不同,reversed 不會改變原 list
print(odd) # result: [99, 4, 4, 3, 2, 1]
# exist
print(99 in odd) # result: True
print(99 not in odd) # result: False
```
#### List comperhension
```python=
str_human = 'human'
# [expression for item in list]
h_letter = [ch for ch in str_human]
print(h_letter) # ['h', 'u', 'm', 'a', 'n']
n_list = [x for x in range(20) if x % 2 == 0]
print(n_list)
# if with list comperhension
y_list= [y for y in range(100) if y % 2 == 0 and y % 5 == 0]
print(y_list)
# if...else With List Comprehension
even_list = ["even" if i % 2 == 0 else "odd" for i in range(10)]
print(even_list)
matrix = [[1,2], [3,4], [5,6], [7,8]]
# by for
new_matrix = []
for i in range(2):
tmp_l = []
for row in matrix:
tmp_l.append(row[i])
new_matrix.append(tmp_l)
print(new_matrix)
print([row[i] for i in range(2) for row in matrix]) # [1, 3, 5, 7, 2, 4, 6, 8] 無法分成兩組
# 採用另外一種寫法 [ [i for i in item] for item in list ]
# by nested comperhension
new_matrix_comp = [[row[i] for row in matrix] for i in range(2)] # 將 nested group 元素,當成一組傳回去
print(new_matrix_comp) # [[1, 3, 5, 7], [2, 4, 6, 8]]
matrix2 = [[1,2,3,4], [5,6,7,8]]
new_matrix_comp2 = [[row[i] for row in matrix2] for i in range(4)]
print(new_matrix_comp2) # [[1, 5], [2, 6], [3, 7], [4, 8]]
```
```python=
# 建立一個固定長度的 list 並切始化所有元素
>>> [1] * 10
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
```
### Tuple
> - 在 indexing, Negative indexing, slicing 都和 List 一樣
> - List功能強大,它內容可以改變,同時也可新增和刪除,但Tuple資料不可修改。
> - Tuple執行速度比List快,因為其內容不會改變,因此Tuple內部結構比List簡單,執行速度較快。
> - 存於Tuple的資料較為安全,因為內容不會改變,不會因程式設計疏忽而變更資料內容
> 需特別注意只有單一元素的 tuple 的定義
> ```python=
> my_tuple = ("hello")
> print(type(my_tuple)) # <class 'str'>
>
> my_tuple = ("hello",) # 要建立一個 element 的 tuple 的寫法
> print(type(my_tuple)) # <class 'tuple'>
> ```
```python=
my_tuple = 3, 4.6, "dog" # create tuple
print(my_tuple) # result: (3, 4.6, 'dog')
a, b, c = my_tuple # unpacking tuple
print(a, b, c)
type_int = 10
print(type(type_int)) # result: <class 'int'>
type_tuple = 10, # 不用刮號也可以建立 tuple
print(type(type_tuple)) # result: <class 'tuple'>
my_tuple = ("mouse", [8, 4, 6], (1, 2, 3))
print(my_tuple[1][:-1]) # result: [8, 4]
my_tuple[1][0:1] = [] # 把元素砍掉
print(my_tuple) # result: ('mouse', [4, 6], (1, 2, 3))
a_tuple = 10, 2
b_tuple = 3,
print((a_tuple + b_tuple)*2) # result: (10, 2, 3, 10, 2, 3) 將兩個 tuple 組合在重複2次
# Tuple method
# 'count'
# 'index'
my_tuple = ('a', 'p', 'p', 'l', 'e')
print(my_tuple.count('p')) # result: 2 有幾個 p
print(my_tuple.index('p')) # result: 1 第一個 p 在那個 idx
# exist
print('a' in my_tuple) # result: True
print('c' not in my_tuple) # result: True
# tuple comperhension
# 因為 () 的縮寫被 generator 用了,所以 tuple 的 comperhension 需要用 tuple() 來做
print(tuple(i for i in my_tuple if i != 'p'))
```
### String
```python=
str = 'programe'
print(str[0]) # result: p
print(str[-1]) # result: e
print(str[1:5]) # result: rogr
try:
str[0] = 'd' # string are immutable (TypeError: 'str' object does not support item assignment)
except Exception as e:
print(e)
# + concat
str1 = 'hello'
str2 = 'world'
print(str1 + str2)
# * repeat
print((str1+' ') * 3)
# iterating
l_counter = 0
for i in str1+str2:
if i == 'l':
l_counter +=1
print(l_counter)
# or using comperhension
l_counter = len([i for i in str1+str2 if i == 'l'])
print(l_counter)
# exist
print('w' in str2) # result: True
print('w' in str1) # result: False
for idx, ch in enumerate(str1, 1):
print(idx, ch)
# 1 h
# 2 e
# 3 l
# 4 l
# 5 o
```
#### format


> - Type
> - `d` Decimal
> - `b` Binary
> - `x` Hex
> - `f` float
> - Alignment
> - `<` 靠左
> - `^` 置中
> - `>` 靠右
> - `=` 強制 (+)(-) 符號位置
```python=
a_str = "{}, {} and {}".format('John', 'Bill', 'Sean')
print(a_str) # John, Bill and Sean
a_str = "{2}, {0} and {1}".format('John', 'Bill', 'Sean')
print(a_str) # Sean, John and Bill
a_str = "{x}, {y} and {z}".format(z='John', x='Bill', y='Sean')
print(a_str) # Bill, Sean and John
a_str = "{} and {z}".format('John', z='Bill')
print(a_str) # John and Bill
print("The number is:{:d}".format(123))
print("The float number is:{:f}".format(123.4567898)) # The float number is:123.456790 (預設 6 位小數)
print("The float number is:{:.3f}".format(123.4567898)) # The float number is:123.456 (3位小數)
print("bin: {0:b}, oct: {0:o}, hex: {0:x}".format(12)) # bin: 1100, oct: 14, hex: a (都用第一欄的資料)
# 轉二進制
print('{:b}'.format(10))
print(bin(10)[2:])
# 長度為 10 的欄位
print('{:10} good'.format('ok'))
# 長度和小數位數一起使用
print("{:5d}".format(12)) # 5 位右邊對齊 result: " 12"
print("{:2d}".format(1234)) # 這個沒用,位數超過 result: "1234"
print("{:8.3f}".format(12.2346)) # 整數加小數共8位,向右對齊 result: " 12.235" (3位四捨五入)
print("{:05d}".format(12)) # 不足5位數,補0 result: "00012"
print("{:08.3f}".format(12.2346)) #補0、小數3位、總長度8 result: "0012.235"
# show the + sign
print("{:+8.3f} {:+8.3f}".format(12.23, -12.23)) # result: " +12.230 -12.230"
print('-'*50)
print("\"{:^10.3f}\"".format(12.2346)) # result: " 12.235 " 置中對齊
print("\"{:<10d}\"".format(12)) # result: "12 " 靠左對齊
print("\"{:=15.3f}\"".format(-12.2346)) # result: "- 12.235" (符號對齊)
print("\"{:*^15d}\"".format(12)) # padding with '*' result: "000000120000000"
# unpack dict and format
person = {'age': 23, 'name': 'Adam'}
print("{name}'s age is: {age}".format(**person)) # Adam's age is: 23
```
```python=
# string method
str1 = "PrOgRaMiZ"
print(str1.upper()) # 全大寫
print(str1.lower()) # 全小寫
str2 = "hello worlld"
print(str2.split()) # ['hello', 'world'] 切割變 list
list1 = ['a', 'b', 'c']
print(','.join(list1)) # a,b,c 把 list join 成 string
print(str2.find('ll')) # 找出第一個 ll substring 的 index
print(str2.rfind('ll')) # 找出最後一個 ll substring 的 index
str2 = str2.replace('ll', 'LL') # 把 string 中所有出現的 ll 換成 LL
print(str2) # heLLo worLLd
```
```python=
# 取得 list 中字串長度最短的字串
>>> min(['flower', 'flow', 'flight'], key=len)
'flow'
# 取得 list 中字串長度最長的字串
>>> max(['flower', 'flow', 'flight'], key=len)
'flower'
```
### Set
```python=
my_set = {1,2,3, (1,2,3), (1,2,3), "hello"}
print(my_set)
l_list = [1,2,3,4,5,5,8,8,7]
l_set = set(l_list) # 從 list 轉 set 把 dup 部份處理掉
print(l_set)
try:
mutable_set = {[1,2,3]} # set 不能有可改變的元素
except Exception as e:
print(e)
a = {} # 這個是建立 dict 不是 set
a = set() # 要用 set() 來建立 empty set
my_set.add(100) # 加一個元素
print(my_set)
my_set.update([100, 99]) # 加一組元素
print(my_set)
my_set.discard(99999) # 丟掉一個叫 99999 的元素,不存在沒關係
try:
my_set.remove(99999) # 砍掉一個叫 99999 的元素,不存在會噴錯
except Exception as e:
print(e)
print(my_set)
for i in range(len(my_set)):
print(my_set.pop()) # 一次噴一個出來,順序不一定
```
```python=
A = {1,2,3,4,5}
B = {4,5,6,7,8}
# Union 連集
print(A | B)
# Intersection 交集
print(A & B)
# Difference 只有 A 的,就是減去 B
print(A - B)
# Symmetric Difference A 和 B 連集,不要共有部份
print(A ^ B)
print((A | B) - (A & B))
```
```python=
apple_set = set("apple") # 等同於 {"a", "p", "l", "e"}
print(apple_set)
apple_set2 = {"apple"} # 不同於 {"a", "p", "l", "e"}
print(apple_set2)
print('a' in apple_set) # True
print('d' in apple_set) # False
```
### Dict
> 鍵值必須為不可改變的類型 (string, number, tuple) 且必須是唯一的
> 用 `[]` 來 indexing 當 key 不存在時會噴 exception ,而使用 `.get()` 則可以避免 exception 當 key 不存在時會回傳 None
```python=
# create dict
my_dict = {'name': 'John', 1: [2,3,4]} # key-value 可以是不同型別
# access the value by key
print(my_dict['name'])
try:
print(my_dict['hello'])
except:
print('key not exist')
print(my_dict.get('hello')) # result: None
# add new piar or update value
my_dict['hello'] = 'world'
my_dict['name'] = 'Ken'
print(my_dict)
# removing elements
print(my_dict.pop('name')) # 移除 'name': 'Ken' 這組 key-value 回傳 value
print(my_dict.popitem()) # 隨便移除 dict 中的一組 key-value
my_dict.clear() # 清空整個 dict
print(my_dict) # result: {}
# create dict from list and fill value
words = ['a','e','i','o','u']
dict_words = dict.fromkeys(words, True)
for key, value in dict_words.items(): # return (key, value) tuple
print(f"{key} => {value}")
for key in dict_words.keys():
print(key)
for value in dict_words.values():
print(value)
# update dict by the other dict (merge)
dict_words.update({'hello': 'world', 'a': False})
print(dict_words)
# check key exist
squares = {1: 1, 3: 9, 5: 25, 7: 49, 9: 81}
print(10 in squares) # result: False
print(1 in squares) # result: True
# count elements of dict
print(len(squares))
```
#### Dict comperhension
```python=
squires = {i: i*i for i in range(10)}
print(squires)
# combine with if
even_squires = {i: i*i for i in range(10) if i % 2 == 0}
print(even_squires)
test_dict = {'jack': 38, 'michael': 48, 'guido': 57, 'john': 33}
new_dict = {k: v for k, v in test_dict.items() if v > 40 and v < 50}
print(new_dict)
# combine with if... else...
test_dict = {'jack': 38, 'michael': 48, 'guido': 57, 'john': 33}
new_dict = {k: 'old' if v > 40 else 'young' for k, v in test_dict.items()}
print(new_dict)
# nest
d = {}
for x in range(1, 10):
d[x] = {y: x*y for y in range(1, 10)} # 9x9
print(d[5][8]) # 8*5 = 40
```
### ListNode
> python 沒有內建 link-list 因此可以透過簡單定義 Node Class 來自行達成實作
>
> ```python=
> class ListNode:
> def __init__(self, val=0, next=None):
> self.val = val
> self.next = next
>
> head = ListNode(1)
> head.next = ListNode(2)
> ```
```python=
from ListNode import ListNode
sample_list = [1, 2, 5, 3, 6, 8]
# 走訪整個 link-list
def show_link_list(l):
tail = l
while tail:
print(tail.val, end='->')
tail = tail.next
else:
print() # 換行
# create a link-list node from list
head = ListNode(sample_list[0])
tail = head # 初始,頭尾都指向同一個
for i in range(1, len(sample_list)):
next_node = ListNode(sample_list[i])
tail.next = next_node # 最舊的指向新建的
tail = next_node # 指向最後一個
show_link_list(head)
# link-list to list
tail = head
to_list = []
while tail:
to_list.append(tail.val)
tail = tail.next
# link-list to reverse int
# 將 link 一個一個走訪,每個乘 10 倍數 ... 1, 10, 100, 1000
multi = 1
tail = head
result = 0
while tail:
result += tail.val * multi
multi *= 10
tail = tail.next
print(result)
# int to link-list
# 透過 int 先轉 str 一格一格建立 link-list
sample_i = 998796
sample_i_str = str(sample_i)
head = ListNode(sample_i_str[0])
tail = head
for idx in range(1, len(sample_i_str)):
next_node = ListNode(sample_i_str[idx])
tail.next = next_node
tail = next_node
show_link_list(head) # result: 9->9->8->7->9->6->
# int reverse link-list
# 透過 int 除以 10 餘數取出一個一個餘數來建立 link-list
sample_i = 998796
head = ListNode(sample_i % 10)
sample_i //= 10
tail = head
while sample_i > 0:
next_node = ListNode(sample_i % 10)
sample_i //= 10
tail.next = next_node
tail = next_node
show_link_list(head) # result: 6->9->7->8->9->9->
```
>Link-list 的反轉 (reverse) 行為圖

```python=
# reverse link-list
prev = None
curr = head
while curr:
next = curr.next # 先記錄下一個,它要被換掉了
curr.next = prev # 把下一個指向 prev
prev = curr # prev 變成現在這個,準備給下個節點指向
curr = next # curr 可以指到下個了,當還有沒有節的條件
head = prev # curr 是 None 了,本來最後的節點變成是 prev 了
show_link_list(head) # result: 9->9->8->7->9->6->
```
```python=
#Link-list 的合併 (一個 Loop 操作兩個 Link-list)
def mergeTwoLists(list1: Optional[ListNode], list2: Optional[ListNode]):
head = None
if list1 is not None and list2 is not None: # 若都不是 None 就挑個小的當 head
if list1.val < list2.val:
head = ListNode(list1.val)
list1 = list1.next
else:
head = ListNode(list2.val)
list2 = list2.next
elif list1 is None and list2: # 其中一個是 None 那結果就是另一個
return list2
elif list2 is None and list1:
return list1
tail = head
while list1 and list2: # 只要兩個 link-list 都還有 node 跑到一邊沒有為止
if list1.val < list2.val:
tail.next = ListNode(list1.val)
list1 = list1.next
else:
tail.next = ListNode(list2.val)
list2 = list2.next
tail = tail.next
if list1: # 若list1 還有 node 直接接過去要 merge 的尾
tail.next = list1
if list2: # 若list2 還有 node 直接接過去要 merge 的尾
tail.next = list2
return head
```
## Object-Oriented Programming
### Classes and Objects
> - class object
> - ex: class Person
> - instance object
> - ex: p = Person()
> - function object
> - ex: Person.greet
> - method object
> - ex: p.greet
```python=
class Person:
"""this is person class"""
pass
print(Person.__doc__) # result: this is person class
```
> `self` 不是關鍵字,在 OOP 中它只是個佔位符,但傳統上還是使用 `self` 這個字
> obj.method() == class.function(obj)
```python=
class Person:
age = 10
def greet(self):
print("hello")
p = Person()
print(Person) # <class '__main__.Person'> 在 main 下的 Person class object
print(Person.greet) # <function Person.greet at 0x10298a310> 將 class 視為 namespace 下使用 function 但必須傳入第一個參數為 Person 的物件實例
print(Person.age) # class attribute
print(p.greet) # <bound method Person.greet of <__main__.Person object at 0x10298efd0>> 實列物件已經挷定到類別物件函式
p.greet() # 等同於 Person.greet(p)
Person.greet(p) # 等同於 p.greet()
```
> python 的 garbage collection 是將 reference 到該 object 的 variable 砍掉,而在 memory 中的 object 沒有被 bind ,所以 garbage collection 機制自動去清掉它
>
> ```python=
> h = Hello() # create a Hello object and use h to bind it.
> del h # delete the bind relation between h and Hello object
> # after that, Hello object in memory will be destroyed by garbage collection
> ```
> 存取 object attribute 不存在時,會 reference 到 class attribute 但是當改變不存在的 object attribute 時,則會 **建立** 新的 object attribute
```python=
class Person:
type = "citizen"
def __init__(self, age: int):
self.age = age
def get_age(self):
return self.age
print(Person.type) # result: citizen
p1 = Person(30)
p2 = Person(27)
print(p1.type, p2.type) # result: citizen citizen
Person.type = "not citizen"
print(p1.type, p2.type) # result: not citizen not citizen (object attribute not exist, reference to class attribute)
p1.type = "p1 citizen" # 建立了 attribute of object
print(p1.type, p2.type) # result: p1 citizen not citizen
```
### Encapsulation
```python=
class Account:
def __init__(self):
self.passwd = '0000'
def get_password(self):
return self.passwd
def set_password(self, new_pwd):
self.passwd = new_pwd
class NewAccount:
'''better practice to encapsule'''
def __init__(self):
self.passwd = '0000'
@property
def password(self):
return self.passwd
@password.setter
def password(self, new_pwd):
self.passwd = new_pwd
def set_password(self, new_pwd):
self.password = new_pwd
a = Account()
print(a.get_password())
a.set_password('1234')
print(a.get_password())
new_a = NewAccount()
print(new_a.password)
new_a.password = '5678' # setter
print(new_a.password)
new_a.set_password('9999') # call set function
print(new_a.password)
```
### Inheritance
> ```python=
> Employee.__init__(self, name, year) # equal below
> super().__init__(name, year) # this is good practice
> ```
```python=
class Employee:
def __init__(self, name, year):
self._name = name
self._year = year
def salary(self):
base = 100
for i in range(self._year):
base = base * 1.03
return base
class Engineer(Employee):
def __init__(self, name, year):
# Employee.__init__(self, name, year)
super().__init__(name, year)
def get_detail(self):
return f"name: {self._name} salary: {self.salary()}"
def salary(self): # override
return super().salary() * 1.1 # call parent salary()
andy = Engineer('Andy', 10)
print(andy.get_detail())
```
### Class method and Static method
> Class Method
> - 和 class variable 一樣用在 class 本身,而不是產生的物件上
> - 像想知道該 class 產生多少物件,就適點用 class method and class variable
> - 可做為建構子的替代,透過不同的參數來產生物件
> - storage.from_connection_string() 建構 storage object
> ```python=
> @classmethod
> def method_name(cls):
> # cls is the class reference
> # cls() == Class()
> ```
> Static Method
> - 想建立一個方法,但不牽涉物件或類別本身,就是不會改變物件,或類別狀態
> - 常用來建立類別的工具集 (以外部的角度來使用該物件的操作)
> - Car.validate(car_object) 一個靜態方法操作物件
> ```python=
> @staticmethod
> def method_name(parameter1):
> # only deal with the parameter1
> # don't have any relationshiop with class or object
> ```
> Class Method vs Static Method
> | method | class | static |
> |---|---|---|
> | 參數 | cls | 沒有 |
> | 存取狀態 | 類別狀態 | 無法存取狀態 |
> | 標記方式 | @classmethod | @staticmethod |
> | 用途 | 適用於 factory pattern | 適用以外部角度操作物件 |
> **NameError: name 'Class' is not defined**
> 如果有用到 typing hint 的話,在 class 中要 reference 自己的類別時,會發生這個錯誤。這是因為該「類別物件」還沒與「class name」挷定
>
> - 3.7 以下可以使用
> ```python=
> def is_vw_car(car: 'Car') -> bool: # 改為 string 來解決
> ```
> - 3.7 以上可以加入以下 annotations 來解決
> ```python=
> from __future__ import annotations
> ```
> - 3.10 就會自動解決這個問題了
```python=
class Car:
_car_counter = 0 # class variable
def __init__(self, brand):
self._brand = brand
Car._car_counter += 1
@classmethod
def vw_brand(cls):
return cls('Volkswagen') # return Car object
@classmethod
def get_car_count(cls) -> int:
return cls._car_counter
def get_brand(self) -> str:
return self._brand
h = Car('Honda')
vw = Car.vw_brand()
for i in [h, vw]:
print(i.get_brand())
print(Car.get_car_count()) # result: 2
```
```python=
from __future__ import annotations
class Car:
def __init__(self, brand):
self._brand = brand
def get_brand(self) -> str:
return self._brand
@classmethod
def vw_brand(cls):
return cls('Volkswagen') # return Car object
@staticmethod
def is_vw_car(car: Car) -> bool: # check objct is vw
return car.get_brand() == 'Volkswagen'
h = Car('Honda')
vw = Car.vw_brand()
print(Car.is_vw_car(h))
print(Car.is_vw_car(vw))
```
### Class and Object create flow
> - 所有定義的 class 都會自動繼承 object class 因此實例的物件都具有 object 基本功能
> - \_\_init__
> - \_\_new__
> - \_\_str__
> - ...
> - 在 python 中所有的東西都是物件
> - a = 9 表示 9 是 class int 的實例化,並透過 a 來 reference 它
> - object or class 都可以使用 type() 來確認它的 class 是那個
> - type(9) <class 'int'>
> - type(int) <class 'type'>
> - type(type) <class 'type'>
> - type(h) 等同於 h.\_\_class__
> - type class (MetaClass)
> - 在 python 世界中,所有建立的 class 都是 type class 的物件 (它的實例化) 所以 type 又稱 metaclass
>
> - class, object, metaclass 的關係
> 
> - Human class 是從 Metaclass type class 生出的 object
> - 這個 Object 是個 class 並繼承了 object class 因此具有 \_\_init__, \_\_new__, \_\_str__ 等 methods
> - 透過 class 這個物件,再實例化物件就是 human_obj
> - human_obj 同時也有 object 的所有 methods
```python=
class Human:
pass
# Human 繼承 object 它是 class 是 type
print(dir(Human))
'''
['__class__', '__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__']
'''
# __bases__ 可以列出該 Class 的繼承列表
print(Human.__bases__)
# object 提供許多 method 如 __new__, __init__, __str__ 這些都會被定義的 class 所繼承
print(dir(object))
'''
['__class__', '__delattr__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__init_subclass__',
'__le__', '__lt__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__']
'''
# 9 是物件,而 a 是這個物件的 reference
a = 9
print(type(a)) # <class 'int'>
print(a.__class__) # <class 'int'> 同上
# 建立 Human 物件,而 h 是這個物件的 reference
h = Human()
print(type(h)) # <class '__main__.Human'>
# 用來建立物件的 class 它本身自己也是物件,是 class type 的物件
# class type 用來建立所有新的類別(class)的類別,它稱為 Metaclass
print(type(Human)) # <class 'type'>
print(type(int)) # <class 'type'>
print(type(float)) # <class 'type'>
print(type(type)) # <class 'type'>
print(type(object)) # <class 'type'>
def simple_function():
pass
print(type(simple_function)) # <class 'function'>
class Car:
color = 'blue'
def __init__(self, color):
self.color = color
c1 = Car('red')
print(c1.color)
# 物件建立流程等同於以下行為
c2 = object.__new__(Car) # step1. create object
print(c2.color) # result: blue (當 object variable 不存在,會 reference class variable)
Car.__init__(c2, 'yellow') # step2. init object
print(c2.color) # result: yellow
h = type.__call__(Human) # 等同於 h = Human()
print(h)
```
> 物件產生問題
> - \_\_init__ 沒有回傳任何的東西,物件怎麼產生的
> - \_\_init__ 中的第一個參數 self 怎麼傳進來的
>
> 物件產生流程
> - step1.
> - 建立物件
> - 使用 \_\_new__ method 來建立物件
> - 如果 class 沒有定義 \_\_new__ 則會使用繼承的 object.\_\_new__
> - \_\_new__
> - is a static method 可以直接 object.\_\_new__(cls) 呼叫
> - cls 為「類別物件」由 metaclass 產生出來的類別物件
> - 它將為 cls 類別配置記憶體,並建立/回傳該物件
> - 會使用 super().\_\_new__(cls) 來呼叫
> - 不會做任何物件初始化動作,只會建立
> - 可以透過 override \_\_new__ 來自己處理建立並初始物件的動作
> - 它不需要 @staticmethod 裝飾,它就是個 static method 是個特殊的存在
> - step2.
> - 初始物件
> - 使用 \_\_init__ method 來初始物件
> - 不回傳任何資料
> - 第一個參數為該類別實例化的物件
> - \_\_init__ 進行初始化,不回傳任何東西
> - 參數接收數量 \_\_new__ 與 \_\_init__ 需要一樣
>
> 透過 type.\_\_call__(cls) 串起整個流程
> - Human() 等同於 type.\_\_call__(Human) 因為 Human 這個 類別物件是由 metaclass type 生成出來的,因此它也繼承了 type.\_\_call__() 的方法
> - 當 Human() 發生了以下事情
> 1. type.\_\_call__(Human)
> 2. Human.\_\_new__(Human) 建立物件回傳物件給 type.\_\_call__
> 1. obj = object.\_\_new__(Human)
> 2. return obj
> 3. Human.\_\_init__(obj, parameters) 呼叫 init 做初始化
> 4. obj .name = name 進行初始
> 5. type.\_\_call__(Human) 結束回傳 obj
> 
```python=
# 定義建立物件時同時初始資料,因此不用實作 __init__
class Human:
def __new__(cls, name):
# cls = "Human"
obj = super().__new__(cls) # create obj
obj.name = name
return obj # 必須要回傳,否則就沒有 reference 了
h = Human('ken')
print(h) # <__main__.Human object at 0x103448ca0>
print(h.name) # ken
# 也可以這樣建立
h = object.__new__(Human) # 土炮建立物件
h.name = 'John' # 土炮初始物件的 attributes
print(h)
print(h.name) # John
```
```python=
# 標準做法定義 __new__, __init__ 讓 type.__call__ 來啟動整個流程
class Human:
def __new__(cls, *args, **kwargs):
print(f"new method: {args} {kwargs}")
obj = super().__new__(cls)
return obj
def __init__(self, first_name, last_name):
print(f"init method: {first_name} {last_name}")
self.first_name = first_name
self.last_name = last_name
def __str__(self):
return f"{self.first_name} {self.last_name}"
h = Human('John', 'cena')
print(h)
h2 = type.__call__(Human, 'Ken', 'chang')
print(h2)
```
```python=
# 類別實作 __call__ 的方法,讓物可以 callable
class Human:
def __init__(self, name):
self.name = name
def __call__(self, *args, **kwargs):
print(f"{self.name} {args}")
h = Human('ken')
h('ok')
h.__call__('ok') # same above
Human.__call__(h, 'ok') # same above
```
```python=
# 透過 __new__ 實現 singleton
class Human:
__inst = None
def __new__(cls, *args, **kwargs):
if cls.__inst is None:
cls.__inst = super().__new__(cls)
return cls.__inst
def __init__(self, name):
self.name = name
def __str__(self):
return f"{id(self)} {self.name}"
h = Human('Ken')
print(h) # 4336594704 ken
h2 = Human('John')
print(h) # 4336594704 John
```
## Keywords
### Function
> 在 python 中定義 function 必須早於呼叫
> 沒有任何的 return value 時 caller 會拿到 None
```python=
def greet(name): # one argument
"""
This function greets to
the person passed in as
a parameter
"""
print("Hello, " + name + ". Good morning!")
greet('Paul')
print(greet.__doc__) # 印出說 function 的說明
```
```python=
def greet(name, msg="Good morning!"): # default argument
# default argument 必須在 non-default 的後面
print("Hello", name + ', ' + msg)
greet("Kate")
greet("Bruce", "How do you do?")
# 2 keyword arguments
greet(name = "Bruce",msg = "How do you do?")
# 2 keyword arguments (out of order)
greet(msg = "How do you do?",name = "Bruce")
# 1 positional, 1 keyword argument
# 當 default argument 很多時,這個較方便使用, 直接指定 non-default arguments
greet("Bruce", msg = "How do you do?")
```
> keyword arguments must follow positional arguments.
>
> 參數的順序
> 1. positional arguments ex: name
> 2. arbitraty arguments ex: *name
> 3. default arguments ex: name='John'
> 4. keyword arguments ex: **name
```python=
# abrbitrary argument
def show_name(*names):
print(type(names))
print(names)
for name in names:
print(name)
show_name('dachi', 'chris', 'darren')
# keyward argument
def show_name2(prefix='@', **names):
print(type(names))
for k, v in names.items():
print(f"{prefix}{k} {v}")
show_name2(prefix='hello ', dachi='chang', lumi='chang') # hello dachi chang
# positional, arbitrary, default, keyword arguments
def show_name3(prefix, *dect, suffix='.', **names):
for d in dect:
print(f"{d} {prefix}: {names['first']}-{names['last']} {suffix}")
show_name3('Hi', '@@', '%%', first='dachi', last='chang', suffix='...') # @@ Hi: dachi-chang ...
```
### Global
> - 在該 module 下所有的 variable, class, constants, function 都是 global
> - 當 import 別的 module 到目前的 namespace 時,必須以 MODULE.VARIABLE 存取該變數
> - ex: config.a
> - ex: config.b
> - 當不具 namespace 進行 import 時,該變數可能被現在的 namespace global variable 蓋掉
> - ex: from config import a, a = 10 此時 a 就被蓋掉了
```python=
def outer_function():
global a # reference module global a
a = 20
def inner_function():
global a # reference module global a
a = 30
print('a =', a)
inner_function()
print('a =', a)
a = 10
outer_function()
print('a =', a)
```
### Iterator and Generator
> - Iterable
> - 可執行 Iteration 的 objects 都稱為 Iterable(可當專有名詞)。參照 官方文件 提及,是指可以被 for loop 遍歷的 objects。以程式碼來說,只要具有 `__iter__` 或 `__getitem__` 的 objects 就是 Iterable。
> - 像是 list, range 是 Iterable
> - Iterator
> - 遵照 Python Iterator Protocol 的 objects。以 Python3 而言,參照 官方文件,只要具有 `__iter__` 和 `__next__` 的 objects 皆為 Iterator。
> - Iterator 是 Iterable 的 subset。
> - 當 Iterable 呼叫了 `__iter__()` 回傳的 object 為 Iterator
> - 像 map() 就是回傳 Iterator
> - Generator
> - 一個 routine 可以控制迴圈的迭代行為,它就像是個 function 可以回傳值
> - 出現在 python3
> - Generator 可產生一個 Iterator 物件
> - 當一個 function 中帶有 yield 時,該 function 就會自動 return 一個 Gerenator 的 object,而這 generator 自帶 `__next__` ,是一個 Iterator。
```python=
# iterator 和 generator 的關係
import sys
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # bounded, in memory, iterator object
print(sys.getsizeof(x)) # result: 152
print(sys.getsizeof(range(1, 11))) # result: 48
for element in x: # iterate in iterator object
print(element)
for i in range(1, 11): # generator
print(i)
y = map(lambda i: i**2, x) # generator
u = list(y) # will iterate x and create a result list, generator STOP
# u = [i for i in x]
print(u) # result: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
for i in y: # 直接結束,因為 generator 已終止
print(f"i in y: {i}")
for i in u: # 迭代已建立的 list object ,佔用記憶體空間,可以重複使用
print(f"i in u: {i}") # 印出結果
```
```python=
# iterator 如何運作
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
y = map(lambda i: i**2, x)
print(next(y)) # next() 用在 iterator object 以取得回傳值
print(y.__next__()) # same above
print(next(y))
print(next(y))
for i in y: # 只是幫忙 call next() 而己
print(f"use for to print: {i}")
# 等同於以下 while loop
# while True:
# try:
# value = next(y)
# print(value)
# except StopIteration:
# print('Done')
# break
```
```python=
# range generator
x = range(1,11)
# next(x) x is not iterator object
y = iter(x)
print(type(x)) # <class 'range'>
print(type(y)) # <class 'range_iterator'> Iteratable Generator Object
print(next(y)) # 先取得 iterator object 再 next() result: 1
for i in y:
print(i) # result 2 ~ 10
try:
print(next(y))
except StopIteration:
print("no more value")
print('-'*50)
for i in x: # equal for i in iter(x) on the fly, 每次都產生一次 generator object
print(i)
for i in x: # 因為不同的 generator object 所以每次使用都是新的
print(i)
# 可以說 y 是產了一次 generator object 被重複使用
# x 是設計,但是未實例化 generator object 當 for loop 它會自動呼叫 iter(x) 讓它實例化成 generator object 但只有在該 for loop 使用
```
```python=
# 實作 Iterator
class MyIter:
def __init__(self, n):
self.n = n
def __iter__(self):
self.current = -1
return self
def __next__(self):
self.current += 1
if self.current >= self.n:
raise StopIteration
return self.current
x = MyIter(5)
print(next(iter(x))) # 每次呼叫都會讓 current = -1 重新設定一次 result: 0
print(next(iter(x))) # result: 0
print(next(iter(x))) # result: 0
for i in x: # equal: for i in iter(x) 這讓 current = 1 重新設定一次,所以每次 for loop 都能印
print(i)
print('-'*50)
class MyIter2: # 另外一種實作
def __init__(self, n):
self.max = n
self.index = 0
def __iter__(self):
return self
def __next__(self):
self.index += 1
if self.index > self.max:
raise StopIteration
return self.index
def __str__(self):
return f"current index: {self.index}"
a = MyIter2(5)
for i in a: # 呼叫 iter(a) 的時候拿到 self
print(i) # result: 1~5
print(a)
for i in a: # 呼叫 iter(a) 的時候拿到 self 的 index 是 6 了
print(i) # 已經 StopIteration
```
```python=
# 透過 yield 實作 generator 產生 iterator 物件
def gen(n):
for i in range(n):
yield i
x = gen(5)
y = iter(x)
print(type(x)) # <class 'generator'>
print(type(y)) # <class 'generator'>
print(next(x)) # result: 0
print(next(x)) # result: 1
print(next(x)) # result: 2
print(next(x)) # result: 3
for i in x:
print(i) # result: 4
print('-'*50)
def gen2():
yield 1 # 把 1 回傳之後,暫停執行,等待下次再呼叫 gen2() 時,從上次回傳點後再執行
print('back to yield 1')
yield 10 # 回傳 10 之後,又暫停執行
print('back to yield 10')
yield 100
print('back to yield 100')
yield 1000
print('back to yield 1000')
yield 10000
print('back to the last yield')
for i in gen2():
print(i)
print('-'*50)
u = (i for i in range(10)) # 是個 generator 的語法糖,讓你不用寫 def 和 yield
print(x) # <generator object gen at 0x100301cf0>
print(u) # <generator object <genexpr> at 0x100301c80>
print(next(u)) # result: 0
for i in u:
print(i) # result: 1 ~ 9
```
### Yield
> - yield 只能出現在 function 裡
> - call 帶有 yield 的 function 會回傳一個 Generator object
> - Generator object 是一個 Iterator,帶有 `__iter__` 和 `__next__` attributes
> - yeild 除了可傳出值外,也可以接受由外部輸入的值,利用 .send() 即可同時傳入值也傳出值,讓 Generator object 可和外部進行雙向溝通,可以傳出也可以傳入值
> - 和 return 搭配的 function,就是吃進 input 然後輸出 output,life cycle 就會消失,yield 的寫法可以視為擴充 function 的特性,使其具有「記憶性」、「延續性」的特質,可以不斷傳入 input 和輸出 output,且不殺死 function
> - 第一次 call next(generator) 執行內容等價於將原 function 執行到第一次出現 yield 之處,「暫停」執行,並返回 yield 後的值
> - 第二次之後每次 call next(generator) 都會不斷從上次「暫停」處繼續執行,直到 function 全部執行完畢並 raise StopIteration
```python=
# get and send to generator
def my_generator(value=0):
while value < 10:
get_value_from_send = yield value
value += 1
print(f"get value: {get_value_from_send}")
gen = my_generator()
print(next(gen)) # 取得 value, result: 0
print(gen.send(5)) # 對 generator 傳入值,並取得結果,相當於 send() 之後再 next(), result: 5 1
print(next(gen)) # 沒有 send(), result: get value None, 2
print(gen.send(100)) # result: get value: 100 3)
print(next(gen)) # 4
print(next(gen)) # 5
print(next(gen)) # 6
print(next(gen)) # 7
print(next(gen)) # 8
print(next(gen)) # 9
try:
print(next(gen)) # result: StopIteration
except StopIteration:
print("StopIteration")
```
### Import
> - 檔案稱為 module
> - 資料夾稱為 package
> - class, function, variable, object, constants 都能夠被 import 進來
> from PACKAGE.MODULE import * is not a good programming practice.
```python=
>>> import math # import module
>>> from math import pi # import module's only pi object
>>> from variable_scope.scope import outer_function # import variable_scope package's scope module's outer_function
```
### Package and Import
> python 3.3 以後不強制要有 `__init__`.py 檔案 (from Python 3.3+ supports Implicit Namespace Packages that allows to create a package without an `__init__`.py file.)
> 幾個重要的原則
>
> - 避免在 Module 中放置定義以外的東西 Module 是用來被 Import 後執行的,執行的程式碼最好放在外部,否則容易發生執行上的混亂
> - 同一個 Package 中的 Module 互相引用使用 **Relative Import**
> - 不同 Package 中的 Module 互相引用使用 **Absolute Import**
> - 執行檔(main.py)引用模組使用 **Absolute Import**
>
>> 只有同一個 Package 中的 Module 要互相引用,才用 Relative Import,否則都用 Absolute Import
> `__init__`.py 可以做為 package 提供對外的接口,可用於隱藏一些 sub module 的實現,有點像是 Facade 的概念
```
Layout of package
.
├── main.py
├── my_package
│ ├── sub_pkg1
│ │ ├── module_a.py
│ │ └── module_b.py
│ └── sub_pkg2
│ └── module_c.py
└── some_package
├── __init__.py
└── sub_pkg1
├── module_d.py
└── module_e.py
```
```python=
# my_package/sub_pkg1/module_a.py
from .module_b import bar
# 使用相同 package 的另外一個 module_b 的 bar
# 採 relative import
def foo():
print("module a foo.", bar())
```
```python=
# my_package/sub_pkg1/module_b.py
def bar():
return "moduel b return bar."
```
```python=
# my_package/sub_pkg2/module_c.py
from my_package.sub_pkg1.module_a import foo
# 用到同 package 不同的 sub package 的 module
# 採 absolute import
def show_foo():
print("show foo()")
foo()
```
```python=
# some_package/sub_pkg1/module_d.py
from my_package.sub_pkg1.module_a import foo
# 用到不同 root package 的 module 功能
# 採 absolute import
def decorate_foo():
print(">"*20)
foo()
print("<"*20)
```
```python=
# some_package/sub_pkg1/module_e.py
from .module_d import decorate_foo
# 用到同 package 不同 module 採 relative import
def decorate_foo_show():
decorate_foo()
```
```python=
# some_package/__init__.py
from .sub_pkg1.module_e import decorate_foo_show
# __init__ 用來 export package 的功能
# 對所有 scope 以下的都採 relative import
```
```python=
# main.py
from my_package.sub_pkg2.module_c import show_foo # import function
from some_package.sub_pkg1 import module_e # import module
import some_package # import package __init__.py
show_foo()
module_e.decorate_foo_show() # 透過 module namespace 使用功能
some_package.decorate_foo_show()
```
### Pass
> - 對 python 說來就像是 no operation (NOP) 會執行,但是沒動作
> - 一般用來當 placeholder
> - 用在未來將會實作,現在先 pass
```python=
for val in sequence:
pass
def function(args):
pass
class Example:
pass
```
### Lambda (Anonymous Function)
> lambda arguments: expression
>
> expression 結果回傳,可以是 int, str, bool 任型別
>
> ex: double = lambda x: x*2
```python=
double = lambda x: x * 2
print(double(5))
```
```python=
# 對 filter 來說,只要 expression 為 true 就會被當成 filter 的條件
# new_item = (x % 2 == 0) ? return x
a_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filter_generator = filter(lambda x: x % 2 == 0, a_list) # return a generator
print(filter_generator)
b_list = list(filter_generator)
print(b_list) # [2, 4, 6, 8, 10]
```
```python=
# 對 map 來說每個 item 都會執行 expression 的動作
# new_item = str(x)
a_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
map_generator = map(lambda x: str(x), a_list)
print(map_generator)
b_list = list(map_generator)
print(b_list) # ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
```
### Exceptions
```python=
print(dir(locals()['__builtins__'])) # 列出所有 built-in exceptions
```
> 如果 calling path 為 a() -> b() -> c() 而當 exception 從 C 發生,如果沒有 handle 的話,會依 c() -> b() -> a() 一路彈回去,如果在 a() 沒有 handle 的話,就直接停下來了
> - Try ... Else 處理沒有發生任何 exception 的狀況,在 Else 中發生 exception 要另外處理
> - Try ... Finally
```python=
random_list = ['a', 0, 2]
for entry in random_list:
try:
r = 1/int(entry)
print(r)
break # break if no exception occur
except ValueError:
print('value error')
except ZeroDivisionError as e:
print(e)
except Exception as e:
print(e.__class__)
# raise exception
try:
a = int(input("enter a postive number: "))
if a <= 0:
raise ValueError("is not a positive number!")
except Exception as e:
print(e)
# try ... else and ... finally
# case 1 without exception
try:
print("hello")
except Exception as e:
print(e)
else:
print("world") # 只有在沒發生 exception 才執行
finally:
print("done") # 不管有沒有發生 exception 都執行
# case 2 with value error exception
try:
raise ValueError("value error!!")
except Exception as e:
print(e) # 處理 exception
else:
print("world") # 發生了 exception 所以不執行
finally:
print("done") # 不管有沒有發生 exception 都執行
```
```python=
# customization exception
class SalaryNotInRangeError(Exception):
def __init__(self, salary, message='salary is not in (5000, 15000) range'):
self.salary = salary
self.message = message
super().__init__(self.message)
def __str__(self):
return f'{self.salary} -> {self.message}'
salary = int(input("Enter salary amount: "))
try:
if salary < 5000 or salary > 15000:
raise SalaryNotInRangeError(salary)
except SalaryNotInRangeError as e:
print(e) # result: 100 -> salary is not in (5000, 15000) range
print(e.salary) # result: 100
```
## Built-in Functions
### dir
> dir([object])
>
> 用在列出 object attributes 和 methods
> - 當該 object 有 `__dir__`() 方法時,會呼叫它,且必須回傳 list
> - 如果 object 沒有 `__dir__`() 方法時,會去找 `__dict__` 並以 list 列出
> - 如果 dir() 不帶 object 時,將列出該 scope 所有的 names
```python=
from pprint import pprint
x = dict(a=10, b=20)
pprint(dir(x))
#['__class__',
# '__class_getitem__',
# '__contains__',
# '__delattr__',
# '__delitem__',
# '__dir__',
# '__doc__',
# '__eq__',
# '__format__',
# '__ge__',
# '__getattribute__',
# '__getitem__',
# '__gt__',
# '__hash__',
# '__init__',
# '__init_subclass__',
# '__ior__',
# '__iter__',
# '__le__',
# '__len__',
# '__lt__',
# '__ne__',
# '__new__',
# '__or__',
# '__reduce__',
# '__reduce_ex__',
# '__repr__',
# '__reversed__',
# '__ror__',
# '__setattr__',
# '__setitem__',
# '__sizeof__',
# '__str__',
# '__subclasshook__',
# 'clear',
# 'copy',
# 'fromkeys',
# 'get',
# 'items',
# 'keys',
# 'pop',
# 'popitem',
# 'setdefault',
# 'update',
# 'values']
```
### type
> **type is used to find the type or class of an object.**
>
> type(object) #type with single parameter
> type(name, bases, dict) # type with 3 parameters
>
> 可以列出 object 的型別,也可以建立新的型別
```python=
>>> l = [1,2]
>>> type(l) # 列出 l 的型別
<class 'list'>
>>> X = type('X', (), dict(a=10, b=20)) # 建立型別
>>> x = X()
>>> x.__dict__ # 列出物件的 dict
{}
>>> X.__dict__ # 列出類別的 dict
mappingproxy({'a': 10, 'b': 20, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'X' objects>, '__weakref__': <attribute '__weakref__' of 'X' objects>, '__doc__': None})
>>> type(X) # X 是個型別
<class 'type'>
>>> type(x) # x 是個實例
<class '__main__.X'>
```
### print
> print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
```python=
a = 10
b = 20
c = 30
print("helloworld")
print("hello", a, b) # result: 10 20
print(1, "hello", 3.4, sep='#', end='&\n') # result: #hello#3.4&
print('The value of x is {} and y is {}'.format(a,b)) # result: The value of x is 10 and y is 20
print('Hello {a}, {b}'.format(a=a, b=b)) # result: Hello 10, 20
print("a = %d, b = %d" %(a, b), "c = %d" %c) # result: a = 10, b = 20 c = 30
print(f"a = {a}, b = {b}") # result: a = 10, b = 20
```
```python=
# raw string
print(r"This is \x61 \ngood example") #This is \x61 \ngood example
```
### input
> input([prompt])
```python=
x = input("enter a number for x: ")
print(f"x = {x}")
```
### file
| 模式 | 可做操作 | 若檔案不存在 | 是否覆蓋 |
|---|---|---|---|
| r | 只能讀 | 報錯 | - |
| r+ | 可讀可寫 | 報錯 | 是 |
| w | 只能寫 | 建立 | 是 |
| w+ | 可讀可寫 | 建立 | 是 |
| a | 只能寫 | 建立 | 否,追加寫 |
| a+ | 可讀可寫 | 建立 | 否,追加寫 |
```python=
with open("test.txt", "w+") as f:
f.write("Hello world\n")
p1 = f.tell()
f.write("How are you\n")
f.seek(p1) # seek the position p1
f.write(" ok") # override the word How
f.flush() # flush to file
f.seek(p1) # back to position p1
result = f.readline().strip() # p1 position to end of the new line
f.seek(p1)
f.truncate() # truncate all data after p1 position
print(result)
print('-'*50)
with open("test2.txt", "w+") as f:
f.write("hello\n")
f.write("world\n")
f.write("good\n")
f.write("ok\n")
f.write("python\n")
f.seek(0)
l1 = f.readline() # read 1 line
ls = f.readlines() # read all line and create list
f.seek(0)
data = f.read() # read entire file data to a str (including newline)
print(l1.strip()) # result: hello
print(ls) # result: ['world\n', 'good\n', 'ok\n', 'python\n']
print(data.strip().split("\n")) # result: ['hello', 'world', 'good', 'ok', 'python']
```
## Basic built-in Module
### os
```python=
import os, shutil
current_path = os.getcwd()
print(current_path) # current path
ls = os.listdir() # list the dir in current path
print(ls)
if 'test' not in ls:
os.mkdir('test') # create a new dir which is named test
os.chdir('test') # change into test dir
with open('test.txt', 'w') as f:
f.write('helloWorld')
print(os.getcwd()) # current path
os.chdir(current_path) # back to the origin current path
shutil.rmtree('test')
# create dir path (super mkdir)
os.makedirs('a/b/c/d/e/f', exist_ok=True)
shutil.rmtree('a')
```
### json
> - `.loads()` json string to dict
> - `.dumps()` dict to json string
> - `.load()` json file to dict
> - `.dump()` dict to json file
```python=
import json
p = '{"name": "Bob", "languages": ["Python", "Java"]}'
# load json from string
dict_p = json.loads(p)
print(json.dumps(dict_p, indent=4)) # dict to json string with indent=4
with open("data.json", "w") as f:
json.dump(dict_p, f) # dump dict to json file
with open("data.json", "r") as f:
dict_p_from_file = json.load(f) # load json to dict from file
print(dict_p_from_file['languages'])
```
### math
```python=
>>> math.pow(2,10) # x 的 y 次方
1024.0
>>> math.factorial(3) # 階乘
6
>>> math.pi # 常數 PI
3.141592653589793
>>> math.sqrt(9) # 平方根
3.0
>>> math.ceil(123.1) # 無條件進位
124
>>> math.floor(3.92) # 無條件捨去
3
>>> abs(-1) # 絕對值
1
>>> round(1.234) # 四捨五入
1
>>> round(1.534)
2
```
### random
```python=
>>> random.random() # 隨機 0 ~ 1 的浮點數
0.1274161084834875
>>> random.randint(1, 10) # 隨機 1 ~ 10 整數
7
>>> random.choice('hello') # 隨機從字串中取的一字元
'l'
>>> random.choice(['ok','yes','no']) # 隨機從 list 取得一個 item
'yes'
>>> random.sample(['ok','yes','no'], 2) # 隨機從 list 取 2 個 item
['yes', 'ok']
>>> l = [1,2,3,4,5]
>>> random.shuffle(l) # 對 l 進行 shuffle
>>> l
[5, 1, 3, 4, 2]
```
```python=
# 產生 a-zA-Z0-9 長度為 10 的亂數字串
>>> import random
>>> import string
>>> ''.join(random.sample(string.ascii_letters + string.digits, 10))
'7QKtzJB4kI'
```
### re
### datetime
> - datetime (module)
> - datetime (class) `date + time`
> - now() (class method)
> - date (class)
> - today() (class method)
> - time (class)
> - timedelta (class)
## Python third-party packages
### click
> function name 怎麼從 options 定義中來的
> - 如果定義了一個沒有 `-` or `--` 的 name ,則優先取用
> - ex: @click.option('-t', 'to') # to 為 function 的 argument
> - 如果前綴為 `--` 則 argument 為拿掉前綴的 name
> - ex: @click.option('-t', '--too') # too 為 function 的 argument
> - 最後是前綴為 `-` 則 argument 為拿掉前綴的 name
> - ex: @click.option('-t') # t 為 function 的 argument
```python=
import click
from google.cloud import translate_v2
@click.group()
@click.option('-t', '--target', type=str, required=True, default='zh-tw', help="iso 639-1", show_default=True)
@click.option('--cert', type=str, help="path for gcp api service account json file.", required=True)
def cli(target, cert):
global translate_client
translate_client = translate_v2.Client.from_service_account_json(cert)
translate_client.target_language = target
@cli.command()
@click.argument('word', required=True, type=str)
def translate(word):
translate_format(word)
@cli.command()
@click.argument('file_path', required=True, type=str)
def translate_by_file(file_path):
try:
with open(file_path, "r") as f:
words = f.read().splitlines()
except FileNotFoundError:
print('file not exist!!')
exit(1)
for word in words:
translate_format(word)
def translate_format(word):
result = translate_client.translate(word)
print(f"{result['input']}\t{result['translatedText']}")
if __name__ == '__main__':
cli()
# result:
# Usage: main.py [OPTIONS] COMMAND [ARGS]...
#
# Options:
# -t, --target TEXT iso 639-1 [default: zh-tw; required]
# --cert TEXT path for gcp api service account json file. [required]
# --help Show this message and exit.
#
# Commands:
# translate
# translate-by-file
```
### requests
## Tools
### autopep8
## Document
## Topic Document
- [Pipenv HowTo](https://hackmd.io/@yLc3McFtQvyjq58xjHT7SQ/rygHU8mOK)
- [Built-in functions reference](https://www.programiz.com/python-programming/methods/built-in)
- [Understanding object instantiation and metaclasses](https://www.honeybadger.io/blog/python-instantiation-metaclass/)
- [The metaclass in python](https://dboyliao.medium.com/%E6%B7%BA%E8%AB%87-python-metaclass-dfacf24d6dd5)