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 表示成功執行了,但卡在1!
"""
```
`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, 接著就可以在此基礎上做其他事情
```

```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的指向,
# 此時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
```