# [AIdrifter CS 浮生筆錄](https://hackmd.io/s/rypeUnYSb) <br> NTU Adavanced Python Programming
# 5/9
:::info
授課老師:李根逸
投影片版權接來自feis.studio
Ref : https://feis.studio/
:::
:::warning
強烈推薦去台大上李根逸老師的課!
https://feis.studio/#/
:::
## 從hellow world開始
### print可以印什麼?
- print為built in function

```python
print(print) # <built-in function print>
print(3+3) # 6
print('3' + '3') # 33
```
```python
print(3 + '3') # 型別錯誤 python有形態安全
```
### '' 和 "" 差異是?
單引號 可以包雙引號印
雙引號 可以包單引號印
或是單引號內 加上escape sequence(跳脫序列)`\`去印單引號(因為太醜 所以設計可以互包
Ref: https://docs.python.org/3/library/functions.html
## type, object and id
### type
- type()可以用來查詢型別名稱
```python
print(type([5,8])) # <class 'list'>
```
### object
會佔據記憶體空間的東西
```
3+7
一共會產生3個物件
10 , 7 ,3
```
整個python過程就會**一直產生新的物件**
所以有人會覺python記憶體消耗很大
### id
python內可以用`id()` 去檢查object編號
所以這兩個其實會是同一份object 這就是python的優化
基於python把資料設計成read only的前提
注意沒有名子的物件 執行完就死了
```python
print(id('3'+'2'))
print(id('32'))
```
## 變數賦值
python用**賦值運算(=)** 去 **binding**物件
### bind
a = 3
3 是物件 a是標籤
同一個物件 可以有多個標籤(名子)
```python
a = '3'
a = 8
a = print
```

### del
刪掉名子
如果該object沒人使用 python有自動回收機制
```python
del a
print(a)
```
### KeyWord
不能拿來當變數名稱的

## 數值的運算與轉型
### 真值測試
幾乎所有物件都可以做真值測試
- 非零為真 零為偽
- 非空為零 空為偽
```python
if bool(3 == int(True)) # 3 == True
print true
else
print false
```
```python
if bool(print)
print true
else
print false
```
```python
l = []
if l:
print('not empty')
else
print('empty')
```

## condition
### while else
### for
**迭代型控制流程(iterable)**
```python
for i in range(5)
for i in [1,2,3]
```
那要如何知道是不是iterable呢?
```python
for i in iter('100')
print(i)
```
```python
l = [1,2,3]
for i in l
i = 0;
printf(l) # 1 2 3
```
```python
l = [1,2,3]
for i in range(length(l))
l[i] = 0;
printf(l) # 0 0 0
```
如何複製一份呢?
```python
l1 = [1,2,3]
l2 = l1[:]
l1[0] = 0
```
```python
l1 = [1,2,3]
l2 = l1
del l2
del l1
```
記住python內 **等號不是複製**
## 換行與對齊


## function
function只是一個名子
程式碼取個名子 用小括號呼叫(執行)他
```python
def hello():
print('hello!')
print(hello)
xxx = hello
xxx()
```
這邊是綁定外面的3 和 4 不是assign
python沒有複製這種東西
所以改不到本尊
這只是新的bind( C++就不是如此
```python
def max2(a = 3, b = 4):
if a > b :
return a
return b
print(max2(3,4))
```
python這邊要怎麼做交換呢?
會變
```python
a = 3
b = 5
print(a,b)
# bind
(a,b) = (b,a)
print(a,b)
```
不會變 因為只是x y 互換 但是 a b 還是沒變
```python
def swap(x,y):
x, y = y, x
a = 3
b = 5
# swap
swap(a,b);
print(a,b)
```
```python
def swap(x,y):
x, y = y, x
a = 3
b = 5
# swap
b,a = swap(a,b);
print(a,b)
```
# 5/10
python 萬物皆物件
python的變數比較像是C++的指標
## 函式定義與呼叫
python物件回傳進去 回傳出來
但是和C++不同 不會複製物件
### 不回傳的function
- pass 表示其內容為空
- 語法限制無法真的什麼都不寫
```python
def f():
pass
f()
```
### type hint
- function可以回傳object
- 呼叫function後 可以得到其回傳的物件
```python
def max(a: int, b: int):
if a>b
return a
return b
print(max(3,5))
```
### 內建函式


### 函式參數

#### 順序型 /
- 有給預設值 一定要放後面
放前面到一半就會compiler error

#### 具名型 *
* 期待用名子去呼叫

- 常見sort()
### python什麼都可以當字串印
print會把嘗試把內容轉str()去印
```python
print(str([3,4]))
```
repr()在打印字串 他會幫你家單引號
- repr() 獨一無二
- str() human readable
## 可視範圍
### swap()

要注意python是類似interpreter的語言

### globals() and locals()
python有簡單可以知道local變數 與 global變數 其scope的方法
```python
a = 1
def f(x=2):
y = 3
print("Globals in f: ", globals())
print("Locals in f: " , locals())
b = 4
f()
```
```shell
Globals in f: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fb8be543d90>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'main.py', '__cached__': None, 'a': 1, 'f': <function f at 0x7fb8be5473a0>, 'b': 4}
Locals in f: {'x': 2, 'y': 3}
```
function有自己的local字典
因為是interpreter
有或沒有 是看執行順序
函式`f()`本身是全域的 因為那裡都可以呼叫

使用global關鍵字,可以讓python在local區域去global字典去拿東西

### Enclosing
在function內定義的function名稱只能在該function內使用
- 函數程式設計的基石

- 讀取名稱代表物件跟賦值`(=)` 讓名稱綁定物件是兩種不一樣的規則
- 只能對目前可視範圍內的名稱進行綁定 但可以讀取上一層名稱的內容
```python
x = 3
def func_a():
x = 5
def func_b():
# x = 7
print('func_b', x)
func_b()
print('func_a:', x)
func_a()
print('global', x)
```
```shell
func_b 5
func_a: 5
global 3
```
### LEGB
local -> enclosing -> globals -> built-in
## generator
### yield
如果function內有yield 則該function回傳值的型別為generator
- 用在異步生成 **asynchronous**
- `type(g)`為 `<class 'generator'>`
```python
def f():
print("X")
yield 1
print("Y")
yield 2
g = f() # 產生一個物件 不是函式
print(next(g)) # 執行到yield 1
print(next(g)) # 執行到yield 2
print(next(g)) # run time error 因為yiled已經沒有了
g = f() # 產生一個物件 不是函式
print(next(g)) # 執行到yield 1
print(next(g)) # 執行到yield 2
```
### fibonacci
- 一般版
```python
def fib(n):
a, b = 1,1
print(a)
print(b)
k = 3
while k <= n:
a, b = b, a+b
print(b)
k += 1
fib(10)
```
- generator版
```python
def fib(n):
if n == 1:
return
a, b = 1, 1
yield a
yield b
k = 3
while k <= n:
a, b = b, a+b
yield b
k += 1
for v in fib(10):
print(v)
```
### 用在跑range不需要額外記憶體空間

## class
- create object "()"
```
(type)
/ \ \ \
(int) (float) (C) (NoneType)
| | | |
3 3.2 x None
```
沒有回傳值是"NoneType"
```python
class C:
pass
x = C()
print(type(x))
```
```python
3
print(type(3))
print(type(int))
```
**讀取和等號是不一樣的邏輯**

## method
p = Record('XD', 30)
p.__init__('XD', 30)
Record.init(p, 'XD', 30)
# 5/16
三個 `'''`長字串 可以換行
```python
'''
mutiple line comment
python ....
'''
print('''
(\(\\
(-.-)
o_(")(")
''')
print(__doc__)
```

Ref: https://docs.python.org/zh-tw/3/reference/lexical_analysis.html#literals
## 4.1 類別物件的定義
python : 萬物皆物件
```python
def f():
return 3
def g():
yield 3
class c:
pass
a = 3
# a is int object
# f is function object
# c is class object
print(type(a))
print(type(b))
print(type(c))
# Callable ()
f() # get return vaule
c() # get instance object
```


### 讀取和寫入的差異
讀取:會往老爸找
設定:會加在自己身上

### 物件產生物件
`foo().bar` vs `foo.bar`

### Method
name 和 scope會被當作區域變數
函式寫在類別內
資料存在instance上面


### self
function 呼叫`()` 一定要加self
:::info
好難喔 再查一下吧
__init__ 一定要加self嗎?
https://blog.csdn.net/SmallTankPy/article/details/54945135
:::


### __init__



### __del__
- `__del__` 是刪除名子 不是物件

## 座標相加
- lhs: left hand side
- rgs: right hand side
```python
class Vector2():
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f'({self.x}, {self.y})'
def __add__(self, rhs):
return Vector2(self.x +rhs.x, self.y + rhs.y)
def length(self):
return (self.x**2 + sefl.y**2)**0.5
def __abs__(self):
return self.length()
def __bool__(self):
return self.x != 0 or self.y != 0
# ZeroDivisionError
def normalize(self):
len = self.length()
self.x /= 1
self.y /= 1
def __iadd__(lhs, rhs): # 會省一個obejct 直接改原本物件 不會產生新的
self.x += rhs.x
self.y += rhs.y
# Vector2.__init__(pos1, 3, 4)
pos1 = Vector2(3 ,4)
pos2 = Vector2(7, 5)
print(pos1)
print(pos2)
# print(str(pos1))
# print(pos1.__str__())
# print(Vector2.__str__(pos1))
pos3 = pos1 +pos2
# pos1.__add__(pos2)
# Vector2.__add__(pos1, pos2)
try:
p.normalize()
expect ZeroDivsionError
pass
print(p)
```
# 5/17
## import
- 用大家寫好的套件
- 內建函示有哪些啊? 怎麼知道__str__ ??
- Built-in Functions : https://docs.python.org/3/library/functions.html
- python所有物件都是obejcts
### f-string
PEP 498 帶來了 f-string,它的學名叫作 “Literal String Interpolation”。用法如下:
```python
>>> def upper(s):
... return s.upper()
...
>>> stock = 'tsmc'
>>> close = 217.5
>>> f'{stock} price: {close}'
'tsmc price: 217.5'
```
ref: https://blog.louie.lu/2017/08/08/outdate-python-string-format-and-fstring/
### 一般函式 vs 成員函式
希望object改變: 成員函式
希望obejct不改變 但是回傳改好的東西: 一般函式
python沒事不會用複製這個東西(搬移記憶體) 但是他有建構
```python
class Vector2
def __init__(self, x, y):
self.x = x
self.y = y
# ZeroDivisionError
def normalize(self):
len = self.length()
self.x /= 1
self.y /= 1
def __getitem__(self, i):
if i == 0 : return self.x
if i == 1 : return self.y
def noramlized(v):
l = v.length()
return Vector2(v.x/l, v.y/l)
v1 = Vector2(3, 4)
# return new object, previous object doesn't change,we add + 'd' in tail
print(normalized(v))
# we change object directly
print(v1.normalize())
# print []
print(v[1])
print(v[0])
```
### Python 的等號 '='
- a, b是同一個物件
:::info
所有的等號都是複製ID 只有ID(很像C++的pointer
:::
```python
a = 3
b = a
print(a)
print(b)
```
- 那如果要真的複製一份呢?
```python
l1 = [1, 2, 3]
l2 = l1.cpoy()
l1[0] = 0
print(l1)
print(l2)
```
### 歡樂的for迴圈
- python如果function執行完 又沒東西 會狂噴none....(所以要raise IndexError當結尾)
- `__getitem__` python有名的鴨子型別
```python3
# 提供中括弧的能力 不然打印會噴一堆none
def __getitem__(self, i)
if i==0 : return self.x
if i==1 : return self.y
raise IndexError
def __iter__(self)
yield 0
yield 1
yield 2
v1 = Vector2(3, 4)
# for n in iter(v1)
for n in v1
print(n)
# 3有在v1內嗎? __contains__
print(3 in v1)
```


### sequence
- python只有sequce和mapping
- sequnce : 0 1 2 3 ...
- mapping : 用鍵值去數
文件上看到`sequence` 代表支援getitem

## tuple 元組 '()'
- immutable sequence type (唯讀: read-only)
```python
t = (3, 2, 1)
print(type(t))
```
- 語法上的先天限制 優先權的問題

- tuple 與 sequence 關係

### iteralbe vs iterator
- iterable: 可以呼叫`__iter__()``
- 有實作`__init__`和`__getitem__`即可
- iterator: iter()的回傳,要可以呼叫`__next__()`
- 每一輪都是一個itrator
- 用next next去走...
- generator(yield)是一種iterator
- 所有的sequence都有實作getitem
- 所以他們都是iterable
```python
l = [1, 2 , 3]
g = list(l)
print(next(g))
print(next(g))
print(next(g))
# v 是指向next回傳的 generator
for v in l:
print v
```
### range
```python
print(range(5))
print([range(5)])
print(list(range(5)))
p = iter(range(5))
print(next(p))
print(next(p))
print(next(p))
print(next(p))
print(next(p))
for(i in range(5))
print(i)
```

### iter of iter
for in 會無條件對後面object呼叫iter()
為了讓iter自己也可以被for-in
只要是iterable的東西都可以丟到list去建構
```python
for v in l
print(v)
for v in iter(l)
print(v)
```

# 5/23
## class vs function
```python
class String():
def __init__(self, s):
self.value = s
def __str__(self):
return self.value
def Append(self, b):
self.value += b
return self
# function
# we need `assign` it(pointer to label)
s = String('Hello')
# class
s.Append(' world').Append('!')
print(s)
```
### __getitem__
讀取用`__getitem__ `寫入用`__setitem__`
- for v in t
```python
class MyTuple:
def __init__(self, x, y, z):
pass
def __getitem__(self, i):
pass
def __str__(self):
t = Mytuple
```
### read only
一般tuple`()`和 字串`''` 都是read only
`[]` we can write it
- 唯讀通常都是為了效率
- 語法上的限制
### sort()
可被`for in` 的就是itreable
sort()給tuple回傳list , why ?

### rversed(seq)
要可以數0, 1, 2, ,3


```python
t1 = (1, 2, 3, 4, 5, 6,)
print(reversed(t))
```
## 寫得很python(潮)
### (右) tuple packing : 省略packing的小括弧
```python
# unpacking
t = (a, b , c)
(d, e, f) = t
```
### (左) sequence unpacking
- 右邊全部算完 才會給左邊
- 其實給值都是一次一個
- sequence unpacking其實等價於...
```python
t = ('Pinkus', 60, 'D')
k = iter(t)
a = next(k)
b = next(k)
c = next(k)
print(a)
print(b)
print(c)
```
所以這樣結果是?
```python
a = 0
a, a, a = a+1, a+2 ,a+3
print(a)
```
```shell
3
```
### 單星號運算子Asterisk
```python
# 一個名子 一個物件
t = (3, 4, 5)
*a, b = t
print(a)
print(b)
```
```python
def avg(a, b, c):
return (a+b+c)/3
t = 3, 4, 5
print(avg(*t))
```
```python
def avg(*args):
print(args)
print(type(args))
print(avg(3, 4, 5))
```
```python
def avg(arg1, *args):
print(args)
print(type(args))
print(3 , avg())
```
```python
# failed
sum(1, 2, 3)
# iterable
# we give tuple to work
# we only give one object
sum((1, 2, 3))
```
max 有兩種版本
```python
max(iterable, *[, key, default])
max(arg1, arg2, *args[, key])
```
```python
# ver1: max(iterable, *[, key, default])
# failed
max(3)
# 一定要加`,` 才會變tuple
t = 3,
max(t)
s1 = 'Hello'
s2 = 'World'
print(max(s1))
print(max(s1, s2)) # ver2: max(arg1, arg2, *args[, key])
print(max(*s1, *s2))
print(max([s1, s2]))
```
## import and module
- 要怎麼用python做到類似C++ include hedaer動作?
### from
用`from ... import ...` 可以把模組內名稱匯入全域使用
- `globals()` 可以查全域名稱
- `dir()` 可以查屬性名稱
-
```python
# from vecotr import *
from vector import Vector
v1 = Vector(3,4)
print(v1)
print(v1.x)
print(abs(v1))
```
### dir()

### __builtins__
#### dir
```python
print(dir())
```
```shell
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
```
#### __builtins__
```python
print(__builtins__)
```
```shell
<module 'builtins' (built-in)>
```
#### type(__bultins__)
```python
print(type(__builtins__))
```
```shell
<class 'module'>
```
#### dir(__builtins__)
``` python
print(dir(__builtins__))#
```
```shell
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
```
### 怎麼看文件?
https://docs.python.org/3/py-modindex.html



### 怎麼產生文件?
- `help()``


# 5/24
## 其他python開發環境
[Juipter note](https://jupyter.org/)
- 可以嵌入圖片 表格 很多資料科學家喜歡用
[cpython](https://zh.wikipedia.org/wiki/CPython)
- 有些人稱之為膠水語言
### __main__ 主模組
- `import` 其實就是執行
- 匯入很多次 但是其實也只會執行一次 和C++ include很像 又稱singleton
```python
import ggg
print(__main__)
print(__name__)
print(ggg.__nam__)
# 有時候我們會改變起點 會用這個方法
if __name__ == '__main__'
print('XD')
```
- 其實一般標準庫 **都是用C寫的** 所以盡量用標準庫才快!!
- 自己用python寫`max()`,一定不會比較快。

### tuple可以變大嗎?
- iterable
- 代表你其實一開始不知道元素個數 才需要一個一個讀 和sequence不一樣

- 用python寫tuple其實不連續的,長得很像指標陣列(可以存字串,數字...等等),為了加速所以其實改用C語言,改成真正的連續記憶體間。
- 所以python一定要用內建的才快
### HW3 find middle num
```python
def min(a, b, c):
result = sorted((a, b, c))[1]
```
```python
def min(a, b, c):
t = (a, b, c)
return (
sum(t) - max(t) - min(t)
)
```
```python
def mid(a, b, c):
if a <= b <= c : return b
if a <= c <= b : return c
...
...
...
```
## 範圍與其常見操作
### range
- 會印兩輪嗎?
```python
r = range(5)
for i in r:
print(i)
for i in r:
print(i)
```
```python
r = range(1, 10 ,2)
print(r)
print(tuple(r))
print(r[3])
print(len(r))
```
```python
a , *b = range(5)
print(b)
```


### Def Range
- 切記 有預設值的一定要放右邊
```python
class Range:
def __init__(self, *args):
# Range(5)
if len(args) == 1:
self.start = 0
self.stop = args[0]
# Range(5, 10)
elif len(args) == 2:
self.start = args[0]
self.stop = args[1]
else:
raise TypeError
def __iter__(self):
i = 0
while i < self.stop
yield i
i += 1
def __getitem__(self, i): #sequence 0, 1 ,2 ...
if i + self.start >= self.stop
raise IndexError
return i + self.start
for v in Range(5):
print(v)
for v in iter(Range(5)):
print(v)
for v in iter(Range(5, 10)):
print(v)
```
### slice [:]
dir可以看屬性
```python
v = slice(5)
print(v.start)
print(v.stop)
print(v.step)
```
- slice 其實就是去產生一個物件
```python
t = (1, 2, 3)
print(t[1]) # t.__getitem__(1)
print(t[1:3]) # t.__getitem__(slice(1,3))
print(t[slice(1,3)])
```

### 省略符 ...

可以吃tuple嗎?

## String
- 四天王之一(tuple, range ...)
- 吃object*
- python 沒有字元char的概念,只有長度為1的字串
- 他其實每"格" 都是一個unicode阿 @皿@
### ord
- 可以用來打印unicode編號
### chr
### string sequence

### enumerate
```python
s = '這是一段字串'
for k in enumerate(s):
print(k)
for (i, c) in enumerate(s):
print(i ,c)
# index and s[index]
for i, c in enumerate(s):
print(i ,c)
```
```python
def Enumeration(iterable):
p = iter(iterable)
i = 0
try:
while True:
yield (i, next(p))
i += 1
expect:
raise
```
### 串接
- 為了效率 我們要用`join()`
```python
t = ('T', 'h', 'e', '豬', '爸', '爸')
print(''.join(t))
```
### 格式化

## 列表[] list
- tuple不能改內容,但是list可以。
- 他可以改變大小(tuple不行??)
- 這邊不是dereference 其實還是拆包
```python
l = [1, 2, 3, 4, 5]
print(reversed(l))
print(*reversed(l)) # 這邊不是dereference 其實還是拆包
```python
l.reverse()
```
### append()
### extend()
### 切片賦值
```python
# [1, 2, 3, 4, 5]
# [3, 4, 5]
# [0, 0, 0, 0, 3, 4, 5]
l = [1, 2, 3, 4, 5]
l[0:2] = [0, 0, 0, 0]
l = [1, 2, 3, 4, 5]
l[2:3] = [0, 0, 0, 0]
```
# 5/30
## Review
### Squence and Iterator
```python
l = [3, 9, 1, 7, 6]
# Sequence
print(l[2]) # l.__getitem__(2)
print(len(l)) # l.__len__()
print(4 in l) # l.contaions__(4)
# Iterator
p = iter(l)
print(next(p))
print(next(p))
print(next(p))
print(next(p))
print(next(p))
print(next(p))
# 和上面等價
for v in l:
print(v)
# Slice
print(l[2]) # l.__getitem__(2)
print(l[2:4]) # l.__getitem__(slice(2, 4))
print(l[2:4:1]) # l.__getitem__(slice(2, 4, 1))
# delete element form 2 to 3 # [2, 3)
l[2:4] = []
#
# 通常寫一行是比較快的 有機會優化
# l[2] = 0
# l[3] = 0
# l.insert(4, 0)
# l.insert(5, 0)
#
# l = [1, 2, 3, 4, 5, 6 ,7]
# l = [1, 2, 0, 0, 0, 0 ,5 ,6, 7 ]
#
l[2:4] = [0, 0, 0, 0]
```
- 優化
```python
a = 3
b = 5
# 不會有新物件 快
a += b
# 會產生新物件 比較慢
a = a + b
```
### *
#### read(等號右邊 拆包) unpacking
- iter, next, next, next
```python
def f():
yield 1
yield 3
yield 5
print([*f, *f])
```
```python
l = [3, 9, 1, 7 ,6]
print(l)
# Same
print(*l)
print(l[0], l[1], l[2], l[3], l[4])
print([*l])
```
#### write(左邊 打包) packing
- 讀的時候很彈性 但是寫的時候 只能寫在列表
```python
x = [1, 2, 1]
y = [4, 5]
*a, b = [*x, *y]
print(a) # 1 2 1 4
```
#### * 的限制
```python
def f():
yield 1
yield 3
yield 5
print(f)
print(f())
print(*f())
print(tuple(f()))
```
## functino 間參數傳遞


- 會幫你用getitem拿東西出來 所以值會變


## Generator
- 有copy的負擔 然後l還會被改寫
```python
def addOne(l):
n = l.copy()
for i in range(len(n)):
n[i] += 1
return n
l = [3, 9, 1, 7, 5]
for x in addOne(l):
print(x)
print(l)
```
- 用generator可以解決這個問題
### generation expression
```python
def addOne(l):
# return (v+1 for v in l)
for v in l:
yield v+1
l = [3, 9, 1, 7, 5]
for x in addOne(l):
print(x)
for x in (v+1 for v in l):
print(x)
# gerneration expression
n = tuple[v+1 for v in l]
n1 = [v+1 for v in l]
print(n)
print(n1)
print(l)
```
```python
import random
def even(l):
for v in l:
if v%2 == 0
yield v
n = [v for v in l if v%2 == 0 and v<5]
```
```python
tuple(v for v in [3,9,1])
list(v for v in [3,9,1])
(*(v for v in [3,9,1]), )
[*(v for v in [3,9,1])]
```

- python因為沒有括號
- 沒有像C++ 爽快幫忙修縮排的工具
### list vs [] vs [*]
```python
l = [range(5)]
# 等價
m = list(range(5))
n = [*range(5)]
print(l)
print(m)
print(n)
```
## Hash Function
其實也可以實作 `__hash__`
```python
print(hash("Mary"))
print(hash("Mary"))
print(hash("John"))
```
- collision
- cpython實作有改良
### dict
1. 來源寫死
2. 來源是iterable


- python的key 可以隨便取
```pythpm
m = {
'Mary': 60,
'Jphn': 90,
(6,7): "129"
}
for k,v in m.items
print(k , v)
print(m[6,7])
```
- python的hash其實用object的id去算的
- list不能當健值 因為list值可以改(mutable)
- 會變得內容 不適合當健值
```python
print()
```


### 雙星號運算子

```python
def f(**kwargs):
print(kwargs)
f(y=1, x=2)
```
```python
def f(*args, **kwargs):
print(args)
print(kwargs)
f(3, 4, y=1, x =2)
```

# 5/31
每個語言都有自己的動機 有不同的生命週期
能力成長後 原本的工具不一定適合你
The Zen of Python
python不是拿來設計大專案的程式語言
- 那些型態?
```python
# ... 3000 lines
print(type(a)
```
## HW 5
```shell
__iter__ -> __getitem__
```
for迴圈會先呼叫iter
所以__getitem__會被呼叫五次
tuple如果是`-1`就是倒過來數
```python
a = (1, 2 , 3, 4)
print(a[-1]) # 4
print(a[1]) # 2
# 0 1 2 3 4 => Indexerror
# Index Error <= -5 -4 -3 -2 -1
# final answer
reurn _t[-1 - i]
```
```python
# Sequence [-len ~0 ~ len -1]
s = '12' # str
t = (1,2) # tuple
l = [1,2, 'string', (1,2)] # list 可以多混 但是這樣就慢了
# Mapping [hashable]
# 無法保證"順序"
e = {'1', '2'} # set
d = {'1':'1', '2':2} # dict
for v in s:print(v)
for v in t:print(v)
for v in l:print(v)
for v in e:print(v)
for v in d:print(v)
```
語法糖
```python
print(tuple(i for i in range(5)))
print(list(i for i in range(5)))
print([i for i in range(5)])
print(set(i for i in range(5)))
print({i for i in range(5)})
```
- generator is belong to iterator
```python
```
- forzenset
- 一般set is mutable, we can not get hash from it
```python
```


- python 通常會分會丟ERROR和不會丟error 的function
- 
## 物件導向
### dic
每個物件都有字典 去描述物件屬性(只有整數沒有
通常可以新增刪除的都有

### type class instance
https://docs.google.com/presentation/d/e/2PACX-1vRZZlJVubAIV66tsLSmN2AADtINx0DnBnltR9Lh7lhZFpRDMpwmryAkHdNeiGUSMf8mgliBJozwSUtj/pub?slide=id.g8172421c63_0_311



### Duck Typing 鴨子型別
- 類似C++的用法阿@@
:::info
只要有`draw()`,我就把他當`shape`
所以如果多function 就要全部都去寫ex: transalte()
:::
- iterator就是可以支援next next next...
```python
#
class Rectangle:
def __init__(self, x, y, w, h):
self.x, self.y = x, y
self.w, self.h = w, h
def draw(self):
print(f'[Rectangle]')
print(f' x: {self.x}, y: {self.y}')
print(f' w: {self.w}, h: {self.h}')
class Triangle:
def __init__(self, x, y, b, h):
self.x, self.y = x, y
self.b, self.h = b, h
def draw(self):
print(f'[Triangle]')
print(f' x: {self.x}, y: {self.y}')
print(f' b: {self.b}, h: {self.h}')
r = Rectangle(0, 0, 50, 50)
r.draw()
t = Triangle(10, 10, 30, 40)
t.draw()
def draw_all(shapes):
for s in shapes:
s.draw()
draw_all([r, t])
```
### 繼承
- 不想重複寫程式碼 不同class 作相同操作要怎麼做
- `__base__`


- 世界是兩個維度
- object是所有人的爸爸
- 預設版本的`__getitem__`
- `__init__` ...
- 一路往右邊問看看function有沒有實作

```python
class Record(object):
pass
print(Record.__str__)
print(object.__str__)
```
:::info
要切記 讀和寫是不一樣的
:::

### 類別和實例的關係
對實例物件呼叫 等於對class 物件呼叫


#### getattribute

:::info
type幫你找繼承關係(找爸爸這個動作 => 找"屬性"有沒有
oject內實作fucntion
:::
### 運用

# 6/6
## 函數式程式設計
python沒有overloading
`=` : 打包列表 `[]`
`*` : tuple `()`
class : 你可以做什麼事 functino()
object : 你的資料實體
- duke typing 其實是很隱諱的 你根本不知道人家有沒有寫
要看才了才知道draw()有沒有實作
- C++ : generic
- python: protovol
### 類別間的繼承
- ABC: Abstract Based Class
- 這個東西很少人用
:::info
object vs type vs inehritance
:::
## HW review
```python
# Homework 6-1
class Range():
def __init__(self, *args):
if len(args) == 1:
args = 0, *args,
# args (0 ,1)
elif len(args) == 2:
args = *args, 1
# args (1 ,5 ,1)
self.start, self.stop, self.step = args
)
def __len__(self):
# 完成此函式
return (self.stop - se;f.start)//self.step
print(len(Range(5)))
print(len(Range(1, 5)))
print(len(Range(1, 5, 2)))
```
```python
# Homework 6-2
class Range():
def __init__(self, *args):
if len(args) == 1:
args = 0, *args, 1
elif len(args) == 2:
args = *args, 1
self.start, self.stop, self.step = args
# 如果沒寫iter就會先call get item
def __getitem__(slef, i):
# 期待你用next() 去完成這件事 就是用yield
def __iter__(self):
# 試著完成這個函式
i = self.start
if self.stop >0:
while i < self.stop:
yield i
i += self.step
else if self.stop <0:
while i > self.stop:
yield i
i += self.step
print(*Range(1, 5, 1))
print(*Range(5, 1, -1))
```
### cmp
python怎麼做到類似c++ function pointer這件事


```python
r = list(range(10))
print(r)
def f(x):
if x % 2 == 0:
return (True, -x)
else
return (False, x)
print(sorted(r, key = f))
# fasle是0 他是小於true 1的
# python的sort是stable的
# tuple比較是兩個維度 先true 在 -x
# [0, 1, 2 ,3 ,4 ,5, 6, 7, 8 ,9]
# f: T F T F T F T F T F
# 0 1 -2 3 -4 5 -6 7
#
# # 一維如果一樣 在跳第二維
# if not (tuye, x) < (true ,- x)
#
```
- 把它當字串比
```python
print( [1,2] > [1, 2, 3])
print( 'abc' > 'cba' )
```
### lambda

### map , filter, reduce
functional比較容易平行

```python
l1 = [('Taipei', 30), ('Tayuan', 40), ('Yieland', 50) ]
print(l1)
a = map(lambda x: x[1], l1)
b = map(lambda x: -x, a)
c = filter(lambda x: x < 30 ,b) # (只留True, 忽略false
# 真正執行在l2
l2 = list(b)
print(l2)
```
- 其實很少有人這樣寫 大家都用generator expression
- reduce


```python
```
### 讓function return function
This is decorator
用visualize python看看

### 裝飾模式
processA()升級了!!
在原本process的前後多做了一些事情


```python
def addOne(f):
def func():
return f()+1
return func
'''
f = addOne(f)
f = addOne(f)
f = addOne(f)
f = addOne(f)
f = addOne(f)
'''
@addone
@addone
@addone
@addone
@addone
def f():
return 0
print(f())
```
# 6/7
## HW
### HW7 Generator
- 用for迴圈,產生列表
```python
for x in a:
for y in b:
if x == y:
ans.append(x)
return ans
```
- 用for迴圈,產生generator(並不是列表)
- 資料沒有一次生出來 在next執行前 都沒有發動
- 用generator expression也是丟yiled回去(被包起來 看不到
```python
return (x for x in a for y in b if x == y)
```
兩次交集,過程中不用產生真的列表。
要產生列表`[]` 請用單星號運算子
```python
for v in set_join(set_join(l1, l2), l3):
print(v)
```
```python
return set(a) & set(b)
```
### HW8 繼承
用繼承就不用寫translate
要注意self變數是誰,所以都要用super去呼叫老爸的init
如果把x 改成 xx會發生什麼事呢?
```python
class Circle(Shape):
def __init__(self, x, y, r)
super().__init__(x,y)
self.r = r
def draw(self):
print(f' {self.x} {self.y}')
```
### HW9 套件
模組(Module): 一個檔案
套件(Package): 一個目錄 也可以是一個檔案
google的結果 有可能很高興寫完 發現版本不一樣
- 先google看一下範例 把範例的key word記起來
- 把randint()丟去文件庫找,例如有沒有包含end()
- return a random interger N such that a <= N
- 結果發現randomrange()沒有包括終點,randint()有
## fcuntion回傳function物件
- Interpreter: 沒執行 要用print去呼叫f()瞬間 才會去執行f()的程式碼
- hello是區域變數 只會活在f()內
- 沒有東西 預設就是return NONE
```python
def f():
def hello():
print("Hello World")
return hello
print(f())
print(f()())
```
```shell
<function f.<locals>.hello at 0x7f1b61d12820>
Hello World
None
```
```python
def f(name):
def hello():
print("Hello ", name)
return hello
# 丟Bob物件進去 拿到加了hello的Bob物件
hBob = f('Bob')
hBob()
hJohn = f('John')
hJohn()
```
- 不想要改原本的fucntion功能,但是又想擴充它
- 這就是decaraotr
```python
# 擴充功能
def john(func):
def f():
func()
print('john')
return f
# 原本程式碼在這
@john
def hello():
print('hello')
# 上架時 就把這一行拿掉
# hello = john(hello)
hello()
```
如果還想要給參數呢?
```python
# 擴充功能
def john(func):
def f():
func()
print('john')
return f
# 原本程式碼在這
@john
def hello():
print('hello')
# 上架時 就把這一行拿掉
# hello = john(hello)
hello()
```
### __call__
- 其實`()` 也是一個特殊方法
- 這是`callable()`嗎?
- C會去type這classs內去找call
```python
calss C:
pass
# type.__call__(C)
obj = C()
```
- 會失敗 因為obj所屬的class是C,class c 沒有實作`__call__`
```python
calss C:
pass
# type.__call__(C)
obj = C()
obj()
```
```python
def f(name):
def __init__(self, name):
self.name = name
def __call__(self)
print("Hello ", self.name)
# 丟Bob物件進去 拿到加了hello的Bob物件
hBob = f('Bob')
hBob()
hJohn = f('John')
hJohn()
```

```python
def f(name):
cnt = 0
def hello():
nonlocal cnt # s要往上一層找 讀的時候會自動網上一層找 但是寫的時候不會
print('hello', name, cnt)
cnt += 1
return hello
# 丟Bob物件進去 拿到加了hello的Bob物件
hBob = f('Bob') # __init__
hBob() # __call__
hBob() # __call__
hBob() # __call__
hBob() # __call__
hBob() # __call__
hJohn = f('John')
hJohn()
hJohn()
hJohn()
hJohn()
hJohn()
```
## Closure 閉包
- 狀態存在這函式內
https://docs.google.com/presentation/d/e/2PACX-1vSP46uYkPNIGtJY2bkbzhqiUPxb1qELpNV_bBSZJmr7mWC9nmviLYl_icnXu3AdW72-mxAcN-mNcflq/pub?slide=id.g81b5307b85_0_93
用這技巧來加速
每次cache都是一份新的

:::info
- TenserFlow的`@tf.function`
- 看看你是哪種function 去判斷他要怎麼優化
- python的LRU cache
:::