1. 程式人生 > >python3學習筆記:零碎知識點

python3學習筆記:零碎知識點

  • break語句可以在迴圈過程中直接退出迴圈,而continue語句可以提前結束本輪迴圈,並直接開始下一輪迴圈。這兩個語句通常都必須配合if語句使用。
  • list:Python內建的一種資料型別叫列表。list是一種有序的集合,可以隨時新增和刪除其中的元素。格式:[ ]
    • 要刪除指定位置的元素,用pop(i)方法,其中i是索引位置:
    • 可以獲取倒數第2個、倒數第3個L[-2]、L[-3]
  • tuple:另一種有序列表叫元組。tuple和list非常類似,但是tuple一旦初始化就不能修改。格式:( )
    • tuple所謂的“不變”是說,tuple的每個元素,指向永遠不變。即指向’a’,就不能改成指向’b’,指向一個list,就不能改成指向其他物件,但指向的這個list本身是可變的!
    • Python規定,只有1個元素的tuple定義時必須加一個逗號,,來消除歧義: t = (1,)
  • dict:字典,dict全稱dictionary,在其他語言中也稱為map,使用鍵-值(key-value)儲存,具有極快的查詢速度。格式:{ }
    • dict的儲存不是按照list的方式順序排列,迭代出的結果順序很可能不一樣
    • 和list比較,dict有以下幾個特點:
      • 查詢和插入的速度極快,不會隨著key的增加而變慢;
      • 需要佔用大量的記憶體,記憶體浪費多。
    • 而list相反:
      • 查詢和插入的時間隨著元素的增加而增加;
      • 佔用空間小,浪費記憶體很少。
    • 所以,dict是用空間來換取時間的一種方法。
  • set:set和dict類似,也是一組key的集合,但不儲存value。格式{ }

    • 要建立一個set,需要提供一個list作為輸入集合:s = set([1, 2, 3])
    • 易錯點:
      • a = ‘abc’
      • b = a.replace(‘a’, ‘A’)
      • 我們呼叫a.replace(‘a’,’A’)時,實際上呼叫方法replace是作用在字串物件’abc’上的,而這個方法雖然名字叫replace,但卻沒有改變字串’abc’的內容。相反,replace方法建立了一個新字串’Abc’並返回,如果我們用變數b指向該新字串,就容易理解了,變數a仍指向原有的字串’abc’,但變數b卻指向新字串’Abc’了
      • 所以,對於不變物件來說,呼叫物件自身的任意方法,也不會改變該物件自身的內容。相反,這些方法會建立新的物件並返回,這樣,就保證了不可變物件本身永遠是不可變的。
  • 函式多樣化的引數:
    • 預設引數一定要用不可變物件,如果是可變物件,程式執行時會有邏輯錯誤!
    • 要注意定義可變引數和關鍵字引數的語法:
      • *args是可變引數,args接收的是一個tuple;
      • **kw是關鍵字引數,kw接收的是一個dict。
    • 以及呼叫函式時如何傳入可變引數和關鍵字引數的語法:
      • 可變引數既可以直接傳入:func(1,2,3),又可以先組裝list或tuple,再通過* args傳入:func(*(1, 2, 3));
      • 關鍵字引數既可以直接傳入:func(a=1,b=2),又可以先組裝dict,再通過* kw傳入:func(*{‘a’: 1, ‘b’: 2})。
    • 使用* args和** kw是Python的習慣寫法,當然也可以用其他引數名,但最好使用習慣用法。
    • 命名的關鍵字引數是為了限制呼叫者可以傳入的引數名,同時可以提供預設值。
    • 定義命名的關鍵字引數在沒有可變引數的情況下不要忘了寫分隔符*,否則定義的將是位置引數。
  • 切片:
    • L[a:b:c]:在list L中[a,b)的元素中每隔c個元素取一個組成新的list並返回
      • a不寫預設是list首,b不寫預設是list尾,a、b均不寫,代表整個list
    • 倒數切片L[-a:-b:c]:在list L中[-a,-b]的元素中每隔c(步長)個元素取一個組成新的list並返回,注意:倒數第一個是-1
    • 步長c不能為0,但是可以為負數,即從右到左提取元素。使用一個負數作為步長時,必須讓開始點(開始索引)大於結束點。
    • eg:numbers = [1,2,3,4,5,6,7,8,9,10]
    • print(numbers[6:3:-1]) 輸出:[7, 6, 5]
    • tuple( )和string‘ ’也支援slice切片操作,切片後返回的是tuple( )和string‘ ’
  • 迭代iterable:
  • 如何判斷一個物件是可迭代物件呢?方法是通過collections模組的Iterable型別判斷
    • from collections import Iterable
    • isinstance(‘abc’,Iterable) #str是否可以迭代
  • 對list實現類似Java那樣的下標迴圈怎麼辦?
    • Python內建的enumerate函式可以把一個list變成索引-元素對,這樣就可以在for迴圈中同時迭代索引和元素本身:
    • for i,j in enumerate([1,2,3]):
    • print(i,j)
  • 生成器generator:
  • 在Python中,這種一邊迴圈一邊計算的機制,稱為生成器:generator
  • 只要把一個列表生成式的[]改成(),就建立了一個generator
  • 可以直接打印出list的每一個元素,但generator函式的“呼叫”實際返回一個generator物件,要打印出generator的每一個元素:
    • 通過next()函式獲得generator的下一個返回值
    • 使用for迴圈,generator可以迭代
  • 如果一個函式定義中包含yield關鍵字,那麼這個函式就不再是一個普通函式,而是一個generator
  • eg:列印楊輝三角
  • def triangles:
    • L = [1]
    • while True:
      • yield L
      • L.append(0)
      • L = [L[i-1] + L[i] for i in range(len(L))]
  • 代器Iterator:
  • Python的Iterator物件表示的是一個數據流,Iterator物件可以被next()函式呼叫並不斷返回下一個資料,直到沒有資料時丟擲StopIteration錯誤。
  • 可以把這個資料流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函式實現按需計算下一個資料,所以Iterator的計算是惰性的,只有在需要返回下一個資料時它才會計算。
  • Iterator甚至可以表示一個無限大的資料流,例如全體自然數。
  • 凡是可作用於for迴圈的物件都是Iterable型別;
  • 凡是可作用於next()函式的物件都是Iterator型別,它們表示一個惰性計算的序列
  • 集合資料型別如list、dict、str等是Iterable但不是Iterator,不過可以通過iter()函式獲得一個Iterator物件。
  • 迭代器和生成器的區別:
  • 迭代器有兩個基本的方法
    • next方法:返回迭代器的下一個元素
    • iter方法:返回迭代器物件本身
  • 生成器使用yield方法保持迭代器的高效
  • 函數語言程式設計
  • 函數語言程式設計的一個特點就是,允許把函式本身作為引數傳入另一個函式,還允許返回一個函式!
  • Python對函數語言程式設計提供部分支援。由於Python允許使用變數,因此,Python不是純函數語言程式設計語言。
  • 把函式作為引數傳入,這樣的函式稱為高階函式,函數語言程式設計就是指這種高度抽象的程式設計正規化。
  • map/reduce
  • map()函式接收兩個引數,一個是函式,一個是Iterable,map將傳入的函式依次作用到序列的每個元素,並把結果作為新的Iterator返回。
    • eg: list = [map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])]
  • reduce把一個函式作用在一個序列[x1, x2, x3, …]上,這個函式必須接收兩個引數,reduce把結果繼續和序列的下一個元素做累積計算:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
    • eg: reduce(add, [1, 3, 5, 7, 9]) 累加
  • filter()函式用於過濾序列
  • filter()也接收一個函式和一個序列,把傳入的函式依次作用於每個元素,然後根據返回值是True還是False決定保留還是丟棄該元素。
  • filter()函式返回的是一個Iterator,也就是一個惰性序列,所以要強迫filter()完成計算結果,需要用list()函式獲得所有結果並返回list。
  • eg: list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
  • sorted
  • sorted(List,key=,reverse=False)函式是一個高階函式,接收一個key函式來實現自定義的排序,reverse預設為False,即預設升序
  • key指定的函式將作用於list的每一個元素上,並根據key函式返回的結果進行排序。
  • eg:sorted([‘bob’, ‘about’, ‘Zoo’, ‘Credit’], key=str.lower, reverse=True)
  • 輸出:[‘Zoo’, ‘Credit’, ‘bob’, ‘about’] 反向排序
  • 閉包(詳見部落格)
  • 裝飾器(詳見部落格)
  • 偏函式
    • Python的functools模組提供了很多有用的功能,其中一個就是偏函式(Partial function)。
    • 當函式的引數個數太多,需要簡化時,使用functools.partial可以建立一個新的函式,這個新函式可以固定住原函式的部分引數,從而在呼叫時更簡單。
    • 建立偏函式時,實際上可以接收函式物件、*args和**kw這3個引數:
      • eg:int2 = functools.partial(int, base=2)
      • int2(‘10010’)
      • 相當於:
      • kw = { ‘base’: 2 }
      • int(‘10010’, **kw)
      • eg:max2 = functools.partial(max, 10)
      • max2(5, 6, 7)
      • 相當於:
      • args = (10, 5, 6, 7)
      • max(*args)
  • 面向物件
  • 如果要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線,在Python中,例項的變數名如果以開頭,就變成了一個私有變數(private),只有內部可以訪問,外部不能訪問
  • 在Python中,變數名類似xxx的,也就是以雙下劃線開頭,並且以雙下劃線結尾的,是特殊變數,特殊變數是可以直接訪問的,不是private變數
  • 有些時候,你會看到以一個下劃線開頭的例項變數名,比如_name,這樣的例項變數外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變數時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變數,不要隨意訪問”。
  • python中可以這樣寫:1<=score<=100,這在其他很多語言中都是不允許的,入java
  • 雙下劃線開頭並以雙下劃線結尾的變數是特殊變數,不是private的,可以直接訪問
  • 單個下劃線開頭的變數雖然可以直接訪問,但是它隱含了“不要隨便訪問我”的意思
  • __name雖然是private的變數,但是依然可以在外部以_Student_name的形式訪問到
  • bart.__name = ‘Chen’ 在外部也可以正常執行,但是和想象中的不是一回事,這時候其實是又定義了新的變數__name
  • python(動態語言)的“鴨子”型別特點:
  • C/C++這樣的靜態語言定義函式的時候是需要顯式地申明引數型別,既然限定了引數型別自然也限定了引數在函式內的行為,因為錯誤的引數型別在傳入的當下就已經被拒絕接受了。
  • 然而python這樣的動態語言函式的引數從來都沒有明確申明,因此你傳入任何型別,python函式都能接住,不過接住以後函式呼叫的時候發現該引數並沒有某行為的話才會丟擲異常,因此只要函式內部呼叫的行為只要傳入引數具備就行。
  • 簡而言之,靜態語言傳參錯誤,是不會進入函式呼叫階段;而動態語言是接收後使用引數的過程中發現錯誤丟擲異常。
  • 屬性與方法
  • 判斷基本型別的物件型別,使用type()函式
  • type()函式既可以返回一個物件的型別,又可以創建出新的型別
    • def fn(self, name=’world’): # 先定義函式
    • print(‘Hello, %s.’ % name)
    • Hello = type(‘Hello’, (object,), dict(hello=fn)) # 建立Hello class
    • type()函式依次傳入3個引數:
    • class的名稱;
    • 繼承的父類集合,注意Python支援多重繼承,如果只有一個父類,別忘了tuple的單元素寫法;
    • class的方法名稱與函式繫結,這裡把函式fn繫結到方法名hello上。
  • 判斷class的型別,可以使用isinstance()函式,isinstance()判斷的是一個物件是否是該型別本身,或者位於該型別的父繼承鏈上。
  • 如果要獲得一個物件的所有屬性和方法,可以使用dir()函式,它返回一個包含字串的list
  • 通過callable()函式,可以判斷一個物件是否是“可呼叫”物件
  • _slots_
  • slot是限制的是例項物件的屬性的新增,而不是類的屬性
  • 類屬性是公共屬性,所有例項都可以引用的,前提是例項自身沒有同名的屬性,因此類屬性不能隨意更改(別的例項可能在引用類屬性的值),就是說不能隨便用a.set_age()更改age的值(因為呼叫此方法更改的是類屬性age的值,不是例項a自身的age屬性值)
  • 使用slots要注意,slots定義的屬性僅對當前類例項起作用,對繼承的子類是不起作用的
  • 給例項繫結一個方法
    • from types import MethodType
    • s.set_age = MethodType(set_age, s) # 給例項繫結一個方法
  • 給所有例項都繫結方法,即給class繫結方法:
    • def set_score(self, score):
      • self.score = score
    • Student.set_score = set_score # 給class繫結方法
  • @property 詳見部落格
  • Python允許使用多重繼承,採用MIxIn設計模式(類名後加上MixIn標記)
    • eg:ForkingMixIn(多程序模型)、ThreadingMixIn(多執行緒模型)
  • metaclass(元類)改變類建立時的行為,一般用於建立API,詳見部落格
  • 除錯與錯誤
  • raise語句如果不帶引數,就會把當前錯誤原樣丟擲(向上拋)
  • pdb除錯:python -m pdb err.py
    • l(l:list) 顯示整個.py檔案
    • n 單步執行
    • p + 變數名 檢視變數名
    • c 繼續執行程式
    • q 退出pdb
  • python -m unittest mydict_test 在命令列通過引數-m unittest直接執行單元測試
  • Python內建的“文件測試”(doctest)模組可以直接提取註釋中的程式碼並執行測試。
    • doctest嚴格按照Python互動式命令列的輸入和輸出來判斷測試結果是否正確。
    • 只有測試異常的時候,可以用…表示中間一大段煩人的輸出。
    • 當模組正常匯入時,doctest不會被執行。
    • 只有在命令列直接執行且自定義的doctest部分有問題,才執行doctest。
    • 所以,不必擔心doctest會在非測試環境下執行。
  • 檔案讀寫
  • r是原始字串操作符,寫在字串的前邊,表示字串內的所有字元都按原始意思解釋。
    • 如果這裡不加r,地址當中的這個符號“\”必須寫成轉義字元的形式,也就是’\’
    • 總結下就是:’C:\’ 和 r’C:\’ 輸出是一樣的
  • 在Linux和OS X中,檔案路徑地址是“/”;在Windows系統中,檔案路徑要使用反斜槓“\”
  • 操作檔案和目錄
  • os.name 作業系統型別:windows下是‘nt’;Linux、unix等是‘posix’
  • os.environ 所有環境變數
    • 獲取某個具體的環境變數:os.environ.get(‘path’)
  • os.path.abspath(‘.’) 當前目錄的絕對路徑
  • 把兩個路徑合成一個,通過os.path.join()函式,這樣可以正確處理不同作業系統的路徑分隔符
  • 拆分路徑時,要通過os.path.split()函式,把一個路徑拆分為兩部分,後一部分總是最後級別的目錄或檔名
  • os.path.splitext()可以直接讓你得到副檔名(第二部分是副檔名)
  • 序列化與反序列化
  • 將python物件序列化為json物件
    • json.dumps()方法返回一個str,內容就是標準的JSON。
      • 例如:
      • import json
      • d = dict(name=’Bob’, age=20, score=88)
      • json.dumps(d)
      • 輸出:’{“age”: 20, “score”: 88, “name”: “Bob”}’
      • JSON語法規定key(age、score、name)必須要用雙引號
    • json.dump()方法可以直接把JSON寫入一個file-like Object。
  • 將json物件反序列化為python物件
    • 最常用:json.loads()把JSON的字串反序列化為python物件
      • 例如:
      • json_str = ‘{“age”: 20, “score”: 88, “name”: “Bob”}’
      • json.loads(json_str)
      • 輸出:{‘age’: 20, ‘score’: 88, ‘name’: ‘Bob’}
    • json.load(),從file-like Object中讀取字串並反序列化為python物件
  • json.dumps(s, default=lambda obj: obj.dict)將任意物件序列化為json物件
  • 程序和執行緒
  • 多程序multiprocessing
    • 多程序通訊 Pipe、Queue
    • process.start()、process.join()、process.terminate()
    • 獲取cpu核數:queue.put()、queue.get()
    • multiprocessing.cpu_count()
  • 多執行緒threading
    • Python的threading模組有個current_thread()函式,它返回當前執行緒的例項
    • 多執行緒不加鎖同時修改一個全域性變數,會由於執行緒中斷出現錯誤
    • 解決方案:但加鎖後,多執行緒相當於轉變成單執行緒,而且由於加鎖操作使效率上:多執行緒<單執行緒
    • 通過threading.Lock()建立一個鎖;
    • 執行前獲取鎖:lock.acquire();
    • 執行完,釋放鎖lock.release()
    • Python的執行緒雖然是真正的執行緒,但直譯器執行程式碼時,有一個GIL鎖:Global Interpreter Lock,任何Python執行緒執行前,必須先獲得GIL鎖,然後,每執行100條位元組碼,直譯器就自動釋放GIL鎖,讓別的執行緒有機會執行。這個GIL全域性鎖實際上把所有執行緒的執行程式碼都給上了鎖,所以,多執行緒在Python中只能交替執行,即使100個執行緒跑在100核CPU上,也只能用到1個核。
    • Python直譯器由於設計時有GIL全域性鎖,導致了多執行緒無法利用多核。多執行緒的併發在Python中就是一個美麗的夢。
    • IO操作不鎖定GIL,這時其他執行緒可以正常執行
    • Python雖然不能利用多執行緒實現多核任務,但可以通過多程序實現多核任務。多個Python程序有各自獨立的GIL鎖,互不影響。
  • ThreadLocal
    • 一個ThreadLocal變數雖然是全域性變數,但每個執行緒都只能讀寫自己執行緒的獨立副本,互不干擾。
    • 把threadlocal看成全域性dict,每個執行緒用自己作為key訪問value,所以互不干擾
  • 程序VS執行緒
    • 多程序下,windows下建立程序代價大(linux、unix下有fork,windows沒有)
    • 多執行緒下,任意一個執行緒掛掉,會導致整個程序崩潰(所有執行緒共享程序的記憶體),即穩定性不夠
    • 計算密集型任務VSIO密集型任務
    • 計算密集型任務的特點是要進行大量的計算,消耗CPU資源。這種計算密集型任務雖然也可以用多工完成,但是任務越多,花在任務切換的時間就越多,CPU執行任務的效率就越低,所以,要最高效地利用CPU,計算密集型任務同時進行的數量應當等於CPU的核心數。
    • IO密集型,涉及到網路、磁碟IO的任務都是IO密集型任務,這類任務的特點是CPU消耗很少,任務的大部分時間都在等待IO操作完成(因為IO的速度遠遠低於CPU和記憶體的速度)。對於IO密集型任務,任務越多,CPU效率越高。
    • 非同步IO
  • 分散式程序(詳見部落格)

    • 用multiprocessing.managers裡的BaseManager來通過網路進行分散式通訊
  • 正則表示式:re模組

  • \d 匹配一個數字
    • eg:’00\d’可以匹配’007’
  • \w 匹配一個數字或字母
    • eg:’\w\w\d’可以匹配’py3’
  • . 匹配任意單個字元
    • eg:’py.’可以匹配’pyc’、’pyo’、’py!’等等
  • *匹配任意個字元 (包括0個)
  • +匹配至少一個字元
  • ?匹配0個或1個字元
  • {n} 匹配n個字元;{n,m} 匹配n-m個字元
  • \s 匹配一個空格(也包括Tab等空白符)
    • eg:\s+ 匹配至少一個空格
  • []表示範圍
    • eg:[0-9a-zA-Z_]可以匹配一個數字、字母或者下劃線
  • ^表示行的開頭,^\d表示必須以數字開頭
  • \d表示必須以數字結束
  • re模組
  • 使用Python的r字首,就不用考慮轉義(即字串中有特殊意義的字元:\n、\t等)
  • re.match()方法判斷是否匹配,匹配成功,返回一個Match物件,否則返回None
  • 可以使用正則表示式切分字串
    • eg:re.split(r’\s+’, ‘a b c’)
    • [‘a’, ‘b’, ‘c’]
  • 正則表示式還有提取子串的強大功能:用()表示的就是要提取的分組(Group)
  • 注意:group(0)永遠是原始字串,group(1)、group(2)……表示第1、2、……個子串
    • eg:匹配區號和號碼 m = re.match(r’^(\d{3})-(\d{3,8})$’, ‘010-12345’)
  • 正則匹配預設是貪婪匹配,也就是匹配儘可能多的字元(加上?改為非貪婪匹配)
    • eg: re.match(r’^(\d+)(0*)$’, ‘102300’).groups()
    • (‘102300’, ”)
    • \d+採用貪婪匹配,直接把後面的0全部匹配了,結果0*只能匹配空字串了
    • 讓\d+採用非貪婪匹配(也就是儘可能少匹配),才能把後面的0匹配出來,加個?即可
    • re.match(r’^(\d+?)(0*)$’, ‘102300’).groups()
    • (‘1023’, ‘00’)
  • 預編譯正則表示式
  • re.compile(reg_expression) 呼叫對應的方法時不用給出正則字串
  • 網路程式設計
  • TCP(可靠連線)
  • socket.SOCK_STREAM面向TCP
  • 伺服器端(多執行緒處理多個客戶端連線)+socket+客戶端
  • 伺服器端:主要用到 socket.bind、socket.listen、socket.accept、socket.send、socket.recv、socket.close
  • 客戶端:主要用到socket.connect、socket.send、socket.recv
  • UDP(無連線)
  • socket.SOCK_DGRAM面向UDP
  • 伺服器端+socket+客戶端+無連線,類似於TCP
  • 訪問資料庫
  • SQLite
  • sqlite驅動:sqlite3,Python已經封裝好了,直接引用即可
  • 主要用到連線sqlite3.connect和遊標conn.cursor
  • 通過遊標cursor執行sql語句:cursor.execute()
  • 執行insert等操作後要呼叫commit()提交事務
  • 佔位符是?
  • 用完記得關閉遊標cursor和連線conn
  • MySQL
  • mysql驅動:pymysql
  • eg:pymysql.connect(host=’127.0.0.1’, port=3306, user=’root’, passwd=”, db=’tkq1’, charset=’utf8’)
  • 佔位符是%s
  • mysql連線connect時需要使用者名稱、密碼連線,其他操作同上
  • SQLAlchemy(暫不深究)
  • ORM技術:Object-Relational Mapping,把關係資料庫的表結構對映到物件上
  • 在Python中,最有名的ORM框架是SQLAlchemy
  • HTTP
  • 當遇到連續兩個\r\n時,Header部分結束,後面的資料全部是Body
  • WSGI:Web Server Gateway Interface
  • 寫一個WSGI的處理函式,針對每個HTTP請求進行響應
  • eg: def application(environ,start_response):
    • start_response(‘200 OK’,[(‘Content-Type’,’text/html’)])
    • body = ‘< h1>hello,%s!< /h1>’ % (environ[‘PATH_INFO’][1:] or ‘web’)
    • return [body.encode(‘utf-8’)]
  • 匯入wsgiref模組裡的make_server,並監聽HTTP請求:serve_forever()
  • web框架Flask
  • 前端模板jinja2
  • 在Jinja2模板中,我們用{{ name }}表示一個需要替換的變數。
  • 迴圈、條件判斷等指令語句,在Jinja2中,用{% … %}表示指令。
  • 使用flask中的render_template呼叫模板,實現MVC模式
  • 非同步IO
  • 協程:非同步併發,不同於多執行緒,在一個執行緒裡,不需要切換執行緒;不同於多程序,不需要鎖;一般與多程序配合實現多cpu+高效率
  • 包檔案:asyncio
  • asyncio提供的@asyncio.coroutine可以把一個generator標記為coroutine型別,然後在coroutine內部用yield from呼叫另一個coroutine實現非同步操作
  • yield from VS yield
    • yield from + 協程任務
    • 將協程任務的值包含到當前協程任務的返回值裡,即可以連線兩個協程任務的值
  • 核心方法:asyncio.get_event_loop()、asyncio.run_until_complete()、asyncio.close()、
  • asyncio.wait()(將多個任務程序封裝成一個新的協程任務並返回)
  • async/await
  • python3.5 加入了async、await來幫助asyncio簡化非同步過程
  • 即async替代@asyncio.coroutine;await替代yield from即可
  • aiohttp
  • asyncio實現了TCP、UDP、SSL等協議,aiohttp則是基於asyncio實現的HTTP框架
  • 15天專案實戰
  • day1:搭建開發環境&&同步到github倉庫
  • day2:利用aiohttp搭建web骨架