---
# System prepended metadata

title: Python進階

---

Python進階
===

* 可變類型: List, Dict
* 不可變類型: Num, Str, Tuple
* [Hash function](https://zh.wikipedia.org/wiki/散列函數)
  ```python3=
  In [xx]: a = {[a]:123}
  TypeError: unhashable type: 'list'
  ```
  * 為避免使用者更改 key 而造成 key 與 Hash Value 無法對應，
  * 故 key 不得使用可變類型

[遞歸(Recursion)](https://zh.wikipedia.org/wiki/递归)
---
* 欲完成[階乘](https://zh.wikipedia.org/wiki/階乘)運算 4!
  * 想法一：4! = 4\*3\*2*1
    ```python
    i = 1
    num = 1
    while i<5:
        num *=i
        i+=1

    print(num)
    ```
  * 想法二: 4! = 4 * 3!  
    ```python
    '''概念：

    def xxxx(num):
        num * xxxxx(num-1)

    def xxx(num):
        num * xxxx(num-1)

    def xx(num):
        num * xxx(num-1)

    xx(4)

    每次都乘自己-1的結果
    '''
    
    # 實現
    
    def getnum(num):
        if num>1:
            return num * getnum(num-1)
        else:
            return num

    result = getnum(4)
    print(result)

    '''
    運作: 
    
    getnum(4) = 4 * getnum(3) = 4 * 6
    getnum(3) = 3 * getnum(2) = 3 * 2
    getnum(2) = 2 * getnum(1) = 2 * 1
    getnum(1) = 1
    '''

    # 使用遞歸時注意: 避免死循環
    # 運作死循環的函數時，執行到某一次程式時就會崩潰
    # 因為內存外溢
    ```

匿名函數
---
* 一般函數
  ```python
  def test(a,b):
    # a+b
    return a+b

  result1 = test(1,2)
  print("result=%d"%result1)
  # TypeError: %d format: a number is required, not NoneType
  # 要回傳，否則會報錯
  ```
  
* 匿名函數
  ```python
  # 變量 = lambda 參數:式子
  c = lambda a,b:a+b

  # 調用匿名函數
  result2 = c(1,2)
  print("result=%d"%result2)
  ```

* 兩者區別
  * 區別標準: 匿名函數自帶 return
  * 區別實益: 
    * 一般函數用來寫較複雜的東西,
    * 匿名函數則寫較簡易的東西(如計算).


匿名函數應用
---

```python
In [xx]: apple = [1,2,3,5,3,23,2,25,235,4,234]   
In [xx]: apple.sort()
In [xx]: apple
Out[xx]: [1, 2, 2, 3, 3, 4, 5, 23, 25, 234, 235]
# sort 按大小排序

In [xx]: a = [{"a":"A", "b":22}, {"a":"B", "b":19}, {"a":"C", "b":20}]
In [xx]: a.sort()
TypeError: '<' not supported between instances of 'dict' and 'dict'

In [xx]: a.sort(key= lambda x:x["b"])
# 指定 key 的參數為 x,
# 亦即 x 有 "a" 跟 "b"
# 接著取 "b" 的 value 出來就能比較了
In [xx]: a
Out[xx]: [{'a': 'B', 'b': 19}, {'a': 'C', 'b': 20}, {'a': 'A', 'b': 22}]
```

匿名函數應用2
---

```python
# 舊寫法
def test(a,b):
    result = a+b
    print("Result1=%s"%result)

# 新寫法
def test2(a,b,func):
    result = func(a,b)
    print("Result2=%s"%result)

test(1,2)
test2(1,2, lambda x,y:x+y)
test2(1,2, lambda x,y:x-y)

# 優點: 讓test function更彈性，而不會侷限在一個功能
```

匿名函數應用2延伸
---
```python
# coding=utf-8

def test(a,b,func):
    result = func(a,b)
    print("Result=%s"%result)

new_func = input("請輸入一個匿名函數: ")

test(1,2,new_func)

'''Terminal
$ python2 匿名函數應用延伸.py
請輸入一個匿名函數: lambda x,y:x+y+100
Result=103

$ python3 0511_匿名函數應用延伸.py 
請輸入一個匿名函數: lambda x,y:x+y+100
TypeError: 'str' object is not callable
'''
# python3 的 input 會將輸入數據視為一個str
# 解決辦法: eval
```
```python
# coding=utf-8

def test(a,b,func):
    result = func(a,b)
    print("Result=%s"%result)

new_func = input("請輸入一個匿名函數: ")
new_func = eval(new_func)

test(1,2,new_func)

'''Terminal
$ python3 匿名函數應用延伸.py
請輸入一個匿名函數: lambda x,y:x+y+100
Result=103
'''
```

自調用函數
---
> - 匿名函數用小括號刮起來, 變成一個表達式, 後面再加一個調用的小括號
```python
(lambda a,b:a+b)(1,2) # 3
(lambda a,b:a+b)(3,4) # 7
```


補充知識點_可變與不可變
---
```python
a = 100
b = [100]
c = [100]

def test(num):
    num+=num
    print(num)

def test2(num):
    num = num + num
    print(num)

test(a)
print(a)
print("---")
test(b)
print(b)
print("---")
test2(c)
print(c)

'''Terminal
200
100
# 遇到不可變時(a,num), 會直接創一個新的變量來儲存(局部變量)
---
[100, 100]
[100, 100]
# 遇到可變時(b,list), 就會直接改(全局變量)
---
[100, 100]
[100]
# num+=num ≠ num=num+num
# num=num+num 是另 num 指向了一個新的id(num+num),而非原本的c
# 故 c 沒被修改
'''


```

補充知識點_交換兩個變量
---

```python
a = 6
b = 4

'''方法一
c = 0
c = a
a = b
b = c
'''

'''方法二
a = a+b
b = a-b
a = a-b
'''

# 方法三
a,b = b,a

print(a,b)
```

str常見操作
---
```python
In [1]: mystr = "dog is dog,not cat."
In [2]: mystr.find("mouse") 
Out[2]: -1
In [3]: mystr.find("dog")
Out[3]: 0
In [4]: mystr.find("cat")
Out[4]: 15
In [5]: mystr.index("mouse")
ValueError: substring not found
In [6]: mystr.index("dog")
Out[6]: 0
In [7]: mystr.index("cat") 
Out[7]: 15
In [8]: mystr.rfind("dog")
Out[8]: 7
In [9]: mystr.rindex("dog")
Out[9]: 7
In [10]: mystr.count("dog") 
Out[10]: 2
In [11]: mystr.count("doggie")
Out[11]: 0

# replace (old, new, count)

In [12]: mystr.replace("is","IS")
Out[12]: 'dog IS dog,not cat.'
In [13]: mystr
Out[13]: 'dog is dog,not cat.'
# str 不可變，故mystr沒有真的被改變，是指向另一個id，
# 如欲保存, 需另用一個變量將其存下
In [14]: mystr.replace("dog","DOG") 
Out[14]: 'DOG is DOG,not cat.'
In [15]: mystr.replace("dog","DOG",1)
Out[15]: 'DOG is dog,not cat.'
In [16]: mystr.replace("dog","DOG",2)
Out[16]: 'DOG is DOG,not cat.'

# split(sep=None, maxsplit=-1) 
In [19]: mystr.split(" ")
Out[19]: ['dog', 'is', 'dog,not', 'cat.']
In [24]: mystr.split(" ", 2)
Out[24]: ['dog', 'is', 'dog,not cat.']

In [56]: mystr.partition("not")
Out[56]: ('dog is dog,', 'not', ' cat.')
In [57]: mystr.partition("dog")
Out[57]: ('', 'dog', ' is dog,not cat.')
In [58]: mystr.rpartition("dog") 
Out[58]: ('dog is ', 'dog', ',not cat.')

In [59]: test2 = "123\n1233234\nasdhjwhkjq\nfdhfjkhfd\n123sff2"
In [61]: test2.splitlines()
Out[61]: ['123', '1233234', 'asdhjwhkjq', 'fdhfjkhfd', '123sff2']

# capitalize & title
In [29]: mystr.capitalize()
Out[29]: 'Dog is dog,not cat.'
In [31]: mystr.title()
Out[31]: 'Dog Is Dog,Not Cat.'

# startswith
In [34]: child_name = "Wang xxx" 
In [35]: child_name.startswith("Wang")
Out[35]: True
In [36]: child_name.startswith("li")
Out[36]: False
# endswith
# 應用: 查檔案類型
In [37]: file_name = "xxx.txt"
In [38]: file_name.endswith("txt")
Out[38]: True
In [39]: file_name.endswith("tnt")
Out[39]: False

# isalpha 判斷str是否為純字母
In [63]: a = "fjkhjkwqhjkh"
In [64]: a.isalpha()
Out[64]: True
In [65]: a = "fjkhjkwqhjkh1"
In [66]: a.isalpha()
Out[66]: False
# isdigit 判斷str是否為純數字
In [70]: b = "123"
In [71]: b.isdigit()
Out[71]: True
'''應用舉例情境
In [75]: num = int(input("請輸入功能數字:"))
請輸入功能數字:1ab
ValueError: invalid literal for int() with base 10: '1ab'
# 用戶如果不是輸入數字，程式就掛了
# 解決辦法: 先判斷用戶是不是輸入你要的再轉，用戶亂輸就print("徹幹礁")
'''
# isalnum 判斷str是不數字與字母組成
# 常用於帳號密碼組成判斷
In [76]: c = "1223fskdjfk"
In [77]: c.isalnum()
Out[77]: True
In [80]: c = "123 jfdks"
In [81]: c.isalnum()
Out[81]: False

# isspace
In [1]: a = "   "
In [2]: a.isspace()
Out[2]: True
In [3]: a = "  _  " 
In [5]: a.isspace()
Out[5]: False

# upper 將所有字都變成大寫
In [xx]: exit_flag = "Yes"
In [42]: exit_flag.upper()
Out[42]: 'YES'
# lower 將所有字都變成小寫
In [43]: exit_flag.lower()
Out[43]: 'yes'
# 應用舉例情境: 如欲使用戶輸入yes來退出程式, 此時用戶可能輸入的yes可能有好幾種(YES,yes,Yes...)
# 解決方法一: 每一種都寫一段程式碼來退出
# 解決方法二: 使用upper / lower 來統一變成大寫/ 小寫
 
In [44]: lyric = "腦海有風, 髮膚無損."
In [48]: lyric.center(20)
Out[48]: '    腦海有風, 髮膚無損.     '
In [49]: lyric.ljust(20)
Out[49]: '腦海有風, 髮膚無損.         '
In [50]: lyric.rjust(20)
Out[50]: '         腦海有風, 髮膚無損.'

# strip 砍空格
In [51]: test = lyric.center(20)
In [52]: test
Out[52]: '    腦海有風, 髮膚無損.     '
In [53]: test.strip()
Out[53]: '腦海有風, 髮膚無損.'
In [54]: test.lstrip()
Out[54]: '腦海有風, 髮膚無損.     '
In [55]: test.rstrip()
Out[55]: '    腦海有風, 髮膚無損.'

# join 在每個str中插入指定的str, 形成一個新的str
In [6]: names = ["apple", "banana", "cow"]
In [7]: a = "_"
In [8]: a.join(names)
Out[8]: 'apple_banana_cow'

# Q
# a = "fdhjsfh sdkjfl\n\n\n jdsf wklj \t\t\t  kqjk jkl jla j  lkjlkqjrlk "
# 欲將 a 的 "space" "\t" "\n" 給砍了
In [10]: a = "fdhjsfh sdkjfl\n\n\n jdsf wklj \t\t\t  kqjk jkl jla j  lkjlkqjrlk "
In [11]: a.split() 
Out[11]: ['fdhjsfh', 'sdkjfl', 'jdsf', 'wklj', 'kqjk', 'jkl', 'jla', 'j', 'lkjlkqjrlk']
In [12]: b = " "
In [13]: b.join(a.split()) 
Out[13]: 'fdhjsfh sdkjfl jdsf wklj kqjk jkl jla j lkjlkqjrlk'
```

讀寫文件
===
```python
In [16]: f = open("haha.txt") 
FileNotFoundError: [Errno 2] No such file or directory: 'haha.txt'

In [17]: f = open("haha.txt", "w")
In [18]: f.write("haha")
Out[18]: 4
In [19]: f.close() # 寫完記得關起來

In [20]: f = open("haha.txt") 
# 沒有報錯 ->已有 haha.txt 可以讀
 
In [22]: f.read(1)
Out[22]: 'h'
In [23]: f.read(1) 
Out[23]: 'a'
In [24]: f.read(1)
Out[24]: 'h'
In [25]: f.read(1) 
Out[25]: 'a'
In [26]: f.read(1) 
Out[26]: ''
In [27]: f.read(1) 
Out[27]: ''
# read 會從上次讀取的地方繼續往後讀
In [29]: f.close() # 讀完記得關起來
```
`vi 隨便寫寫.py`
```python
f = open("hahaha.txt", "w")
f.write("哈哈哈")
f.close()

'''Terminal
$ python3 隨便寫寫.py
$ ls
隨便寫寫.py hahaha.txt
'''
```
`vi 讀取隨便寫寫.py`
```python
f = open("hahaha.txt", "r")

content = f.read()
print(content)

f.close()

'''Terminal
$ python3 讀取隨便寫寫.py
哈哈哈
'''
```
`vi 複製檔案.py`
```python=
old_file_name = input("請輸入欲複製的文件名(含檔名): ")
f_read = open(old_file_name, "r")
content = f_read.read()

new_file = open("xxxx.txt", "w")

new_file.write(content)

f_read.close()
new_file.close()

'''Terminal
$ 複製檔案.py
請輸入欲複製的文件名(含檔名): hahaha.txt
$ ls
隨便寫寫.py 複製檔案.py hahaha.txt xxxx.txt
$ cat xxxx.txt
哈哈哈
''' 
# 檔名有點問題
```
`vi 複製檔案.py`
```python=5
# new_file = open("xxxx.txt", "w")
new_file_name = old_file_name + "[copy]"
new_file = open(new_file_name, "w")

'''Terminal
$ 複製檔案.py
請輸入欲複製的文件名(含檔名): hahaha.txt
$ ls
隨便寫寫.py 複製檔案.py hahaha.txt hahaha.txt[copy] xxxx.txt
'''
# hahaha.txt[copy] 這在win系統無法被正確執行!
```
`vi 複製檔案.py`
```python=5
# new_file = open("xxxx.txt", "w")
# new_file_name = old_file_name + "[copy]"
new_file_name = old_file_name.rreplace(".", "[copy].", 1)
new_file = open(new_file_name, "w")

'''Terminal
$ python3 0703_複製隨便寫寫.py 
請輸入欲複製的文件名(含檔名): hahaha.txt 
AttributeError: 'str' object has no attribute 'rreplace'
$ ipython3
In [xx] a = "xxx"
In [xx] a = r
replace() rjust() rstrip() rfind() rpartition() rindex() rsplit()
'''
```
`vi 複製檔案.py`
```python=5
# new_file = open("xxxx.txt", "w")
# new_file_name = old_file_name + "[copy]"
# new_file_name = old_file_name.rreplace(".", "[copy].", 1)
position = old_file_name.rfind(".")
new_file_name = old_file_name[:position] + "[copy]" + old_file_name[position:]
new_file = open(new_file_name, "w")

'''Terminal
$ 複製檔案.py
請輸入欲複製的文件名(含檔名): hahaha.txt
$ ls
隨便寫寫.py 複製檔案.py hahaha.txt hahaha.txt[copy] xxxx.txt hahaha[copy].txt
$ cat hahaha[copy].txt
哈哈哈
'''
```
```python
In [44]: f = open("hello_world.py")
In [45]: f.read()
Out[45]: '# -*- coding:utf-8 -*-\n\n# 輸出Hello,world\nprint("Hello, world")\n'
In [47]: f.close()

In [48]: f = open("hello_world.py")
In [49]: f.readlines()
Out[49]: 
['# -*- coding:utf-8 -*-\n',
 '\n',
 '# 輸出Hello,world\n',
 'print("Hello, world")\n']
In [51]: f.close()

In [52]: f= open("hello_world.py") 
In [53]: f.readline() 
Out[53]: '# -*- coding:utf-8 -*-\n'
In [54]: f.readline() 
Out[54]: '\n'
In [55]: f.readline() 
Out[55]: '# 輸出Hello,world\n'
In [56]: f.readline()
Out[56]: 'print("Hello, world")\n'
In [57]: f.readline() 
Out[57]: ''
In [58]: f.readline()
Out[58]: ''
```
`vi 複製檔案.py`
```python=
old_file_name = input("請輸入欲複製的文件名(含檔名): ")
f_read = open(old_file_name, "r")


# new_file = open("xxxx.txt", "w")
# new_file_name = old_file_name + "[copy]"
# new_file_name = old_file_name.rreplace(".", "[copy].", 1)
position = old_file_name.rfind(".")
new_file_name = old_file_name[:position] + "[copy]" + old_file_name[position:]
new_file = open(new_file_name, "w")

# content = f_read.read() -> 萬一讀取的檔案超級大石，電腦就會掛掉
# new_file.write(content)

while True:
    content = f_read.read(1024)
    
    if len(content) == 0:
        break

    new_file.write(content)

f_read.close()
new_file.close()
```
```python
In [71]: f = open("0-100_even_numbers.py")
In [72]: cat 0-100_even_numbers.py
a = 1
while a<=100:
    if a%2==0:  # 如果a/2沒有餘數時
        print(a)
    a+=1
    
# read 會從頭開始讀
In [73]: f.read(1)
Out[73]: 'a'
In [74]: f.read(1)
Out[74]: ' '
In [75]: f.read(1)
Out[75]: '='

# tell 會告訴你現在讀到哪
In [78]: f.tell()
Out[78]: 3


# seek(cookie, whence=0, /) 可以指定位置
# Values for whence are:
    # 0 -- start of stream
    # 1 -- current stream position
    # 2 -- end of stream
In [79]: f.seek(0,2)
Out[79]: 89 
In [80]: f.read(1)
Out[80]: '' # 指針已經指到最後了，故後面沒東西了
In [82]: f.seek(0,0)
Out[82]: 0
In [73]: f.read(1)
Out[73]: 'a'
In [74]: f.read(1)
Out[74]: ' '
In [75]: f.read(1)
Out[75]: '='
In [78]: f.tell()
Out[78]: 3
In [88]: f.seek(50,0)
Out[88]: 50
In [89]: f.read(1)
Out[89]: '有'
```
Import os
---
```python
In [xx]: import os

# os.listdir = ls
In [116]: os.listdir 
['xxxx.txt']

# os.rename = mv
In [118]: os.rename("xxxx.txt", "test.txt")
In [116]: os.listdir 
['test.txt']

# os.remove = rm
In [120]: os.remove("test,txt")
In [116]: os.listdir

# os.mkdir = mkdir
In [122]: os.mkdir("哈哈")
In [116]: os.listdir
['哈哈']

# os.getcwd = pwd
In [124]: os.getcwd()
Out[124]: '/Users/xxxxx/Desktop/python'

# open 支援相對路徑與絕對路徑!
In [125]: f = open("../xxxx", "w")
In [134]: f = open("哈哈/xxxx", "w")

# os.chdir = cd
In [xx]: os.chdir("../")
In [124]: os.getcwd()
Out[124]: '/Users/xxxxx/Desktop'
In [116]: os.listdir
['xxxx']
In [xx]: os.chdir("python/哈哈")
In [124]: os.getcwd()
Out[124]: '/Users/xxxxx/Desktop/python/哈哈'
In [116]: os.listdir
['xxxx']
In [xx]: os.remove("xxxx")
In [xx]: os.chdir("../")
In [124]: os.getcwd()
Out[124]: '/Users/xxxxx/Desktop/python'

# os.rmdir = rmdir
In [150]: os.rmdir("哈哈/")
```
`vi 批量重命名.py`
```python=
import os

folder_name = input("請輸入重命名的文件夾：")

file_name = os.listdir(folder_name)

for name in file_name:
    os.rename(name,"[小中夭]")
    
'''Terminal
$ python3 0704_批量重命名.py 
請輸入重命名的文件夾：test
FileNotFoundError: [Errno 2] No such file or directory: '大中天-1.txt' -> '[小中夭]'
$ tree
.
├── test
│   ├── 大中天-1.txt
│   ├── 大中天-2.txt
│   ├── 大中天-3.txt
│   ├── 大中天-4.txt
│   └── 大中天-5.txt
└── 批量重命名.py
'''
# 當前路徑下找不到那些文件
# 方法一: 跳進去改
# 方法二: 指定路徑
```
`vi 批量重命名.py`
```python
import os

folder_name = input("請輸入重命名的文件夾：")

file_name = os.listdir(folder_name)

'''方法一
os.chdir(folder_name)
for name in file_name:
    os.rename(name, "小中夭-" + name)
'''

'''方法二
for name in file_name:
    os.rename("./" + folder_name + "/" + name, "./" + folder_name + "/" + "小中夭-" + name)
    # os.rename(./test/大中天.txt, ./test/小中天-大中天.txt)
'''

# 方法二整理
for name in file_name:
    old_file_name = "./" + folder_name + "/" + name
    new_file_name = "./" + folder_name + "/" + "小中夭-" + name
    os.rename(old_file_name, new_file_name)
```

Class
---
`vi class_basic.py`
```python
#class 類名:
    # 屬性
    # 方法

class Cat: # Class 名建議首字大寫
    # 屬性

    # 方法:
    # def 定義在class裡面稱為方法
    # class 中的方法參數要寫一個參數，用來傳遞當前的對象，常用self
    def eat(self):
        print("貓在吃飯")

    def drink(self):
        print("貓在喝水")

    def introduce(self):
        # print("%s 的年齡是%d"%(tom.name, tom.age))
        print("%s 的年齡是%d"%(self.name, self.age))ff

# 創建一個對象
# 變量 = 執行class -> 執行class時，會在內存開啟一個空間(對象)並返回對象的引用(id)，接著用一個變量指向該引用
tom = Cat()

# 調用對象的方法
tom.eat()
tom.drink()

# 添加屬性
tom.name = "Tom"
tom.age = 100

# 輸出屬性方法一
# print("%s 的年齡是%d"%(tom.name, tom.age))

# 輸出屬性方法二
tom.introduce() # 相當於 tom.introduce(tom)

# 建立另一個對象
meowth = Cat()
meowth.name = "meowth"
meowth.age = 50
meowth.introduce()
'''Terminal
貓在吃飯
貓在喝水
Tom 的年齡是100
Tom 的年齡是100
# 問題出在introduce 的 print 不應取tom！
'''
```
`vi init.py`
```python
class Cat: 
   
    # 初始化對象
    def __init__(self, new_name, new_age):
        # print("------")
        self.name = new_name
        self.age = new_age

    def eat(self):
        print("貓在吃飯")

    def drink(self):
        print("貓在喝水")

    def introduce(self):
        print("%s 的年齡是%d"%(self.name, self.age))

tom = Cat("Tom", 100)
# tom.name = "Tom"
# tom.age = 100
tom.introduce()

meowth = Cat("Meowth")
# meowth.name = "meowth"
# meowth.age = 50
meowth.introduce()

'''Terminal
------
Tom 的年齡是100
------
meowth 的年齡是50

# 創建對象的流程
# ->等號右邊<-
# 1. 創建一個對象
# 2. python 會自動調用 __init__ -> 對象的引用會傳到__init__ -> self 指向該對象來執行 function

# ->等號左邊<-
# 3. 返回創建對象的引用給變數(tom/ meowth)
'''
```


import
---

* 查看import搜尋路徑

```python
In [1]: import sys
In [2]: sys.path
Out[2]: 
['/usr/local/bin',
'/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python37.zip',
'/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7',
'/usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload',
'',
'/usr/local/lib/python3.7/site-packages',
'/usr/local/lib/python3.7/site-packages/IPython/extensions',
'/Users/xxx/.ipython']
# 從上到下為搜索順序
# '' 表示當前路徑
# path為list，故可以增刪改查
```

* 增加搜索路徑

```python
In [xx]: sys.path.append("路徑")
In [xx]: sys.path.insert(0, "路徑") # 直接插入第一個
```

* 重新導入

```python
In [xx]: import test1     
In [xx]: cat test1.py  
def test():
    print("-1-")
In [xx]: test1.test() 
-1-

# 將test1.test()修改
In [xx]: cat test1.py 
def test():
    print("-2-")
    
In [xx]: test1.test()     
-1- # 直接使用時發現沒有變！

In [xx]: import test1 
In [xx]: test1.test()  
-1- # 再導入一次後再使用一樣沒變

# 方法一: 直接退出再重新導入

# 方法二: 使用 imp.reload()
In [xx]: from imp import * 
/usr/local/bin/ipython:1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses #!/usr/local/opt/python/bin/python3.7

In [xx]: reload(test1)
Out[xx]: <module 'test1' from '/Users/xxx/Desktop/python核心_note/01/test1.py'>
In [13]: test1.test()
-2-
```

* 循環導入

`vi a.py`
```python
from b import b

def a():
    print("a")
    b()

a()
```
`$ vi b.py`
```python
from a import a

def b():
    print("b")

def c():
    a()

c()
```
```Terminal
$ python3 a.py`
ImportError: cannot import name 'b' from 'b' (/Users/xxx/Desktop/python核心_note/01/b.py)

$ python3 b.py 
ImportError: cannot import name 'a' from 'a' (/Users/xxx/Desktop/python核心_note/01/a.py)

# 問題: a.py 跟 b.py會互相導入，造成死循環
# 解決:
    # 1. 程序設計分層，減少耦合
    # 2. 導入句不要放在上面，需要時再倒
```

is, ==
---
```python
In [1]: a = [1,2,3]
In [2]: b = [1,2,3]
In [3]: c = a

In [4]: a == b
Out[4]: True
In [5]: a is b
Out[5]: False

In [6]: a == c
Out[6]: True
In [7]: a is c
Out[7]: True

In [8]: id(a)
Out[8]: 4416556680
In [9]: id(b)
Out[9]: 4412183816
In [10]: id(c)
Out[10]: 4416556680

# == 是比較兩個對象的值是否相等
# is 是比較兩個引用是否指向同一對象
```
```python
In [11]: a = 100
In [12]: b = 100 
In [13]: a is b                
Out[13]: True

In [14]: a = 10000 
In [15]: b = 10000 
In [16]: a is b                
Out[16]: False

# 在一定數值內的num id都相同
```

深拷貝與淺拷貝
===
```python
In [1]: a = [1,2,3]
In [2]: b = a # 淺拷貝
In [3]: id(a)                  
Out[3]: 4414052424
In [4]: id(b)                  
Out[4]: 4414052424

In [5]: import copy
In [6]: c = copy.deepcopy(a) # 深拷貝
In [7]: c                      
Out[7]: [1, 2, 3]
In [8]: id(c)                 
Out[8]: 4418342984

In [9]: a.append(4)
In [10]: a                      
Out[10]: [1, 2, 3, 4]
In [11]: b                      
Out[11]: [1, 2, 3, 4]
In [12]: c                      
Out[12]: [1, 2, 3]
```
* copy 與 deepcopy 的不同

```python
In [1]: import copy  
In [2]: a = [1,2,3] 
In [3]: b = [4,5,6]            
In [4]: c = [a,b]

In [5]: d = copy.deepcopy(c) # deepcopy 連指向的飲用內容都獨立拷貝一份
In [6]: id(c)                  
Out[6]: 4576653192
In [7]: id(d)                  
Out[7]: 4576943816
In [8]: a.append(4) 
In [9]: c[0]                    
Out[9]: [1, 2, 3, 4]
In [10]: d[0]         # 故c指向的a增加時,d沒有改變(d的(x,x)不是指向a, b指向的引用)

In [11]: a = [1,2,3] 
In [12]: b = [4,5,6]
In [13]: c = [a,b] 
In [14]: d = copy.copy(c) # copy只有複製第一層 
In [15]: a.append(4) 
In [16]: c[0]                  
Out[16]: [1, 2, 3, 4]
In [17]: d[0]                  
Out[17]: [1, 2, 3, 4]
In [18]: id(c)                  
Out[18]: 4576305992
In [19]: id(d)                   
Out[19]: 4576688456
```
```python
In [1]: a = [1,2,3] 
In [2]: b = [4,5,6]            
In [3]: c = (a, b) 
In [4]: import copy
In [5]: d = copy.copy(c)
In [6]: id(c)                  
Out[6]: 4464671432
In [7]: id(d)                  
Out[7]: 4464671432

# copy 會根據當前數據類型是可變或不可變而有不同處理方式
# a = b 事實上就是淺拷貝
```

進制
---
```python
# 十進制轉二進制 bin(num)
In [10]: bin(18)                
Out[10]: '0b10010' # 0b代表2進制
# 十進制轉八進制 oct(num)
In [11]: oct(18)                 
Out[11]: '0o22' # 0o代表八進制
# 十進制轉十六進制 hex(num)
In [12]: hex(18)                
Out[12]: '0x12' # 0x代表十六進制

# x進制轉十進制 int("", x)
In [14]: int("0b10010",2)      
Out[14]: 18
In [17]: int("0o22", 8)         
Out[17]: 18
In [19]: int("0x12", 16)        
Out[19]: 18
```

私有化
---
- xx: 公有變量
- _xx & __xx 私有變量
- \_\_xx__: 
- xx_: 純粹讓你跟keyword有所區別, 不建議使用, e.x.: if_
---
* 公有變量

`$ vi 0201_test.py`
```python
class Test(object):
    def __init__(self):
        self.num = 100

t = Test()
print(t.num)
```
```
$ python3 0201_test.py 
100
```

* 私有變量

`$ vi 0201_test.py`
```python
class Test(object):
    def __init__(self):
        self.__num = 100

t = Test()
print(t.__num)
```
```
$ python3 0201_test.py 
AttributeError: 'Test' object has no attribute '__num'

# 私有化不能被『直接』使用
```
`$ vi 0201_test.py`
```python
class Test(object):
    def __init__(self):
        self.__num = 100

    def setNum(self, newNum):
        self.__num = newNum

    def getNum(self):
        return self.__num

t = Test()
#print(t.__num)

print(t.getNum())
t.setNum(50)
print(t.getNum())
```
```
$ python3 0201_test.py 
100 
50

# 私有化可以被間接使用
```
---
`$vi siyou.py`
```python
num = 100
_num2 = 200
__num3 = 300
```
```python
In [1]: from siyou import * 
In [2]: num                   
Out[2]: 100
In [3]: _num 
NameError: name '_num' is not defined
In [4]: __num 
NameError: name '__num' is not defined
In [5]: exit

# 私有變量無法被『from xx import xx』所導入
```
```python
In [2]: import siyou 
In [3]: siyou.num              
Out[3]: 100
In [6]: siyou._num2            
Out[6]: 200
In [7]: siyou.__num3             
Out[7]: 300
In [8]: exit

# import xxx 是整個模塊導入，所以可以
```
- 私有化原理：透過 name mangling (_類名+私有化名)

```python
In [1]: class Test(object): 
   ...:     def __init__(self): 
   ...:         self.__num = 100 
   ...: 
In [2]: t = Test() 
In [3]: t._Test__num  # 透過改名讓你不能用，事實上可以 
Out[3]: 100
```

- proprety

`vi 0201_test.py`

```python
class Test(object):
    def __init__(self):
        self.__num = 100

    def setNum(self, newNum):
        print("setNum")
        self.__num = newNum

    def getNum(self):
        print("getNum")
        return self.__num

    num = property(getNum, setNum)

t = Test()
#print(t.__num)

print(t.getNum())
t.setNum(50)
print(t.getNum())

print("-"*5)

t.num = 200
print(t.num)

# 註一: 
    # t.num = 200 時, property會調用第二個參數
    # 亦即 t.num= 200,就相當於 t.setNum(200)
# 註二:
    # print(t.num)時, property會調用第一個參數
    # 亦即 print(t.num) 相當於 print(t.getNum())

# proprety:
    # 相當於對方法進行封裝，使開發者對屬性設置時更方便
```
```
$ python3 0201_test.py
getNum
100
setNum
getNum
50
-----
setNum # 註一
getNum # 註二
200
```

生成器
---
- 列表生成式

`$ ipython3`
```python
In [1]: a = [x*2 for x in range(5)]
In [2]: a                      
Out[2]: [0, 2, 4, 6, 8]

# 如果我range過大，造成內存溢出時，會有程式崩的問題
# 如果需要一個很大的列表，且不想占用太大的內存空間，可以使用生成器
```
- 生成器(Generator)
    - 一邊循環一邊計算

`$ ipython3`
```python
# 生成器方法一: 只要把列表生成式的[]改成()即可
In [3]: b = (x*2 for x in range(5))
In [4]: b                      
Out[4]: <generator object <genexpr> at 0x11146acf0> # 此時b指向的id即存有生成器

# 使用 next() 來取返回值
In [5]: next(b)                
Out[5]: 0
In [6]: next(b)                
Out[6]: 2
In [7]: next(b)                 
Out[7]: 4
In [8]: next(b)                 
Out[8]: 6
In [9]: next(b)                 
Out[9]: 8
In [10]: next(b)
StopIteration:
```

- 完成[斐波那契數列](https://zh.wikipedia.org/wiki/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97)
    - 1,1,2,3,5,8...
    - 後面的值為前兩個之和

`$ ipython3`
```python
# 思考
In [1]: a,b = 0,1
In [2]: a,b = b,a+b 
In [3]: a                      
Out[3]: 1
In [4]: b                      
Out[4]: 1
In [5]: a,b = b,a+b 
In [6]: a                      
Out[6]: 1
In [7]: b                      
Out[7]: 2
In [8]: a,b = b,a+b 
In [9]: b                      
Out[9]: 3
In [10]: a,b = b,a+b 
In [11]: b                      
Out[11]: 5
# 只要一直循環a,b = b,a+b 
# 且輸出b, 即可完成斐波那契數列
```
`vi Fibonacci.py`
```python
def fibonacci():
    a,b = 0,1
    for i in range(5):
        print(b)
        a,b = b,a+b

fibonacci()

'''Terminal
$ python3 Fibonacci.py
1
1
2
3
5
'''
```
- 改成生成器:把 print()改成 yield

`$ vi Fibonacci.py`
```python
# 生成器方法二: yield
def fibonacci():
    print("Start")
    a,b = 0,1
    for i in range(5):
        yield b
        a,b = b,a+b
    print("Stop")

fibonacci()

"""Terminal
$ python3 fiboncaai.py
$

# 什麼都沒有！
# Fibonacci() 本應該至少有print()吧！！！
# 沒有print() 表示事實上該函數沒有被執行
"""

"""Terminal
$ cp fiboncaai.py test1.py
$ ipython3
In [1]: from test1 import *
In [2]: fibonacci()                 
Out[2]: <generator object Fibonacci at 0x10d720660>

# 只要函數裡有 yield, 該函數就應視為生成器, 而非普通函數
# 用普通函數的調用方法調用時, 生成器會生成一個對象並返回id
# 使用方法: 找變量指向該對象,並用 next()

In [3]: a = fibonacci()
In [4]: next(a)                 
Start
Out[4]: 1

# 此時有start 表示成功執行了,但卡在１!
"""
```
`vi test1.py`
```python
# 深入檢視問題
def fibonacci():
    print("Start")
    a,b = 0,1
    for i in range(5):
        print("-1-")
        yield b
        print("-2-")
        a,b = b,a+b
        print("-3-")
    print("Stop")

fibonacci()

"""Terminal
$ ipython3
In [2]: from test1 import * 
In [3]: a = fibonacci() 
In [4]: next(a)                
Start
-1-
Out[4]: 1

# -1-執行, -2-沒執行
# 表示 yield b ->讓程序停止了
# yield 運作模式:
    # 停止程序並返回 yield 後面的值 b
    # 故 next() 接收到返回值1
    # 緊接著再繼續執行next()時, 會從剛剛停止的地方繼續往下走
    
In [5]: next(a)                
-2-
-3-
-1-
Out[5]: 1
In [6]: next(a)                
-2-
-3-
-1-
Out[6]: 2
In [7]: next(a)                 
-2-
-3-
-1-
Out[7]: 3
In [8]: next(a)                  
-2-
-3-
-1-
Out[8]: 5
In [9]: next(a)                    
-2-
-3-
Stop
StopIteration:
"""

# 生成器特點:
    # 保存一套算法
    # 並非馬上算出值
    # 調用時才運算並返回值
```
`vi test1.py`
```python
# 為了避免程序異常(StopIteration),
# 調用時可以使用for循環來調用
def fibonacci():
    print("Start")
    a,b = 0,1
    for i in range(5):
        print("-1-")
        yield b
        print("-2-")
        a,b = b,a+b
        print("-3-")
    print("Stop")

a = fibonacci()
for num in a:
    print(num)

"""Terminal
Start
-1-
1
-2-
-3-
-1-
1
-2-
-3-
-1-
2
-2-
-3-
-1-
3
-2-
-3-
-1-
5
-2-
-3-
Stop
"""
```
```python
# 除了用next(a) 來調用
# 還可以用__next__()
def fibonacci():
    print("Start")
    a,b = 0,1
    for i in range(5):
        print("-1-")
        yield b
        print("-2-")
        a,b = b,a+b
        print("-3-")
    print("Stop")

a = fibonacci()

ret = a.__next__()
print(ret)

"""Terminal
Start
-1-
1
"""

# next() = __next__()
```

- send()

```python
In [1]: def test(): 
   ...:     i = 0 
   ...:     while i<5: 
   ...:         temp = yield i 
   ...:         print(temp) 
   ...:         i+=1 
   ...:            

In [2]: t = test()
In [3]: t.__next__()             
Out[3]: 0
In [4]: t.__next__()              
None
Out[4]: 1
In [5]: t.__next__()           
None 
Out[5]: 2

# temp 指向了 yield i 整體
# yield i 整體並沒有任何值! 
# i 本身才有
# 故 print(temp) 時得出 None

In [6]: t.send("haha")          
haha
Out[6]: 3
In [7]: t.send("gogo")            
gogo
Out[7]: 4

# send() 與 __next__() 一樣可以讓生成器繼續往下走一步
# 但send()可以傳值給 yield i 整體
```
```python
# 注意事項

# Q1. 如果第一次執行就用send?
In [1]: def test(): 
   ...:     i = 0 
   ...:     while i<5: 
   ...:         temp = yield i 
   ...:         print(temp) 
   ...:         i+=1 
   ...:   
In [2]: t = test()  
In [3]: t.send("haha")  # Q1
TypeError: can't send non-None value to a just-started generator 

# Q2. 都不給？
In [4]: t.send()
TypeError: send() takes exactly one argument (0 given) # 使用send一定要參數


In [5]: t.send(None) # 根據第一段的Error, 第一次生成器時,使用send一定要給None
Out[5]: 0
In [6]: t.send("haha")            
haha
Out[6]: 1
In [7]: t.__next__()             
None # Q3
Out[7]: 2

# Q3. 為何不是haha？
# Ans:
    # 因為 while 下一圈時又重新執行 yield i 
    # 此時用__next__()代表沒有給值,所以是 None
    # 解決辦法: 使用判斷

In [16]: def test(): 
    ...:     i = 0 
    ...:     while i<5: 
    ...:         if i == 0: 
    ...:             temp = yield i 
    ...:             print(temp)  
    ...:         else: 
    ...:             yield i 
    ...:             print(temp) 
    ...:         i+=1 
    ...:  

In [17]: t = test() 
In [18]: t.send(None)          
Out[18]: 0 # 此時程序會停在yield i
In [20]: t.send("haha")  # 故此時把 haha塞進去    
haha
Out[20]: 1 # 往後就都跑else了, i 不會等於0
In [21]: t.send("gogo")        
haha
Out[21]: 2
In [22]: t.send("baba")        
haha
Out[22]: 3
```
- 應用: 多任務-協程
```python
def test1():
    while True:
        print("-1-")
        yield None

def test2():
    while True:
        print("-2-")
        yield None

t1 = test1()
t2 = test2()
while True:
    t1.__next__()
    t2.__next__()
```

迭代器
---

- 迭代: 基於上一次工作的進度基礎上，完成下一個任務
- 可迭代對象(Iterable): 
    - 可以直接用for循環的對象
    - list, tuple, dict, set, str...
    - Generator
- 判斷是否為可迭代對象
```python
In [1]: from collections import Iterable   

In [2]: isinstance([], Iterable) 
Out[2]: True
In [3]: isinstance({}, Iterable)
Out[3]: True
In [4]: isinstance("abc", Iterable)              
Out[4]: True
In [5]: isinstance((), Iterable)
Out[5]: True
In [10]: isinstance((x for x in range(5)), Iterable)  
Out[10]: True

In [11]: isinstance(100, Iterable)  
Out[11]: False
```

- 迭代器(Iterator)
    - 可以用next()的,都是迭代器

```python
In [1]: from collections import Iterator

In [2]: isinstance((x for x in range(5)), Iterator) 
Out[2]: True

# 這些都是 Iterable 卻不是 Iterator!!!
In [3]: isinstance([], Iterator) 
Out[3]: False
In [4]: isinstance({}, Iterator) 
Out[4]: False
In [5]: isinstance("abc", Iterator)       
Out[5]: False
In [7]: isinstance((), Iterator) 
Out[7]: False

In [6]: isinstance(100, Iterator)                 
Out[6]: False
```
- Iter()
    - 把Iterable 轉成 Iterator
```python
In [8]: a = [1,2,3,4]                             
In [9]: type(a)                                   
Out[9]: list
In [10]: iter(a) 
Out[10]: <list_iterator at 0x109a3dc88>
In [11]: b = iter(a)
In [12]: b    
Out[12]: <list_iterator at 0x109dc0550>
In [13]: next(a) 
TypeError: 'list' object is not an iterator
In [14]: next(b)
Out[14]: 1
In [15]: next(b)    
Out[15]: 2
In [16]: next(b)
Out[16]: 3
In [17]: next(b) 
Out[17]: 4
In [18]: next(b) 
StopIteration: 
# 優點: b佔的空間比a小

In [19]: c = 100 
In [20]: iter(c) 
TypeError: 'int' object is not iterable
```

閉包
---
- 函數
```python
In [1]: def test(): 
   ...:     print("-1-") 
   ...:                                           
In [2]: test() 
-1-
In [3]: test   
Out[3]: <function __main__.test()>
# def : 創建一個函數體
# 而test指向該函數體

In [4]: b = test 
In [5]: b                                         
Out[5]: <function __main__.test()>
In [6]: b()                                       
-1-
# 如童a = 100, c =a, 
# a跟c 都是指向同一個對象
# b = test時, b跟test 指向同一個函數體
# 故b()也可以調用該函數體
# 這種方式只有在python有,在C++需運用『函數指針』來完成
```

- 閉包

```python
def test(num):
    print("-1-")
    def test_in(num2):
        print("-2-")
        print(num+num2)
    print("-3-")
    return test_in

test(100)
"""Terminal
-1-
-3-
"""
# test調用經過-1-後，接著遇到def test_in(), 
# 因為沒有調用, 故繼續往下走-3-,最後返回 test_in的指向
```
```python
def test(num):
    print("-1-")
    def test_in(num2):
        print("-2-")
        print(num+num2)
    print("-3-")
    return test_in

ret = test(100) # 此時找個變量接收返回值, 就跟b = test一樣
print("-4-")
ret(200) # 如此就可以調用
"""Terminal
-1-
-3-
-4-
-2-
300
"""
# 如此就是閉包的一種應用, 初始值100, 接著就可以在此基礎上做其他事情
```
![](https://i.imgur.com/bjA4pV1.png)

```python
def test(a,b):
    def test_in(x):
        print(a*x+b)
    return test_in

a = test(1,2)
a(0)
b = test(3,4)
b(5) 
a(0) # a,b 分別指向不同的函數體

"""Terminal
2
19
2
"""
"""回顧:ㄨ
def test2(a,b,x):
    print(a*x+b)
test2(3,4,5)
test2(3,4,7)
# 這種方法如果只想改一個值時,每次都要傳三個參數
# 用閉包就只需傳x即可
"""
```
```python
# 實現ax+b=y

# 方法一
a = 1
b = 2
y = ax+b
# 每計算一次就寫一次
# 計算別條線就要對全局變量寫一次
# 
 
# 方法二
def temp(a,b,x):
    print(a*x+b)

f = lambda a,b,x: a*x+b

temp(1,2,3)
f(1,2,3)
# 每次都要傳三個參數


# 方法三
a = 1
b = 2
def temp2(x):
    print(a*x+b)
temp2(3)
a = 11
b =22
temp2(3)
# 計算別條線時, 每次都要對全局變量進行修改

# 方法四: 缺省參數
def temp3(x, a=1, b=2):
    print(a*x+b)

temp3(3)
temp3(3, a=11, b=22)
# 計算別條線時, 要傳三個參數

# 方法四: class
class Temp4(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    #def test(self, x):
    #    print(self.a*x+self.b)
    def __call__(self, x):
        print(self.a*x + self.b)


temp = Temp4(1,2)
#temp.test(3)
temp(3)
temp2 = Temp4(11,22)
#temp.test(0)
temp(3)
# 佔用空間大

# 閉包
def temp(a, b):
    def temp2(x):
        print(a*x + b)
    return temp2 # 返回temp2的指向的函數空間

oo = temp(1,2) # oo 指向temp2指向的空間
oo(3) 
# 雖然temp2沒有a跟b, 邏輯跟函數用全局變量一樣, 
# 找不到就往外找, a,b 存在temp裡, 故使用temp的a跟b
xx = temp(11,22)
xx(3)

# 閉包二
def temp():
    a = 1
    b = 2
    def temp2(x):
        print(a*x + b)
    def temp3(y):
        print("-2-")
    return temp2, temp3 # 返回temp2的id

oo, xx = temp()
oo(3)
xx(4)
```
> - 小結:
>   - 匿名函數: 實現簡單功能
>   - 函數: 實現大功能編寫
>   - 閉包: 帶有數據的大功能編寫
>   - 對象: 什麼都有

#### nonlocal
> python3以後才能用
```python
x = 100
def oo():
    x = 200
    def xx():
        print("-1-%d" %x)
    return xx
temp = oo()
temp() # -1-200
```
```python
x = 100
def oo():
    x = 200
    def xx():
        x = 300
        print("-1-%d" %x)
    return xx
temp = oo()
temp() # -1-300
```
```python
# 問題來了
x = 100
def oo():
    x = 200
    def xx():
        print("-1-%d" %x)
        x = 300
        print("-2-%d" %x)
    return xx
temp = oo()
temp()
# 執行結果: 
# UnboundLocalError: local variable 'x' referenced before assignment
# python解釋器認為-1-的x是取300, 但是300在下面, 故報錯
```
```python
# 解法: 取消局部變量 > nonlocal
x = 100
def oo():
    x = 200
    def xx():
        nonlocal
        print("-1-%d" %x) # 既然沒有局部變量, 就找外面
        x = 300
        print("-2-%d" %x)
    return xx
temp = oo()
temp()
#-1-200
#-2-300
```

裝飾器
---
> - 同一個裝飾器可對多個函數裝飾
> - 裝飾器在函數調用前即執行裝飾
> - 每裝一次相當於一個閉包, 閉包外部函數的形參指向原函數
- 前情提要
```python
def test():
    print("-1-")
def test():
    print("-2-")

test()

"""Terminal
-2-
"""
# test()從指向 -1- 變成指向 -2-
# 名字如果沒有用過, 那麼就會定義變量去指向函數
# 名字如果用過了, 那麼就會導向新的函數
# 故名字盡量不要取一樣的
```

- 驗證

```python
# 老闆要求將以下def增加驗證程序,才能調用
def f1():
    print("f1")

f1()
```
```python
# 方法一
def f1():
    # 驗證
    print("f1")
f1()

# 問題:
    # 如果有一百萬個def 那不就要改一百萬次def？
``` 
```python
# 方法二
def check_login():
    # 驗證

def f1():
    check_login()
    print("f1")

f1()

# 問題:
    # 寫程式盡量遵循『開放封閉』原則
    # 開放: 對擴展開發
    # 封閉: 以實現功能的代碼塊
    # 
    # 因為一個函數可能有多個地方調用, 
    # 如果因為其中一個出現問題而改函數, 難免影響到其他調用者
    # 故邏輯上應該是再寫一個函數, 那個函數調用原函數
    # def a():
    #     pass
    # 
    # def b():
    #     a()
    # 而讓那個有問題的調b
```
```python
# 方法三 - 裝飾器原理
def w1(func):
    def inner():
        print("-驗證-")
        if True:
            func()
        else:
            print("沒有權限")
    return inner

def f1():
    print("f1")

#innerfunc = w1(f1)
#innerfunc()

# 流程:
    # w1(f1) 傳了實參給 w1(func) , 故 func 指向 f1()
    # w1 調用時會返回 inner 的 id, 故 innerfunc 指向 inner() 
    # 接著調用 innerfunc 時, 實現驗證功能後調用 func, 
    # 而 func 指向 f1() , 故驗證完就調用 f1()

# 問題:
    # 調用的方法都要改成innerfunc()

f1 = w1(f1)
f1()

# 流程:
    # w1調用時給實參f1, func指向f1()
    # 接著w1() return inner給 f1, 造成f1改指向inner()
    # 調用f1()時 = 調用 inner()
    # 驗證完之後調用func(), func()指向原本的f1()

# 如此就不用修改調用式而加入驗證功能！
# 
# 簡單說裝飾器就是改變原變量的指向,
# 將被裝飾的函數變量指向裝飾器的返回值
# 
# 裝飾器作用:
# 在不修改原代碼的前提下
# 對原函數的前後做一點事情, 如驗證, 計時
```
```python
def w1(func):
    def inner():
        print("-驗證-")
        if True:
            func()
        else:
            ("沒有權限")
    return inner

@w1 #  f1 = w1(f1)
def f1():
    print("f1")

@w1
def f2():
    print("f2")
    
f1()
```
```python
# 計時
import time

def w1(func):

    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print("all_time= %f" %(start_time - end_time))
    return inner

@w1 #  f1 = w1(f1)
def f1():
    print("f1")



f1()

# %f: 將浮點數以10進位方式輸出
# %d: 將整數以10進位方式輸出
```
```python
# 多個裝飾器
def test1(fn):
    print("A")
    def test1_in():
        print("-1-")
        return "<b>"+fn()+"</b>"
    return test1_in

def test2(fn):
    print("B")
    def test2_in():
        print("-2-")
        return "<i>"+fn()+"</i>"
    return test2_in

@test1
@test2
def test3():
    print("-3-")
    return "haha"

print(test3())
"""Terminal
B
A
-1-
-2-
-3-
<b><i>haha</i></b>
"""
# 裝飾由下而上, 執行由上而下
#     3裝2 打印B, 3指向2_in, 2的fn指向3
#     2裝1 打印A, 3指向1_in, 1的fn指向2_in
#     調用3() > 1_in > 調用1的fn而轉調用2_in >2的fn轉調用3, 打印123
#     最後haha回傳給2的fn穿<i>後再傳給1的fn穿<b>
```
```python
# 裝飾的時機
def test1(fn):
    print("裝飾中")
    def test1_in():
        print("-1-")
    return test1_in

@test1 # 解釋器解釋此行時即開始裝飾, 而非等到調用時才裝飾
def test2():
    print("-2-")
    
"""Terminal
裝飾中
"""
```
```python
# 總結: 裝飾器運作流程
def test1(fn):
    print("裝飾1")
    def test1_in():
        print("-執行1-")
        fn()
    return test1_in

def test2(fn):
    print("裝飾2")
    def test2_in():
        print("-執行2-")
        fn()
    return test2_in

@test1
@test2
def test3():
    print("-執行3-")

test3()
"""Terminal
裝飾2
裝飾1
-執行1-
-執行2-
-執行3-
"""
```
```python
# 對有參數的函數進行裝飾
def test(fn):
    def test_in():
        fn()
    return test_in

@test
def test2(a,b):
    print("a=%d, b=%d"%(a,b))

test2(1,2)
"""Terminal
TypeError: test_in() takes 0 positional arguments but 2 were given
"""
# test2(1,2) 對 test_in() 傳送了a,b兩個實參
# 故def test_in()應該有兩個形參來接收
```
```python
# 接續
def test(fn):
    def test_in(a,b):
        fn()
    return test_in

@test
def test2(a,b):
    print("a=%d, b=%d"%(a,b))

test2(1,2)
"""Terminal
TypeError: test2() missing 2 required positional arguments: 'a' and 'b'
"""
# 執行到fn() 時, 等於調用def test2(a,b)
# def test2(a,b) 有兩個形參, 故fn()必須給予兩個實參

def test(fn):
    def test_in(a,b):
        fn(a,b)
    return test_in

@test
def test2(a,b):
    print("a=%d, b=%d"%(a,b))

test2(1,2) 
```
```python
# 對不同多的參數函數進行裝飾
def test(fn):
    def test_in(*args,**kwargs):
        #fn(args, kwargs) 
            # args是以元祖保存, kwargs是以lib保存
            # 所以要傳要先拆包, 故直接原樣傳回去即可 
        fn(*args, **kwargs) # 拆包
    return test_in

@test
def test2(a,b):
    print("a=%d, b=%d"%(a,b))

def test3(a,b,c):
    print("a=%d, b=%d, c=%d"%(a,b,c))
    
def test4(*args, **kwargs):
    print(args)
    print(kwargs)

test2(1,2)
print("---------")
test3(1,2,3)
print("---------")
test4(1,2,3, n=100)

"""Terminal
a=1, b=2
---------
a=1, b=2, c=3
---------
(1, 2, 3)
{'n': 100}
"""
# 利用 args 跟 kwargs 就能避免只能傳指定個數的參數
```
```python
# 對有返回值的函數進行裝飾
def test1(fn):
    def test1_in():
        fn()
    return test1_in # test2的"haha"會返回到這, 
                    # 故要再返回出去

def test2():
    return "haha"

test2()
print("%s"%test2())
"""Terminal
haha
"""
```
```python
def test1(fn):
    def test1_in():
        fn()
    return test1_in

@test1
def test2():
    return "haha"

print("%s"%test2())
"""Terminal
None
"""

# 問題：當沒裝時，有返回haha, 裝上卻沒返回？
    # test2()實際上是調用test1_in, fn()才是調用test2()
    # 故haha是返回到fn(), 此時要把fn()傳回來才有東西
```
```python
def test1(fn):
    def test1_in():
        ret = fn() # 返回來的哈哈在這
        return ret # 把哈哈傳回去
    return test1_in

@test1
def test2():
    return "haha"

print("%s"%test2())
"""Terminal
haha
"""
```
```python
# 總結：通用裝飾器
def test1(fn):
    def test1_in(*args, **kwargs):
        print("日誌")
        return fn(*args, **kwargs)
            # 不論有無返回值都沒有影響
            # 沒返回值, 返回None給結果
            # 兩個以上也會存進元祖參數 *args
    return test1_in

# 沒有參數也沒有返回值
@test1
def test2():
    print("-2-")

# 有返還值
@test1
def test3():
    print("-3-")
    return "haha"

# 有參數
@test1
def test4(a):
    print("-4-%s"%a)

test2()
print(test3())
test4(1)

"""Terminal
日誌
-2-
日誌
-3-
haha
日誌
-4-1
"""
```
```python
# 帶有參數的裝飾器

# 如果我想要對godjj, toyz用同ㄧ個裝飾器做不同的裝飾
# 例如對某些情況嚴格驗證, 對某些情況輕鬆驗證
def test(func):
    print("-test-")
    def test_in(*args, **kwargs):
        print("-test_in-")
        return func(*args, **kwargs)
    return test_in

@test
def godjj(*args, **kwargs):
    print("-godjj-")

@test
def toyz(*args, **kwargs):
    print("-toyz-")


godjj()
toyz()

# 方法一調用傳參
# 缺點: 邏輯不對, 想法也不對
# 1. 如果有一百萬個人調用, 那就要改一百萬個調用?
# 2. 如果這裝飾是要做驗證, 參數怎麼會是使用者自己傳呢? 
def test(func):
    print("-test-")
    def test_in(*args, **kwargs):
        level = args[0]
        if level == 1:
            print("-test_in-")
        elif level == 2:
            print("-test_in2-")
        return func(*args, **kwargs)
    return test_in

@test
def godjj(*args, **kwargs):
    print("-godjj-")

@test
def toyz(*args, **kwargs):
    print("-toyz-")


godjj(1)
toyz(2)

# 方法二: 裝飾器傳參數

def level(level_num):
    def test(func):
        print("-test-")
        def test_in(*args, **kwargs):
            if level_num == 1:
                print("-test_in-")
            elif level_num == 2:
                print("-test_in2-")
            return func(*args, **kwargs)
        return test_in
    return test

@level(1) 
def godjj(*args, **kwargs):
    print("-godjj-")

@level(2)
def toyz(*args, **kwargs):
    print("-toyz-")


godjj()
toyz()
# 首先會執行level(1), 把參數傳進去
# 接著回傳test的位置, 執行@test裝飾def godjj()
```

- 類裝飾器
 
```python
# 前情提要
In [1]: class Test(object): 
   ...:     pass 
   ...: 
In [2]: t = Test()
In [3]: t()
TypeError: 'Test' object is not callable
```
```python
# 事實上類也可以直接調用
In [1]: class Test(object): 
   ...:     def __call__(self): 
   ...:         print("Test") 
   ...:
In [2]: t = Test()
In [3]: t() # 直接調用類會執行__call__
Test
```
```python
# 類裝飾器
In [1]: class Test(object): 
   ...:     def __init__(self, func): 
   ...:         print("---初始化---") 
   ...:         print("func name is %s"%func.__name__) 
   ...:         self.__func = func 
   ...:     def __call__(self): 
   ...:         print("---装饰器中的功能---") 
   ...:         self.__func() 
   ...: 
In [2]: @Test 
   ...: def test(): 
   ...:     print("----test---") 
   ...: 
---初始化---
func name is test

#说明：
#1. 当用Test来装作装饰器对test函数进行装饰的时候，首先会创建Test的实例对象
#   并且会把test这个函数名当做参数传递到__init__方法中
#   即在__init__方法中的func变量指向了test函数体
#
#2. test函数相当于指向了用Test创建出来的实例对象

In [3]: test()
---装饰器中的功能---
----test---


#3. 当在使用test()进行调用时，就相当于让这个对象()，因此会调用这个对象的__call__方法
#
#4. 为了能够在__call__方法中调用原来test指向的函数体，所以在__init__方法中就需要一个实例属性来保存这个函数体的引用
#   所以才有了self.__func = func这句代码，从而在调用__call__方法中能够调用到test之前的函数体
```


## 作用域
### 命名空間: 名字的起作用範圍

```python
In [1]: a = 100 
In [2]: a 
Out[2]: 100
# 打開一個ipython3空間, 在裡面定義ａ的指向, 
# 此時a 在此空間就起作用, 稱之為命名空間

In [3]: import test1 # 導入test1命名空間
In [4]: test1.test() 
test
In [5]: test()
NameError: name 'test' is not defined
# 當前的作用範圍空間內沒有test
# 而是在test1命名空間裡面才有test
```
```python
In [1]: from test1 import * # 從test1命名空間導入內容
In [2]: test() # 此時愛怎麼叫就怎麼叫
test
``` 
### Globals & Locals

```python
# globals()會以字典的形式返回所有的全局變量
In [1]: globals()                  
Out[1]: 
{'__name__': '__main__',
'__doc__': 'Automatically created module for IPython interactive environment',
'__package__': None,
'__loader__': None,
'__spec__': None,
'__builtin__': <module 'builtins' (built-in)>,
'__builtins__': <module 'builtins' (built-in)>,
...}

In [2]: a = 100
In [3]: def b(): 
   ...:     pass 
   ...:
In [4]: class c(): 
   ...:     pass 
   ...: 
   
# 每創建一個變量, 就會被丟進這個globals裡
# 如果要使用就必須要在這個globals裡面存在
In [5]: globals()                 
Out[5]: 
{...
'a': 100,
'b': <function __main__.b()>,
'c': __main__.c,
 ...}
 
# Q. 那某些特殊且常用的功能沒有在globals裡看到,為何能只用?
# A. 存在__builtin__裡 

# 因為globals返回的是一個字典
In [17]: xx = globals()["__builtin__"]
In [18]: xx                       
Out[18]: <module 'builtins' (built-in)>

# 用__dict__打開來看看
In [20]: xx.__dict__
{'__name__': 'builtins',
'__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.",
'__package__': '',
...
'print': <function print>, #
...}

# 此時字典裡面print對應的是一個函數, 就可以調用了
In [21]: yy = xx.__dict__
In [22]: yy["print"]              
Out[22]: <function print>
In [23]: yy["print"]("haha")      
haha


# 整理一下
In [24]: globals()["__builtin__"].__dict__["print"]("haha") 
haha
```

- Globals & Locals

```python
In [1]: a = 100 # 全局變量: 在ipython3裡作用
In [3]: def test(): 
   ...:     b = 200 # 局部變量: 在def test() 作用
   ...:     c = 300 
   ...:     print(locals()) 
   ...:    
In [4]: test()
{'b': 200, 'c': 300}
# locals()：返回值即所有的局部變量

In [5]: globals() # 返回所有的全局變量
Out[5]: 
{'__name__': '__main__',
 '__doc__': 'Automatically created module for IPython interactive environment',
 .
 .
 'a': 100,
 .
 .}
```
- LEGB: `locals -> enclosing function -> globals -> builtins`
```python
a = 100
    # globals
    # 都沒有時, 從全局變量找
def test1():
    #a = 200
        # enclosing function
        # 沒有局部變量時, 從外部嵌套找(閉包常見)
    def test2():
        #a = 300
            # locals(func, module, arg..)
            # 有局部變量時, 先找局部變量
        print(a)
    return test2

ret = test1()
ret()
```
```python
In [1]: dir(__builtin__)
Out[1]: 
['ArithmeticError',
 'AssertionError',
 .
 .
 'False',
 .
 'None'
 .
 'print',
 .
 .]

# 如果Globals 也沒有,就會到builtin裡面找, 都沒有就會程序錯誤
# builtin 存有常用的函數功能 (如print, list..等)
# 啟動python時, 首先會加載內建函數到內存, 
# 故不用 import 任何模塊就可以使用
```

動態語言
===
- 靜態語言: 運行之前先編譯, 且不允許再運行中修改code
```python
In [1]: a = 100 
In [2]: def test(): 
   ...:     pass 
   ...:  
In [3]: a = test()
In [4]: a
# 運行當中可以修改(type(num)->type(def))
```
```python
# 運行中添加屬性
In [1]: class Test(object): 
   ...:     pass 
   ...: 
In [2]: t = Test()
In [3]: t.age
AttributeError: 'Test' object has no attribute 'age' 
    # 定義類時沒有定義age屬性

In [4]: t.age = 18 # 運行中給『對象』添加屬性
In [5]: t.age
Out[5]: 18

In [6]: t2 = Test()  
In [7]: t2.age 
AttributeError: 'Test' object has no attribute 'age'
    # t.age是給對象添加屬性, 屬性存在t指向的對象中

# 直接給『類』添加屬性？
In [8]: Test.age = 19
In [9]: t2.age
Out[9]: 19
In [11]: t.age
Out[11]: 18 
    # 依據LEGB, 從裡面開始找, 實例屬性t就有age了, 故不會出來類找
In [13]: t3 = Test()
In [14]: t3.age
Out[14]: 19

```
```python
# 動態添加實例方法
In [1]: class Person(object): 
   ...:     def __init__(self, newName): 
   ...:         self.name = newName 
   ...:     def eat(self): 
   ...:         print("%s正在吃"%self.name) 
   ...: 
In [2]: p1 = Person("GodJJ")
In [3]: p1.eat()
GodJJ正在吃

In [4]: p1.run()
AttributeError: 'Person' object has no attribute 'run'

In [5]: def run(self): 
   ...:     print("%s正在跑"%self.name) 
   ...: 
In [6]: p1.run = run
In [7]: p1.run()
TypeError: run() missing 1 required positional argument: 'self'
    # 添加屬性可以x.xxx = xxx
    # 但添加方法如此作法雖然p1對象中的run屬性已經指向def run(self)
    # 但是self並沒有指向p1, 所以出現缺少參數的錯誤

# 正確的動態添加實例方法
In [8]: import types
In [9]: p1.run = types.MethodType(run, p1) 
    # method(function, instance)
In [10]: p1.run()
GodJJ正在跑

In [13]: xxx = types.MethodType(run, p1)
In [14]: xxx()
GodJJ正在跑
# 左邊只是個變量而已，寫啥都可, 為了可讀性, 一般都會寫清楚
```
```python
# 動態添加靜態方法
In [15]: @staticmethod 
    ...: def test(): 
    ...:     print("-staticmethod-") 
    ...:
In [16]: Person.test = test
In [17]: Person.test() 
-staticmethod-
In [18]: Person.xxx = test
In [19]: Person.xxx()
-staticmethod-

In [21]: p1.test = test
In [22]: p1.test()
TypeError: 'staticmethod' object is not callable

# 動態添加類方法
In [23]: @classmethod 
    ...: def test2(cls): 
    ...:     print("-classmethod-") 
    ...:
In [24]: Person.test2 = test2
In [25]: Person.test2()
-classmethod-
In [26]: Person.xxx = test2
In [27]: Person.xxx()
-classmethod-

In [28]: p1.test2 = test2
In [29]: p1.test2()
TypeError: 'classmethod' object is not callable

# 小結
In [30]: p2 = Person("p2")
In [31]: p2.eat()
p2正在吃
In [32]: p2.test()
-staticmethod-
In [33]: p2.test2()
-classmethod-
In [34]: p2.run = types.MethodType(run, p2)
In [35]: p2.run()
p2正在跑
In [36]: p2.xxxx = "aaaaa"
In [37]: p2.xxxx
Out[37]: 'aaaaa'
```
- 動態添加實例
    - app在沒有更新的情況下卻改版？
    - 實際上他將新的代碼傳到app後台裏
    - 待開啟app後執行更新

- __slots__
    - 限制實例屬性
```python
In [1]: class Test(object): 
   ...:     __slots__ = ("name", "age") 
   ...: 
In [2]: t = Test()
In [3]: t.name = t 
In [4]: t.age = 10
In [5]: t.addr = "xxooxxo"
AttributeError: 'Test' object has no attribute 'addr'

# slots 限制只能添加name與age屬性, 故添加以外的屬性即錯誤
```

元類
---

```python
# 觀念一
In [1]: class Test(object): 
   ...:     a = 100 
   ...:     print("Test") 
   ...:     def __init__(self): 
   ...:         self.name = "haha" 
   ...:
Test
# Q:沒有其他操作就打印 Test ?
# 實際上定義class後，解釋器會自動創建一個class

# 觀念二
In [2]: print(100)
100
In [3]: print("100")
100
In [4]: print(Test)
<class '__main__.Test'> 
# 類能夠被打印出來，證明類實際上也是一個對象
```
> - 類的功能: 
>   - 創建實例對象, 
>   - 且類的數據可讓實例對象共享, 而實例對象的數據則自己獨有
> - 元類創建類, 類創建實例對象
> - Python萬物皆對象

### 動態創建

```python
In [5]: def test2(name): 
   ...:     if name == "haha": 
   ...:         class Haha(object): 
   ...:             pass 
   ...:         return Haha 
   ...:     else: 
   ...:         class Hihi(object): 
   ...:             pass 
   ...:         return Hihi 
   ...: 
# 根據參數不同，創建不同的實例對象
In [7]: t = test2("haha")
In [8]: print(t) # 函數返回的是一個類
<class '__main__.test2.<locals>.Haha'>
In [9]: print(t()) # 通過類創建類實例對象
<__main__.test2.<locals>.Haha object at 0x11192ac88>
```
### Type

> - Type即使用動態創建, 根據不同參數產生不同功能
```python
# type功能一: 查看某對象是通過哪個類所創建出來的
In [10]: class Apple(object): 
    ...:     pass 
    ...:
In [11]: type(Apple())
Out[11]: __main__.Apple # Apple()通過Apple創建一個類對象
In [12]: type(100)
Out[12]: int # 100是通過int創建一個類對象
In [13]: type("apple")
Out[13]: str #"apple"就是str創建出來的一個對象
    # 類型(str, int,...)實際上是一個類!
```
```python
# type功能二: 元類
    # 動態創建對象
    # type("類名", (繼承,), 有的沒的{"name":value})
    # 元類是用來控制如何創造類, 如同類如何創造實例對象

# - 類屬性
In [1]: class Test1: 
   ...:     a = 100 
   ...:
In [2]: Test2 = type("Test2", (), {"a":100})
In [3]: help(Test1)
In [4]: help(Test2)
# 兩個都是類
In [5]: Test1()
Out[5]: <__main__.Test1 at 0x1048902e8>
In [6]: Test2()
Out[6]: <__main__.Test2 at 0x104890b70>

# 兩個都是type所創造的類
  # Test1/Test2 是type的一個類實例
In [4]: type(Test1) # type
In [5]: type(Test2) # type

# t1/t2 是 Test1/Test2 的一個類實例
In [6]: t1 = Test1()
In [7]: t2 = Test2()
In [8]: type(t1) # __main__.Test1
In [9]: type(t2) # __main__.Test2


# - 繼承
In [8]: class Test11(Test1): 
   ...:     pass 
   ...: 
In [9]: help(Test11)
In [10]: Test22 = type("Test22", (Test2,), {})
In [11]: help(Test22) 

# - 實例方法
In [14]: class Test111(): 
    ...:     def test_111(self): 
    ...:         print("haha") 
    ...: 
In [17]: t111 = Test111()
In [18]: t111.test_111() # haha

  # 直接寫在頂級作用域上
In [19]: def test_222(self): 
    ...:     print("test_222") 
    ...: 
  # 再拿一個鍵去指向
In [21]: Test222 = type("Test222", (), {"test_222":test_222}) 
In [23]: t222 = Test222()
In [24]: t222.test_222()
test_222

# - 類方法 
# 複習: 實例方法vs類方法
#   - 實例方法的第一個參數要指向創造出來的實例對象
#   - 故必須要先創造實例對象才能使用該方法, 否則會有缺行參的報錯
#   - 類方法的第一個參數則指向類對象
#   - 故不需創造則可直接用
In [26]: class Test1111(object): 
    ...:     @classmethod 
    ...:     def test_1111(cls): 
    ...:         print("test_1111類方法") 
    ...: 
In [27]: t1111 = Test1111()
In [28]: t1111.test_1111()
test_1111類方法
In [29]: Test1111.test_1111() # 直接用
test_1111類方法

In [30]: @classmethod 
    ...: def test_2222(cls): 
    ...:     print("test_2222類方法") 
    ...:
In [31]: Test2222 = type("Test2222", (), {"test_2222":test_2222})
In [33]: Test2222.test_2222() # 直接用
test_2222類方法

# - 靜態方法
# 複習: 靜態方法常用在類的某個方法沒有需要使用參數的時候
In [35]: class Test11111(object): 
    ...:     @staticmethod 
    ...:     def test_11111(): 
    ...:         print("test_11111") 
    ...:
In [36]: Test11111.test_11111()
test_11111

In [37]: @staticmethod 
    ...: def test_22222(): 
    ...:     print("test_22222靜態方法") 
    ...:
In [38]: Test22222 = type("Test22222", (), {"test_22222":test_22222})
In [39]: Test22222.test_22222() # test_22222靜態方法

# 統整
In [44]: Test3 = type("Test3", (Test2,), {"a":100, "test_222":test_222, "test_2222":test_2222, "test_22222":test_22222})
In [46]: Test3.test_2222()
test_2222類方法
In [47]: Test3.test_22222()
test_22222靜態方法
In [48]: t3 = Test3()
In [50]: t3.test_222()
test_222
In [51]: Test3.a
Out[51]: 100


# Test3創建t3
In [54]: t3.__class__ 
Out[54]: __main__.Test3
# type創建Test3
In [55]: t3.__class__.__class__ 
# type創建type
Out[55]: type
In [56]: t3.__class__.__class__.__class__ 
Out[56]: type
```

### __metaclass__

```python
# 由上可知, class是由元類所創建
# 那他是如何創建的呢？
class Test(object):
    __metaclass__ = xxx
        # 事實上類都是用__metaclass__所創建
        # __metaclass__的搜尋路徑
            # class裡面找->父類->module->buildIn
```

```python
# 舉例應用
#-*- coding:utf-8 -*-

# python2
#class Haha(object):
#    __metaclass__ = upper_attr
#    gogo = "我愛How哥, How哥Number1"

# python3
# 設置Haha的元類為upper_attr
# metaclass預設為type
class Haha(object, metaclass = upper_attr): 
    gogo = "我愛How哥, How哥Number1"

# def upper_attr("Foo", (object,) {"gogo": 我愛..})
def upper_attr(newName, newBases, newDict):
    newAttr = {}
    # 遍歷屬性字典, 把開頭不是__的屬性名都改成大寫
    for name,vaule in newDict.items():
        # startswith 開頭
        # endswith 結尾
        if not name.startswith("__"): 
            # upper 小寫轉大寫
            newAttr[name.upper()] = vaule
           # type("Foo", (object,) {"GOGO": 我愛..})
    return type(newName, newBases, newAttr) 
        # 此時Haha就指向這個返回值
    
print(hasattr(Haha, "gogo"))
print(hasattr(Haha, "GOGO"))

How = Haha()
print(How.GOGO)
print(How.gogo)

"""Terminal
False
True
我愛How哥, How哥Number1
AttributeError: 'Haha' object has no attribute 'gogo'
"""

# 小結:
    # 元類決定了將來class如何創建
    # 當你指定metaclass屬性為一個函數名, 該函數如何創建即決定該類樣子
    # 
    # 故平常我們定義的類並非他原來的樣子,
    # 他會經過type整容後返回的type才是真正類的模樣

# 思考: python3的類不寫object也會繼承object, 如何完成的?
#    - 傳進元類, 
#    - 遍歷判斷繼承有沒有object, 
#    - 沒有就創一個元祖多加一個object後傳回去
```
```python
# 用類實現, 
# 畢竟叫做元類, 還是應該用類來實現
class Haha(object, metaclass = upper_attr): 
    gogo = "我愛How哥, How哥Number1"

class Upper_Attr(type):
    
    # __new__是在創建類實例對象的過程中最先調用的方法, 並返回類實例對象
    # __init__則是在類實例創建出來後初始化參數給對象
    # 這裡想要自定義類, 故改寫__new__
    def __new__(cls, newName, newBases, newDict):
        newAttr = {}
        for name,vaule in newDict.items():
            if not name.startswith("__"): 
                newAttr[name.upper()] = vaule
            
    return type(newName, newBases, newAttr) 
```

### 整理思緒: 生成類與實例類的過程
```python
# 當我定義一個類時, 他就會執行元類來生成類
  # 預設元類: type
  In [1]: class Foo(metaclass=type): # Foo = type('Foo', (), {'__init__':__init__})
     ...:     def __init__(self): 
     ...:         pass 
  In [2]: print(Foo()) # <__main__.Foo object at 0x111463d30>


# 自己定義元類
  In [3]: class MyType(type): 
     ...:     def __init__(self): 
     ...:         pass 
  In [4]: class Foo(metaclass=MyType): 
     ...:     def __init__(self): 
     ...:         pass 
  # TypeError: __init__() takes 1 positional argument but 4 were given
  # 我只是定義兩個類, 為何缺形參？
    # 因為在定義類的時候, 預設都會執行 type() 類來生成我們定義的類
    # 也就是 MyType('Foo', (), {'__init__':__init__})
    # 執行類方法之前會先初始化對象 MyType.__init__(self)
    # 而 MyType.__init__(self) 只有一個形參 self
    # 故產生要一個給四個的報錯
  In [15]: class MyType(type): 
      ...:     def __init__(self, a, b, c): 
      ...:         print(self) 
      ...:         print(a) 
      ...:         print(b) 
      ...:         print(c) 

  In [16]: class Foo(metaclass=MyType): 
      ...:     def __init__(self): 
      ...:         pass 

    # <class '__main__.Foo'> // self
    # Foo // 類名
    # () // 繼承
    # {'__module__': '__main__', '__qualname__': 'Foo', 
    # '__init__': <function Foo.__init__ at 0x1112f9ea0>} // 內容物

  # 也就是說創見類的時候會把對象, 類名, 繼承, 內容物 傳到元類中,
  # MyType(Object, name, Inherit, 內容物), 而由元類的__init__接收來初始化
  # 此時元類創造的類就定義好了
  In [17]: class MyType(type): 
      ...:     def __init__(classObject, className, classInherit, classDict): 
      ...:         print(classObject) 
      ...:         print(className) 
      ...:         print(classInherit) 
      ...:         print(classDict)

  In [20]: class Foo(metaclass=MyType): 
      ...:     def __init__(self): 
      ...:         pass 

  In [24]: type(Foo) # __main__.MyType
  
# 創建類實例
# 以前學過的創建實例類
  # 先創建一個類
  In [38]: class Woo(object): 
      ...:     def __init__(self, number): 
      ...:         self.number = number 

  # 接著實例類
  In [39]: w = Woo(2) 

  # 此時就可以操作那個實例了
  # 因為實例類會自己調用__init__方法
  In [40]: print(w.number) # 2
  In [42]: print(w.__dict__) # {'number': 2}
  
# 問題: 實例類如何自己調用 __init__ 的?
  # 事實上實例類並非自己調用
  # Woo(2) 的時候, 是調用 Woo.__call__(self, *args, **kwargs)
  # 而 Woo.__call__ 會調用 Woo.__new__ 方法來創建一個實例類
  # 然後調用 Woo.__init__ 方法, 並把資料封裝存進剛創建的對象中
  # 最後再將該實例類返回
  
  # 而因為我只有改 Woo.__init__, 所以其他都是直接用object的
    # object.__call__
    # object.__new__
  

# 證明
  In [64]: class MyType(type): 
      ...:     def __init__(cls, className, classBases, classDict): 
      ...:         pass 
      ...:     def __call__(cls, *args, **kwargs): 
      ...:         print(cls) 
      ...:         print(args) 
      ...:         print(kwargs) 

  In [65]: class Foo(metaclass=MyType): 
      ...:     def __init__(self, number, n): 
      ...:         self.number = number 
      ...:         self.n = n

  In [66]: f = Foo(2, n=1)  
  # Out: 
    # <class '__main__.Foo'>
    # (2,)
    # {'n': 1}
  # 當我執行 Foo(3) 時, 他會執行 Foo.__call__()
  # 而因為我的 Foo.__call__ 啥也沒幹, 只有print
  # 所以 f 裡面當然啥都沒有
  
  In [67]: f.number # 'NoneType' object has no attribute 'number'
  In [68]: f.__dict__ # 'NoneType' object has no attribute '__dict__'
  In [70]: print(f) # None

# 完整流程
  In [93]: class MyType(type): 
      ...:     def __init__(cls, className, classBases, classDict): 
      ...:         pass 
      ...:     def __call__(cls, *args, **kwargs): 
      ...:         obj = object.__new__(cls) 
      ...:         cls.__init__(obj, *args, **kwargs) 
      ...:         return obj 

  In [94]: class Foo(metaclass=MyType): # Foo = MyType(FooObj, 'Foo', (), {})
      ...:     def __init__(self, number1, n): 
      ...:         self.number = number1 
      ...:         self.n = n 

  In [95]: f = Foo(2, n=1)
  In [96]: f.number # 2
  In [97]: f.n # 1
  In [98]: f # <__main__.Foo at 0x1113c5588>

# 當然object是預設的, 要改也可以自己改
  In [102]: class MyType(type): # 1
       ...:     def __init__(cls, className, classBases, classDict): # 2
       ...:         pass # 3
       ...:     def __call__(cls, *args, **kwargs): # 4
       ...:         obj = cls.__new__(cls) # 5
       ...:         cls.__init__(obj, *args, **kwargs) # 6
       ...:         return obj # 7
       ...:     def __new__(cls, newName, newBases, newDict): # 8
       ...:         return type(newName, newBases, newDict) # 9

  In [103]: class Foo(metaclass=MyType): # 10
       ...:     def __init__(self, number1, n): # 11
       ...:         self.number = number1 # 12
       ...:         self.n = n # 13

  In [104]: f = Foo(2, n=1) # 14

  In [105]: f.number # 15
  In [106]: f.n # 16
  In [107]: f # 17
  
# 流程
  # 一開始會先加載一遍 「1~13」
  # 接著創建類 「13 -> 2」 的 MyType.__init__
    # cls: <class '__main__.Foo'> 
    # className: 'Foo' 
    # classBases: <class 'tuple'>: ()
    # classDicr: {'__module__': '__main__', '__qualname__': 'Foo',
    #             '__init__': <function Foo.__init__ at 0x1112f9ea0>}
  # 創建完後就直接跳 「14」 讀取 F00(2, n=1) 後執行 Foo.__call__ 「4」
    # cls: <class '__main__.Foo'>
    # args: <class 'tuple'>: (2,)
    # kwargs: {'n': 1}
  # 「5->8->9」執行完後就創建出實例類了, 並返回了對象的地址
  # 「9->6」執行完後把args跟kwargs的東西封裝到那個對象裡
  # 「6->7」最後把對象地址回傳到「14」讓 f 指向
```



垃圾回收
---

- 小整數池(-5~256)
    - 共用對象，常駐內存
    - 引用數0時, 不會銷毀
    - python提前建立好的
- 大整數池
    - 各自創建對象
```python
# 小整數池，共用對象
In [1]: a = 256
In [2]: b = 256
In [3]: id(a)                        
Out[3]: 4558483568
In [4]: id(b)                         
Out[4]: 4558483568

In [9]: e = -5
In [10]: f = -5
In [11]: id(e)                     
Out[11]: 4558475216
In [12]: id(f)                      
Out[12]: 4558475216

# 大整數池, 各自創建
In [5]: c = 257
In [6]: d = 257
In [7]: id(c)                        
Out[7]: 4590648272
In [8]: id(d)                        
Out[8]: 4590648688

In [13]: g = -6
In [14]: h = -6
In [15]: id(g)                      
Out[15]: 4588832656
In [16]: id(h)                       
Out[16]: 4588833328

# 不會銷毀
In [28]: a = 100
In [29]: id(a)                      
Out[29]: 4558478576
In [30]: del(a)
In [31]: b = 100
In [32]: id(b)                      
Out[32]: 4558478576
```

- intern機制
    - 單個字符，共用對象
    - 引用數0，銷毀
    - 字符串(含空格)，不共用對象 
```python
# 單個字符，共用對象
In [17]: a = "haha"
In [18]: b = "haha"
In [19]: id(a)                     
Out[19]: 4589276104
In [20]: id(b)                      
Out[20]: 4589276104

# 字符串，不共用對象
In [21]: c = "ha ha"
In [22]: d = "ha ha" 
In [23]: id(c)                     
Out[23]: 4591450352
In [24]: id(d)                      
Out[24]: 4591451472

# 引用數0，銷毀
In [36]: a = "haha"
In [37]: id(a)                      
Out[37]: 4591776464
In [38]: del(a)
In [39]: b = "haha"
In [40]: id(b)          
Out[40]: 4591897152
```
```python
# Num & Str 不可變
# 修改 = 重創
In [41]: a = 100 
In [42]: id(a)                     
Out[42]: 4558478576
In [43]: a+=1
In [44]: id(a)                     
Out[44]: 4558478608
In [45]: b = "haha"
In [46]: id(b)              
Out[46]: 4591897152
In [47]: b = "gogo"
In [48]: id(b)               
Out[48]: 4591728880
```

- Garbage Collection(GC)
    - 高級語言大多都採『垃圾收集機制』
    - C/C++ 採用戶自行管理內存，
        - 自行申請( malloc(100) )與清除( free )內存
        - 問題：內存泄露、懸空指針
    - Python採
        - 『引用計數機制』為主
        - 標記-清除、Generation Zero為輔

- 引用計數機制
    - 優點: 簡單、即時
    - 缺點:
        - 維護引用計數消耗資源
        - 循環引用 
```c
// python的解釋器是由c語言所撰寫的, 稱為cpython

typedef struct_object {
    int ob_refcnt; 
    struct_typeobject *ob_type;
} PyObject;

// Q. 佔用字節
    // char 1
    // int 4
    // float 4
    // double 8
    // 故 ob_refcnt 最大能存4個字節, 亦即2^31
// ob_refcnt 
    // ob: object
    // refcnt 引用計數器
    // cpython解釋python時, 每一對象的引用計數皆以此變量儲存

#define Py_INCREF(op)   ((op)->ob_refcnt++) // 增加計數
#define Py_DECREF(op) \ //減少計數
    if (--(op)->ob_refcnt != 0) \ // 計數為0
        ; \ // 幹掉
    else \
        __Py_Dealloc((PyObject *)(op))
```

- 循環引用

```python
# 舉例一
import gc

class A(object):
    pass

def test():
    while True:
        c1 = ClassA() # 創建一個對象
        c2 = ClassA() # 創建另一對象
        c1.t = c2 # 實例屬性指向c2對象
        c2.t = c1 # 實例屬性指向c1對象
            # 此時兩個對象計數皆為2
        del c1
        del c2
            # 各坎一個, 還剩1 ,故不會刪除
            # 但是沒有任何變量指向這兩個對象
            # 然後死循環繼續不停創建沒有變量的對象
            # 內存使用量飆升, 程序不穩定
        gc.collect() # 手動回收

gc.disable() # 關掉gc
test()

# 舉例二
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
```

- 補充: 數據結構-[鏈表](https://zh.wikipedia.org/wiki/%E9%93%BE%E8%A1%A8)
    - [單項鍊錶](https://zh.wikipedia.org/wiki/%E5%8D%95%E5%90%91%E9%93%BE%E8%A1%A8)
    - [雙項鍊表](https://zh.wikipedia.org/wiki/%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8)
        - 歌詞顯示(xxx.lrc)
            - 快轉倒轉, 歌詞都會跟著動
            - 實踐: 每句歌詞放到各鏈表中, 對應時間

- [Visualizing Garbage Collection in Ruby and Python](http://patshaughnessy.net/2013/10/24/visualizing-garbage-collection-in-ruby-and-python)
    - Ruby : 標記-清除
        - 把有用的標記起來，內存用光時清掉沒有標記的
    - Python : Generation Zero
        - 共有三代鏈表，
        - 每次條件到時，檢測有無循環引用, 測到的-1, 引用數0的釋放
        - 沒有相互引用的會被丟到一代鏈表
    - 區別
        - 申請分配空間 
            - Ruby 先創建空間，待使用者使用
            - Python 待申請後才創建
        - 清除時機
            - Ruby 待內存用光了才清
            - Python 變量為0馬上丟

- 常用函數

```python
In [1]: import gc 

# 獲取gc自動回收的頻率
In [2]: gc.get_threshold() 
Out[2]: (700, 10, 10) 

# 獲取當前自動回收的計數器
In [3]: gc.get_count()              
Out[3]: (17, 0, 11)
In [4]: gc.get_count()              
Out[4]: (17, 4, 13)
In [5]: gc.get_count()             
Out[5]: (17, 5, 15)

# 查看引用記數
In [1]: import sys

In [9]: a = "KKBOX" # +1
In [10]: sys.getrefcount(a) # +1
Out[10]: 2
In [11]: b = a # +1
In [12]: sys.getrefcount(a)       
Out[12]: 3

# GC開關
    # gc.enable()
        # Enable automatic garbage collection.
    # gc.disable()
        # Disable automatic garbage collection.
    # gc.isenable()
        # Returns true if automatic collection is enabled

# 手動執行回收
    # gc.collect()

# 清理訊息
    # gc.garbage
```

- 引用計數+1
    - `a = "HaHA"` (創建對象)
    - `b = a` (引用對象)
    - `func(a)` (實參: 會有形參去引用,故會+1)
    - `list1 = [a,a]` (對象儲存在容器中)
   
- 引用計數-1
    - `del a` (刪除變量)
    - `a = "DD"` (改變引用)
    - func(a) 執行完畢, 局部變量-1
    - 刪除列表或列表中的對象

- 回收時機
    - gc.collect()
    - 到達threshold
    - 程序退出時

- 注意點
    - 所謂的GC，就是調用該對象的__del__
    - 如果重寫了__del__，會產生uncollectable的情況
    - 如果真的需要重寫，在最後記得調用父類的__del__，以清理之 

內建屬性
===

- \_\_str__ vs \_\_repr__

```python
In [1]: a = "GodJJ"
In [2]: a                      
Out[2]: 'GodJJ' # repr
In [3]: print(a)                
GodJJ # str
```

- \_\_getattribute__

```python
class Itcast(object):
    def __init__(self,subject1):
        self.subject1 = subject1
        self.subject2 = 'cpp'

    #属性访问时拦截器，打log
    def __getattribute__(self,obj): 
        if obj == 'subject1':
            print('log subject1')
            return 'redirect python'
        else:   #测试时注释掉这2行，将找不到subject2
            return object.__getattribute__(self,obj) # 把傳來的東西傳回去

    def show(self):
        print('this is Itcast')

s = Itcast("python")
print(s.subject1) # subject1->obj
print(s.subject2) # subject2->obj
```
```output
log subject1
redirect python
cpp

# 本來的結果應該是
    # python
    # cpp

# 由於__getattribute__攔截屬性
# 變成把調用的屬性變量傳給 obj 來執行 if..else..

# 應用
    # Log日誌：何時訪問都記錄下來，保密防諜
```
```python
# 邏輯測試

class Itcast(object):
    def __init__(self,subject1):
        self.subject1 = subject1
        self.subject2 = 'cpp'

    #属性访问时拦截器，打log
    def __getattribute__(self,obj):
        print("==>1:%s"%obj)
        if obj == 'subject1':
            print('log subject1')
            return 'redirect python'
        else: 
            temp = object.__getattribute__(self,obj)
            print("===>2:%s"%str(temp))
            return temp

    def show(self):
        print('this is Itcast')

s = Itcast("python")
print(s.subject1)
print(s.subject2)

s.show()
    # 先把show傳進去obj, 並會得到一個對象的id
    # 再將那對象的id傳回來執行
```
```
==>1:subject1
log subject1
redirect python
==>1:subject2
===>2:cpp
cpp
==>1:show
===>2:<bound method Itcast.show of <__main__.Itcast object at 0x110301518>>
this is Itcast
```
```python
# 不返回temp
class Itcast(object):
    def __init__(self,subject1):
        self.subject1 = subject1
        self.subject2 = 'cpp'

    #属性访问时拦截器，打log
    def __getattribute__(self,obj):
        print("==>1:%s"%obj)
        if obj == 'subject1':
            print('log subject1')
            return 'redirect python'
        else: 
            temp = object.__getattribute__(self,obj)
            print("===>2:%s"%str(temp))
            # return temp

    def show(self):
        print('this is Itcast')

s = Itcast("python")
print(s.subject1)
print(s.subject2) # 沒有返回所以是None

s.show()
    # 先將show傳入obj中, 並得到一個object的id
    # 但沒有回傳, 故此時等於None()
    # 所以產生TypeError
```
```
==>1:subject1
log subject1
redirect python
==>1:subject2
===>2:cpp
None
==>1:show
===>2:<bound method Itcast.show of <__main__.Itcast object at 0x11018c518>>
TypeError: 'NoneType' object is not callable
```
觀念更新
```python
import types
p1.eat = types.MethodType(eat,p1)
# 事實上並非在p1中創建eat
# 而是在一對象中創建一個變量，指向另一個對象
```
```python
In [1]: class Test(): 
   ...:     pass 
   ...: 
In [2]: t = Test()  
In [3]: t.num
AttributeError: 'Test' object has no attribute 'num'
In [5]: t.num()  
AttributeError: 'Test' object has no attribute 'num'
    # 錯誤訊息是寫沒有這個『屬性』而非函數
    # 也就是說對象裡面不會有函數, 只有變量指向一個函數

```
內建屬性
---
- range
```
$ipython2
In [1]: range(0,10)
Out[1]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [2]: xrange(0,10)
Out[2]: xrange(10)
    # 節省內存
```
- map
    - 應用：矩陣 
```python
map(function, sequence[, sequence, ...]) -> list
```
```python
# 一個參數
In [10]: ret = map(lambda x:x*x, [1,2,3])
In [11]: ret
Out[11]: <map at 0x10b9493c8>
In [12]: for tmp in ret: 
    ...:     print(tmp) 
    ...:          
1
4
9

# 兩個參數
In [13]: ret = map(lambda x,y:x+y, [1,2,3],[4,5,6,])
In [14]: for tmp in ret: 
    ...:     print(tmp) 
    ...:          
5
7
9
```
- filter
```python
filter(function or None, sequence) -> list, tuple, or string
```
```python
filter(lambda x: x%2, [1, 2, 3, 4])
[1, 3]
    # 0 是 False; 1 是 True

filter(None, "she")
'she'
    # 不過濾, 全取
```
- reduce
    - 應用：累積和、階層
```python
reduce(function, sequence[, initial]) -> value
```
```python
reduce(lambda x, y: x+y, [1,2,3,4])
10
    # x=1, y=2 : x+y=3
    # x=x+y, y=3 : x+y=6
    # x=x+y, y=4 : x+y=10

reduce(lambda x, y: x+y, [1,2,3,4], 5)
15
    # x=5, y=1 : x+y=6
    # x=x+y, y=2: x+y=8
    # ...

reduce(lambda x, y: x+y, ['aa', 'bb', 'cc'], 'dd')
'ddaabbcc'
```
- sorted
```python
sorted(iterable, cmp=None, key=None, reverse=False) --> new sorted list
```
```python
In [27]: sorted([1,2,3,6,3,5])    
Out[27]: [1, 2, 3, 3, 5, 6]
In [28]: sorted([1,2,3,6,3,5], reverse=1)
Out[28]: [6, 5, 3, 3, 2, 1]

In [29]: sorted(['dd','aa','cc','bb'])
Out[29]: ['aa', 'bb', 'cc', 'dd']
In [30]: sorted(['dd','aa','cc','bb'], reverse=1)    
Out[30]: ['dd', 'cc', 'bb', 'aa']
    # Why 字母可以排序？
    # 根據 ASCII碼
```
Set
---

```python
# 去重複
In [1]: a = [1,1,2,3,3,5,5,6,6,7]
In [2]: b = set(a)
In [3]: list(b)            
Out[3]: [1, 2, 3, 5, 6, 7]
In [4]: b
Out[4]: {1, 2, 3, 5, 6, 7}


# 交集
# 應用: 查看重複流量-> 真客戶
In [1]: a = "abcdefg"
In [2]: b = "bcd"
In [3]: A = set(a)
In [4]: A    
Out[4]: {'a', 'b', 'c', 'd', 'e', 'f', 'g'}
In [5]: B = set(b)
In [6]: B        
Out[6]: {'b', 'c', 'd'}
In [7]: A&B
Out[7]: {'b', 'c', 'd'}

# 併集
# 應用: 查看所有流量
In [8]: c = "dscnq"
In [9]: C = set(c)
In [10]: C         
Out[10]: {'c', 'd', 'n', 'q', 's'}
In [11]: B|C
Out[11]: {'b', 'c', 'd', 'n', 'q', 's'}

# 差集
# 應用: 查看流失流量
In [13]: A-C
Out[13]: {'a', 'b', 'e', 'f', 'g'}

# 對稱差集
# 應用: 查看不重複流量
In [14]: A^C 
Out[14]: {'a', 'b', 'e', 'f', 'g', 'n', 'q', 's'}
```
functools
---

```python
In [15]: import functools
In [16]: dir(functools)   
Out[16]: 
['RLock',
 'WRAPPER_ASSIGNMENTS',
 'WRAPPER_UPDATES',
 '_CacheInfo',
 ...]
```
- wraps
```python
def note(func):
    "note function"
    def wrapper():
        "wrapper function"
        print('note something')
        return func()
    return wrapper

@note
def test():
    "test function"
    print('I am test')
    
help(test)
```
```
test()
    wrapper function
```
```python
import functools
def note(func):
    "note function"
    @functools.wraps(func)
    def wrapper():
        "wrapper function"
        print('note something')
        return func()
    return wrapper

@note
def test():
    "test function"
    print('I am test')
    
help(test)
```
```
test()
    test function
```
Module 進階
---
- hashlib
    - 註冊帳密時 
        - 瀏覽器傳輸註冊帳密給服務器，
        - 服務器裡有個數據庫專門存取。
    - 登入帳號
        - 瀏覽器傳輸帳密給服務器，
        - 服務器項數據庫比對，
        - 服務器返回T/F
    - 危險: 外洩
        - [2011年中國網站用戶信息泄露事件](https://zh.wikipedia.org/wiki/2011%E5%B9%B4%E4%B8%AD%E5%9B%BD%E7%BD%91%E7%AB%99%E7%94%A8%E6%88%B7%E4%BF%A1%E6%81%AF%E6%B3%84%E9%9C%B2%E4%BA%8B%E4%BB%B6)
        - [拖庫](https://baike.baidu.com/item/%E6%8B%96%E5%BA%93)
    - [Hashing](https://zh.wikipedia.org/wiki/%E6%95%A3%E5%88%97)加密
        - 常見: md5、sha256
```python
In [1]: import hashlib
In [2]: t = hashlib.md5()

In [3]: t.update(b"haha")
In [4]: t.hexdigest()
Out[4]: '4e4d6c332b6fe62a63afe56171fd3725'

In [5]: t.update(b"hahafdjsakjqwejhfadkjfd")
In [6]: t.hexdigest()
Out[6]: '9bfb960fdf24ea0642a712310639929a'
    # 不論密碼多長, md5都是得到128密文
    # 128密文為128bit*8=16Byte
    # 常用4bit表示16進制,故1Byte可以表示兩個16進制
    # 亦即128密文=16Byte=32個16進制的字符

```
理論上如此複雜的加密很難解密, 但是現在已被運用[撞庫](https://baike.baidu.com/item/%E6%92%9E%E5%BA%93)破解，利用生成的數據庫比對，做成一個數據庫，此時此刻已經得已逆回來了(Google:『md5 破解』)

- 開啟簡單服務器
`python3 -m http.server 8888`
  - 瀏覽器: IP(localhost):8888 -> 即可進入
 
調試
---
調試C/C++時，可以用[IDE](https://zh.wikipedia.org/wiki/%E9%9B%86%E6%88%90%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83)或[GDB](https://zh.wikipedia.org/wiki/GNU%E4%BE%A6%E9%94%99%E5%99%A8), 而Python則常用pycharm或PDB

- 執行時調適(常用)
```python
$ python3 -m pdb test1.py 
    # -m -> module
-> def getSum(a,b):

# l -> list
(Pdb) l
  1  ->	def getSum(a,b):
  2  	    result = a+b
  3  	    print("result=%d"%result)
  4  	    return result
  5  	
  6  	a = 100
  7  	b = 200
  8  	ret = getSum(a,b)
  9  	print(ret)
[EOF]

# n -> next
(Pdb) n
-> a = 100
(Pdb) l
  6  -> a = 100
  7  	b = 200
(Pdb) n
-> b = 200
  6  	a = 100
  7  ->	b = 200

# c -> continue
result=300
300
The program finished and will be restarted
-> def getSum(a,b):

# b -> break 斷點
(Pdb) b 6
(Pdb) b 8
(Pdb) l
  1  ->	def getSum(a,b):
  2  	    result = a+b
  3  	    print("result=%d"%result)
  4  	    return result
  5  	
  6 B	a = 100
  7  	b = 200
  8 B 	ret = getSum(a,b)
  9  	print(ret)
  
(Pdb) c
-> a = 100
(Pdb) c
-> ret = getSum(a,b)
(Pdb) c
result=300
300
The program finished and will be restarted
-> def getSum(a,b):

(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   test1.py:6
2   breakpoint   keep yes   test1.py:8

# clear 清除斷點
(Pdb) clear 1
Deleted breakpoint 1 test1.py:6
(Pdb) l
  6  	a = 100
  7  	b = 200
  8 B	ret = getSum(a,b)
[EOF]

(Pdb) c
-> ret = getSum(a,b)

# p print
(Pdb) p a
100
(Pdb) p b
200

# s step 進入函數
(Pdb) s
--Call--
-> def getSum(a,b):
(Pdb) l
  1  ->	def getSum(a,b):
  2  	    result = a+b
  3  	    print("result=%d"%result)
  4  	    return result

(Pdb) n
-> result = a+b
(Pdb) l
  1  	def getSum(a,b):
  2  ->	    result = a+b
  3  	    print("result=%d"%result)
  4  	    return result
[EOF]

# a args 打印形參
(Pdb) a
a = 100
b = 200

# r return 快速執行到函數最後一行
(Pdb) r
result=300
--Return--
-> return result
(Pdb) l
  1  	def getSum(a,b):
  2  	    result = a+b
  3  	    print("result=%d"%result)
  4  ->	    return result
(Pdb) p result
300
(Pdb) n
-> print(ret)
(Pdb) n
300
--Return--
-> print(ret)
(Pdb) n
--Return--
> <string>(1)<module>()->None
(Pdb) n
-> def getSum(a,b):

# q quit
```
- 交互調試
```python
In [1]: def test(a,b): 
   ...:     c = a+b 
   ...:     return c 
   ...: 

# 第一種
In [2]: import pdb
In [3]: pdb.run("test(1,2)")   
> <string>(1)<module>()
(Pdb) l
[EOF]
(Pdb) s
--Call--
> <ipython-input-1-7e1e04efc5a9>(1)test()
-> def test(a,b):
(Pdb) l
  1  ->	def test(a,b):
  2  	    c = a+b
  3  	    return c
  4  	
[EOF]
(Pdb) a
a = 1
b = 2
(Pdb) r
--Return--
> <ipython-input-1-7e1e04efc5a9>(3)test()->3
-> return c
(Pdb) q
```
- 程序裡埋點
```python
# 第二種
import pdb
def getSum(a,b):
    result = a+b
    print("result=%d"%result)
    return result

a = 100
b = 200
pdb.set_trace()
ret = getSum(a,b)
print(ret)
```
```
$ python test3.py
-> ret = getSum(a,b)
(Pdb)
```
- 日誌調試

編碼風格
---
- 分號
```python
In [1]: a = 100 b = 200
SyntaxError: invalid syntax

In [2]: a = 100; b = 200
In [3]: a                       
Out[3]: 100
In [4]: b                     
Out[4]: 200
```