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, 接著就可以在此基礎上做其他事情 ``` ![](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的指向, # 此時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 ```