變量
變量定義
# 什麼是變量
用標識符命名的存儲單元的地址稱爲變量,變量是用來存儲數據的,經過標識符能夠獲取變量的值,也能夠對變量進行賦值
做用域
建立、改變、查找變量名時,都是在一個保存變量名的空間中進行,該空間稱做爲做用域
Python中,一個變量的做用域在聲明時就決定了,與在何處調用無關
函數做用域的LEGB順序
L:local 函數內部做用域
E:enclosing 函數內部與內嵌函數之間
G:global 全局做用域
B:build-in 內置做用域
python在函數裏面的查找分爲4種,稱之爲LEGB,也正是按照這是順序來查找的
可變類型和不可變類型
# 可變類型(mutable):列表、字典
當該數據類型的對應變量的值發生了改變,那麼它對應的內存地址不發生改變,對於這種數據類,就稱可變數據類型
# 不可變類型(immutable):數字、字符串、元組
對於不可變類型來講,變量保存的實際上都是對象的引用,對其進行修改時,會開闢一塊內存空間建立了一個新的對象並指向它,而後將原對象的引用計數-1
# 例1
a = 1
print(a)
a = 2
print(a)
# 例2
b = [1, 2, 3]
print(id(b))
b[1] = 1
print(id(b))
is和==區別
is:比較的是兩個對象的id值是否相等,也就是比較倆對象是否爲同一個實例對象,是否指向同一個內存地址
==:比較的兩個對象的內容/值是否相等,默認會調用對象的eq()方法
引用、淺拷貝、深拷貝
import copy
a = 'Allen'
a = b
c = copy.copy(a)
d = copy.deepcopy(a)
# 賦值:傳遞的a所指對象的引用,若是是可變類型修改後,影響原對象,不可變類型則建立一個新的對象,並指向新的對象,將原對象的應用計數-1
# 淺拷貝:將一個對象的引用拷貝到別一個對象,對拷貝的對象做出改動,會影響到原對象
# 深拷貝:將一個對象拷貝到別一個對象,對拷貝的對象做出改動時,不會影響到原對象
推導式
# 字典推導式
d = {'allen': 'name'}
print({v: k for v, k in d.items()})
# 列表推導式
print([i.upper() for i in 'abcdef'])
# 集合推導式
print({i for i in 'abcdef'})
# 生成器推導式
print((i for i in range(10)))
%:沒法同時傳遞一個變量和元組
c = (250, 250)
s1 = "pos:%s" % c # 報一個異常:TypeError: not all arguments converted during string formatting
c = (250, 250)
s1 = 'pos:%s'(c,)
format:就不會存在上面的問題
c = (250, 250)
s1 = 'pos:{}'.format(c)
# python3.6支持 f-strings
c = (250, 250)
s1 = f'pos:{c}'
鏈接字符串用join仍是+
# +
當用操做符+鏈接字符串的時候,每執行一次+都會申請一塊新的內存,而後複製上一個+操做的結果和本次操做的右操做符到這塊內存空間,所以用+鏈接字符串的時候會涉及好幾回內存申請和複製
# join
join在鏈接字符串的時候,會先計算須要多大的內存存放結果,而後一次性申請所需內存並將字符串複製過去
函數
函數參數傳遞
# 例1
a = 1
def fun(a):
a = 2
# 因爲是不可變類型,在向不可變類型變量賦值時,就是建立新的對象並引用,將原有的對象引用計數-1
fun(a)
print(a) # 結果爲1
# 例2
a = []
def fun(a):
a.append(1)
# 由於list是可變類型,能夠在原處修改
fun(a)
print a # [1] #
lambda
# lambda
語法:lambda argument_list:expression
lambda是python預留關鍵字,argument_list和expression有用戶自定義;
argument_list:參數列表 # *arg, **kwargs, None
expression:表達式,表達式中出現的參數必須在argument_list中定義,而且表達式只能是單行的
lambda argument_list:expression表示的是一個函數,這個函數叫作lambda函數
# 三個特性
1.lambda函數式匿名的:所謂匿名函數,lambda函數沒有名字,引用計數爲0,使用一次就釋放
2.lambda函數有輸入和輸出:輸入是argument_list中傳遞的參數,輸出是:根據expression計算出來的值
3.lambda函數通常功能簡單:單行expression決定了lambda函數不可能完成複雜的邏輯,只能完成很是簡單的功能
# 四種用法
1.將lambda函數賦值給一個變量,經過這個變量間接調用該lambda函數
2.將lambda函數賦值給其餘函數,從而將其餘函數用該lambda函數替換
3.將lambda函數做爲其餘函數的返回值,返回給調用者
4.將lambda函數做爲參數傳遞給其餘函數
函數式編程
純粹的函數式編程語言編寫的函數沒有變量,所以,任意一個函數,只要輸入是肯定的,輸出就是肯定的,這種純函數咱們稱之爲沒有反作用。而容許使用變量的程序設計語言,因爲函數內部的變量狀態不肯定,一樣的輸入,可能獲得不一樣的輸出,所以,這種函數是有反作用的
函數式編程的一個特色就是,容許把函數自己做爲參數傳入另外一個函數,還容許返回一個函數
Python對函數式編程提供部分支持。因爲Python容許使用變量,所以,Python不是純函數式編程語言
# 函數式編程的三大特性
函數式編程的一個特色就是,容許把函數自己做爲參數傳入另外一個函數,還容許返回一個函數
1.immutable data 不可變數據
2.first class function
3.尾遞歸優化 # 尾遞歸優化技術——每次遞歸時都會重用stack!!!python不支持
函數式編程的幾個技術
# filter函數
此時lambda函數用於指定過濾列表元素的條件。例如filter(lambda x: x % 3 == 0, [1, 2, 3])指定將列表[1,2,3]中可以被3整除的元素過濾出來,其結果是[3]
# sorted函數
此時lambda函數用於指定對列表中全部元素進行排序的準則。例如sorted([1, 2, 3, 4, 5, 6, 7, 8, 9], key=lambda x: abs(5-x))將列表[1, 2, 3, 4, 5, 6, 7, 8, 9]按照元素與5距離從小到大進行排序,其結果是[5, 4, 6, 3, 7, 2, 8, 1, 9]
# map函數
此時lambda函數用於指定對列表中每個元素的共同操做。例如map(lambda x: x+1, [1, 2,3])將列表[1, 2, 3]中的元素分別加1,其結果[2, 3, 4]
# reduce函數
python3中從"functools"中導入
此時lambda函數用於指定列表中兩兩相鄰元素的結合條件。例如reduce(lambda a, b: '{}, {}'.format(a, b), [1, 2, 3, 4, 5, 6, 7, 8, 9])將列表 [1, 2, 3, 4, 5, 6, 7, 8, 9]中的元素從左往右兩兩以逗號分隔的字符的形式依次結合起來,其結果是'1, 2, 3, 4, 5, 6, 7, 8, 9'
*args和**kwargs
當你不肯定你的函數裏將要傳遞多少參數時你能夠用*args,它能夠接受任意數量的參數:
def f1(*args):
for couter,item in enumerate(args):
print(f'{couter}-{item}')
f1(1, 2, 3, 4)# 咱們能夠傳遞任意個數的參數
**kwargs容許你使用沒有事先定義的參數名:
def f2(**kwargs):
for key,value in kwagrs.items():
print(key,value)
*args和**kwargs能夠同時存在,可是*args必須在**kwargs前面,
# 咱們能夠在調用函數傳遞參數是使用*和**
def f3(*args, **kwargs):
print(args,kwargs)
t = (1,2,3)
d = {'name':'allen','age':18}
f3(*t, **d) # *將t拆成一個一個位置參數:1, 2, 3,**將d拆成一個個的關鍵字參數:name='allen','age'=18
# 缺省參數便是調用該函數時:
# foo(x, y=1)
缺省參數的值若未被傳入,則傳入默認預設的值
# 多值參數
函數參數列表中,參數前增長一個*能夠接收元組,增長兩個*能夠接收字典。
*args(arguments) 存放元組參數
**kwargs 存放字典參數
函數的工做原理
def foo():
bar()
def bar():
pass
首先要知道當咱們執行一個python程序其實是由python解釋器運行的(CPython是運行在C語言之上)
python解釋器會用一個叫作PyEval_EvalFrameEx()的C函數去執行foo函數,在C函數執行foo函數時,首先會建立一個棧幀對象(stack frame),棧幀是一個上下文,保存命名空間的全局/局部字典,當前正在執行字節碼的信息,索引指針,而後將代碼轉成字節碼對象,字節碼是運行在棧幀上下文中的,當foo調用子函數bar時,又會建立一個棧幀對象,在新建立的棧幀對象上下文中執行字節碼
# 全部的棧幀時分配在堆內存中的,堆內存的特性就是若是咱們分配的堆內存不去釋放,那麼會一直保存在堆內存中
函數工做的流程圖
# 1
PyEval_EvalFrameEx(PyFrameObject *f)
PyFrameObject
f_back # 指向調用的棧幀對象,此時foo沒有調用的棧幀對象
f_code # 指向foo的字節碼對象
PyCodeObject
foo's bytecode #
# 2
PyEval_EvalFrameEx(PyFrameObject *f)
PyFrameObject
f_back # 指向foo的棧幀對象
f_code # 指向bar的字節碼對象
PyCodeObject
bar's bytecode #
迭代器和生成器
迭代器
# 可迭代對象 iterable
對象必須擁有__iter__方法,該方法返回一個迭代器對象
# 迭代器對象
迭代器是訪問集合內元素的一種方式,通常用來遍歷數據
迭代器和如下標的訪問方式不同,迭代器是不能返回的,迭代器提供了一種惰性訪問數據的方式
對象必須擁有__iter__和__next__方法,執行是返回迭代器中的下一項,或者引發StopIteration異常,終止迭代
# 迭代過程
使用iter(iterable object)內置函數,該函數利用可迭代對象的__iter__方法生成一個迭代器對象;每一步調用next(iterator)內置函數調用迭代器對象,利用迭代器的__next__方法生成一個值,直到拋出StopIteration異常,結束迭代
# 自定義可迭代對象
from collections.abc import Iterator
class MyIterator(Iterator):
def __init__(self, iterable):
self.iterable = iterable
self.index = 0
def __next__(self):
try:
result = self.iterable[self.index]
except IndexError:
raise StopIteration
self.index += 1
return result
class MyIterable(object):
def __init__(self, employee):
self.employee = employee
def __iter__(self):
return MyIterator(self.employee)
emp = ['allen', 'kevin', 'collins']
my = MyIterable(emp)
for i in my:
print(i)
----------------------------------------------------------------
# 迭代器是訪問集合內元素的一種方式,通常用來遍歷數據
# 迭代器和下標獲取的方式不一樣,迭代器不能返回,迭代器提供了一種惰性的訪問機制
# 迭代協議
# python是基於協議的編程的
# 一個可迭代對象內部實現一個__iter__魔法函數,調用iter是返回一個迭代器對象,迭代器實現__iter__和__next__的魔法函數
class Iterable(object):
def __init__(self, seq):
self.seq = seq
def __iter__(self):
return Iterator(self.seq)
class Iterator(object):
def __init__(self, seq)
self.seq = seq
self.index = 0
def __next__(object):
try:
ele = self.seq[self.index]
except InderError:
raise StopIteration
index+=1
return ele
生成器
# 生成器
只要在函數中出現yield關鍵字,他就是一個生成器
生成器能夠掛起執行而且保持當前執行的狀態
生成器對象,在python編譯字節碼的時候就產生了
生成器對象,實現了迭代協議,因此咱們能夠使用for循環來遍歷
生成器與函數運行過程有所不一樣,PyGenObject(生成器對象)封裝了一個棧幀對象和字節碼對象,而棧幀對象中ilast保存生成器最後執行的位置(剛開始爲-1,意味着生成器還沒有開始),當咱們調用send時(預激),生成器執行到第一個yield暫停,並返回值,基於這一特性生成器能夠在任什麼時候候被任何函數恢復執行
函數中聲明瞭 yield 關鍵字,python解釋器在編譯字節碼的時候,將該函數標記成生成器
# 基於生成器的協程
python3以前沒有原聲協程,只有基於生成器的協程
pep342贈強了生成器功能
生成器能夠經過yield暫停執行和產出數據
同時支持send()向生成器發送數據和throw()向生成器拋出異常
# 基於生成器的協程注意點
協程序須要使用send(None)或者next(coroutine)來"預激"(prime)才能啓動
在yield處協程會暫停執行
單獨的yield value會產出值給調用方
能夠經過coroutine.send(value)來給協程發送值,發送的值會賦值給yield表達式左邊的變量,value=yield
協程執行完成後(沒有遇到下一個yield語句)會拋出StopIteration異常
# 協程裝飾器
from functools import wraps
# 避免每次都要用send預激它
def coroutine(func):
@wraps(func)
def primer(*args,**kwargs): # 這樣就不用每次都用send(None)啓動了
"""裝飾器:向前執行到第一個yield表達式,預激func"""
gen = func(*args, **kwargs)
next(gen)
return gen
return primer
面向切面編程AOP
AOP
# AOP
簡言之、這種在運行時,編譯時,類和方法加載時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程
咱們管切入到指定類指定方法的代碼片斷稱爲切面,而切入到哪些類、哪些方法則叫切入點。有了AOP,咱們就能夠把幾個類共有的代碼,抽取到一個切片中,等到須要時再切入對象中去,從而改變其原有的行爲。
優勢是:這樣的作法,對原有代碼毫無入侵性
閉包
# 內部函數包含對外部做用域而非全局做用域的引用
# 閉包的意義:返回的函數對象,不只僅是一個函數對象:在該函數外還包裹了一層做用域,這使得,該函數不管在何處調用,優先使用本身外層包裹的做用域
# 應用領域:延遲計算(原來咱們是傳參,如今咱們是包起來)
# 裝飾器就是閉包函數的一種應用場景
當一個內嵌函數引用其外部做用域的變量,咱們就會獲得一個閉包. 總結一下,建立一個閉包必須知足如下幾點:
1.必須有一個內嵌函數
2.內嵌函數必須引用外部函數中的變量
3.外部函數的返回值必須是內嵌函數
def index(path):
def get():
fd = open(path)
fd.read()
fd.close()
return get
裝飾器
# 裝飾器
常常被用於有切面需求的場景,有了裝飾器,咱們就能夠抽離出大量函數中與函數功能自己無關的雷同代碼並繼續重用
裝飾器的原則:
1.不修改被裝飾對象的源代碼
2.不修改被裝飾對象的調用方式
裝飾器的目標:爲被裝飾對象添加上新功能
# 裝飾模版
# 裝飾器就是把其餘函數做爲參數的函數
def decorator(func):
# 在函數裏面,裝飾器在運行中定義函數: 包裝.
# 這個函數將被包裝在原始函數的外面,因此能夠在原始函數以前和以後執行其餘代碼..
def wrapper(*args, **kwargs):
# 把要在原始函數被調用前的代碼放在這裏
print "Before the function runs"
# 調用原始函數(用括號)
result = func(*args, **kwargs)
# 把要在原始函數調用後的代碼放在這裏
print "After the function runs"
# 在這裏"a_function_to_decorate" 函數永遠不會被執行
# 在這裏返回剛纔包裝過的函數
# 在包裝函數裏包含要在原始函數先後執行的代碼.
return wrapper
# 假如你建了個函數,不想修改了
def foo():
print "I am a stand alone function, don't you dare modify me"
foo()
#輸出: I am a stand alone function, don't you dare modify me
# 如今,你能夠裝飾它來增長它的功能
# 把它傳遞給裝飾器,它就會返回一個被包裝過的函數.
new_foo = decorator(foo)
# 執行
nwe_foo()
#輸出s:
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
# 裝飾器語法糖
def decorator(func):
def wrapper(*args, **kwargs):
print('Before func runs')
result =func(*args, **kwargs)
print('After func runs')
return wrapper
@decorator # 其實就是decorator(foo)簡寫
def foo(*args, **kwargs):
print('Hello')
# 無參裝飾器
def decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result
return wrapper
# 有參裝飾器
def decorator(*arg, **kwargs):
def wrapper(func):
def inner(*args, **kwargs):
result = func(*args, **kwargs)
return result
return inner
return wrapper
# 疊加多個裝飾器
1. 加載順序(outter函數的調用順序):自下而上
2. 執行順序(wrapper函數的執行順序):自上而下
# 案例
def wrapper1(func):
print('loading wrapper1')
def inner1(*args, **kwargs):
print('execute inner1')
result = func(*args, **kwargs)
return result
return inner1
def wrapper2(func):
print('loading wrapper2')
def inner2(*args, **kwargs):
print('execute inner2')
result = func(*args, **kwargs)
return result
return inner2
def wrapper3(func):
print('loading wrapper3')
def inner3(*args, **kwargs):
print('execute inner3')
result = func(*args, **kwargs)
return result
return inner3
@wrapper1
@wrapper2
@wrapper3
def my_func():
print('From my_func')
# 裝飾器知識
裝飾器使函數調用變慢了.必定要記住.
裝飾器不能被取消(有些人把裝飾器作成能夠移除的可是沒有人會用)因此一旦一個函數被裝飾了.全部的代碼都會被裝飾.
Python自身提供了幾個裝飾器,像property, staticmethod,classmetho
Django用裝飾器管理緩存和視圖的權限.
Twisted用來修改異步函數的調用.
面向對象編程OOP
OOP
面向對象是至關於面向過程而言的,面向過程語言是一種基於功能分析的,以算法爲中心的程序設計方法,而面向對象是一種基於結構分析的,以數據爲中心的程序設計思想。在面嚮對象語言中有一個很重要的東西,叫作類。面向對象有三大特性:封裝、繼承、多態。
面向對象的基本特徵
1.封裝
簡單來說: 將現實世界的事物抽象成計算機領域中的對象,對象同時具備屬性和行爲,這種抽象就是封裝.
封裝的一個重要特性: 數據隱藏. 對象只對外提供與其它對象交互的必要接口,而將自身的某些屬性和實現細節對外隱藏,
經過這種方式,對象對內部數據提供了不一樣級別的保護,以防止程序中無關的部分意外的改變或錯誤的使用了對象的私有部分。
這樣就在確保正常交互的前提下,保證了安全性.
2.繼承
面向對象的一個重要特性是複用性.繼承是實現複用性的一個重要手段.
能夠在不重複編寫以實現的功能的前提下,對功能進行復用和拓展.
繼承概念的實現方式有二類:實現繼承與接口繼承。
*實現繼承是指直接使用基類的屬性和方法而無需額外編碼的能力
*接口繼承是指僅使用屬性和方法的名稱、可是子類必須提供實現的能力
被繼承的類叫作父類
繼承的類叫作派生類、子類
3.多態
多態就是不一樣的對象能夠調用相同的方法而後獲得不一樣的結果
多態的幾個前提
* a:要有繼承關係。
* b:要有方法重寫。
* c:要有父類引用指向子類對象。
多態的好處
* a:提升了代碼的維護性(繼承保證)
* b:提升了代碼的擴展性(由多態保證)
多態的限制
* 不能使用子類的特有屬性和行爲。
鴨子類型
# 當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就能夠被稱爲鴨子
# python中的鴨子類型容許咱們使用任何提供所需方法的對象,而不須要迫使它成爲一個子類。
# 在鴨子類型中,關注的不是對象的類型自己,而是它是如何使用的
python鴨子類型的體現
file StringIO socket 對象都有read方法,因此都叫 like file object
好比定義__iter__魔法方法的對象能夠使用for循環迭代它
類變量和實例變量
# 類變量(類建立後在全局是惟一的)
類變量定義在類中且在函數體以外,類變量一般不做爲實例變量使用,類變量在整個實例化的對象中是公用的,即類實例均可以訪問
# 實例變量
實例化以後,每一個實例單獨擁有的變量
class Test(object):
country = 'China'
def say(self):
print('Hello%s' % Test.country)
print('Hello%s' % self.country)
t = Test()
print(t.country)
t.country = 'ZH'
print(t.country)
# 實例的做用域裏把類變量的引用改變了,就變成了一個實例變量,self.name再也不引用Person的類變量name了.
實例方法、類方法、靜態方法
class User(object):
def __inin__(self, name):
self.name = name
def intro(self):
print(self.name)
@staticmethod
def welcome():
print('Welcome')
@classmethod
def from_cls(cls, name):
return cls(name)
# 實例方法
綁定到對象的方法:沒有被任何裝飾器裝飾的方法
經過對象來調用該方法,自動將對象看成第一個參數傳入
# 類方法
綁定到類的方法:用classmethod裝飾器裝飾的方法
經過類來調用該方法,調用時自動將類看成第一個參數傳入
# 靜態方法
不與類或對象綁定,類和對象均可以調用,不會自動傳值
單下劃線和雙下劃線
# __foo__
__foo__:雙下劃線開頭雙下劃線結尾的是一些 Python 的"魔術"對象,如類成員的 __init__、__del__、__add__、__getitem__ 等,以及全局的 __file__、__name__ 等,Python 官方推薦永遠不要將這樣的命名方式應用於本身的變量或函數,而是按照文檔說明來使用
# _foo
_foo:一種約定,用來指定變量私有,程序員用來指定私有變量的一種方式,不能用from module import * 導入,其餘方面和公有同樣訪問
# __foo
__foo:這個有真正的意義:解析器用_classname__foo來代替這個名字,以區別和其餘類相同的命名,它沒法直接像公有成員同樣隨便訪問,經過對象名._類名__xxx這樣的方式能夠訪問
# foo_
foo_:在Python的官方推薦的代碼樣式中,還有一種單下劃線結尾的樣式,這在解析時並無特別的含義,但一般用於和 Python關鍵詞區分開來,若是咱們須要一個變量叫作 class,但 class 是 Python 的關鍵詞,就能夠以單下劃線結尾寫做 class_
重載
函數重載主要是爲了解決兩個問題
1. 可變參數類型
2. 可變參數個數
另外,一個基本的設計原則是,僅僅當兩個函數除了參數類型和參數個數不一樣之外,其功能是徹底相同的,此時才使用函數重載,若是兩個函數的功能其實不一樣,那麼不該當使用重載,而應當使用一個名字不一樣的函數。
對於狀況1:函數功能相同,可是參數類型不一樣,python 如何處理?答案是根本不須要處理,由於 python 能夠接受任何類型的參數,若是函數的功能相同,那麼不一樣的參數類型在 python 中極可能是相同的代碼,沒有必要作成兩個不一樣函數。
對於狀況2:函數功能相同,但參數個數不一樣,python 如何處理?你們知道,答案就是缺省參數。對那些缺乏的參數設定爲缺省參數便可解決問題。由於你假設函數功能相同,那麼那些缺乏的參數終歸是須要用的
# 缺省參數便是調用該函數時:缺省參數的值若未被傳入,則傳入默認預設的值
# 多值參數
函數參數列表中,參數前增長一個*能夠接收元組,增長兩個*能夠接收字典。
* args(arguments) 存放元組參數
* * kwargs 存放字典參數
好了,鑑於狀況 1 跟 狀況 2 都有了解決方案,python 天然就不須要函數重載了。
新式類和舊式類
新式類都從object繼承,經典類不須要。
新式類的MRO(method resolution order 基類搜索順序)算法採用C3算法廣度優先搜索,而舊式類的MRO算法是採用深度優先搜索
新式類相同父類只執行一次構造函數,經典類重複執行屢次
Python 2.x中默認都是經典類,只有顯式繼承了object纔是新式類
Python 3.x中默認都是新式類,經典類被移除,沒必要顯式的繼承object
# python 新式類例子
class A(object):
pass
class B(A):
pass
class C(A):
pass
class D(A):
pass
class E(B, C):
pass
class F(C, D):
pass
class G(D):
pass
class H(E, F):
pass
class I(F, G):
pass
class K(H, I):
pass
# K --> H --> E--> B--> I --> F --> C --> G --> D -->A
魔法函數
class A(object):
def __new__(cls,*args,**kwargs):
print('__new__')
return super().__new__(cls)
def __init__(self,*args,**kwargs):
print('__init__')
a = A()
# __init__
__init__方法作的事情是在對象建立好以後初始化變量
# __new__
__new__方法會返回一個建立的實例,而__init__什麼都不返回
當建立一個新實例時調用__new__,初始化一個實例時用__init__
# __call__
當調用實例對象,自動會調用此方法
# __del__
析構函數,當刪除一個對象時,則會執行此方法,對象在內存中銷燬時,自動會調用此方法
元類
# 類也是對象,元類是建立類的類
# 元類可控制類對象建立的過程
# python中類的實例化過程,會首先尋找metaclass,經過metaclass建立類
# 若是沒有找到metaclass,則由type來建立類對象
class MetaClass(type): # 自定義元類繼承type
def __new__(cls, *args, **kwargs):
super().__new__(cls, *args, **kwargs)
class User(metaclass=MetaClass):
# _metaclass_ 指定元類
def __init__(self):
pass
自省
# 自省(Introspection):自省是經過必定的機制查詢到對象的內部結構
dir(obj)
dir(obj)能夠獲取一個對象全部的屬性與方法,返回爲列表(僅有屬性或方法名稱)
dir()是Python提供的一個API函數,dir()函數會自動尋找一個對象的全部屬性(包括從父類中繼承的屬性和方法)
__dict__
__dict__字典中存儲的是對象或類的部分屬性,鍵爲屬性名,值爲屬性值
實例對象的__dict__僅存儲與該實例相關的實例屬性
類的__dict__存儲全部實例對象共享的變量和函數(類屬性,方法等),類的__dict__並不包含其父類的屬性和方法
dir()和__dict__的區別
1.dir()是一個函數,返回的是list,僅有屬性名和方法名;
2.__dict__返回是一個字典,鍵爲屬性名,值爲屬性值;
3.dir()用來尋找一個對象的全部屬性和方法(包括從父類中繼承的屬性和方法),包括__dict__中的屬性和方法,__dict__是dir()的子集;
注:並非全部對象都擁有__dict__屬性。許多內建類型就沒有__dict__屬性,如list,此時就須要用dir()來列出對象的全部屬性和方法
---------------------------------------------------------------------------------------------
hasattr(object, name)
檢查對象是否具體 name 屬性。返回 bool.
getattr(object, name, default)
獲取對象的name屬性。
setattr(object, name, default)
給對象設置name屬性
delattr(object, name)
給對象刪除name屬性
dir([object])
獲取對象大部分的屬性
isinstance(name, object)
檢查name是否是object對象
type(object)
查看對象的類型
callable(object)
判斷對象是不是可調用對象
內存管理和垃圾回收
內存管理
Python有內存池機制,用於對內存的申請和釋放管理,預先在內存中申請必定數量的,大小相等的內存塊留做備用,當有新的內存需求時,就先從內存池中分配內存給這個需求,不夠了以後再申請新的內存,這樣作最顯著的優點就是可以減小內存碎片,提高效率
垃圾回收
垃圾回收機制,Python採用GC做爲自動內存管理機制,GC要作的有2件事,一是找到內存中無用的垃圾對象資源,二是清除找到的這些垃圾對象,釋放內存給其餘對象使用。
Python採用了"引用計數"爲主,"標誌清除"和"分代回收"爲輔助策略
# 引用計數
PyObject是每一個對象必有的內容,其中ob_refcnt就是作爲引用計數,當一個對象有新的引用時,它的ob_refcnt就會增長,當引用它的對象被刪除,它的ob_refcnt就會減小,一旦對象的引用計數爲0,該對象當即被回收,對象佔用的內存空間將被釋放
優勢:
簡單
實時性
缺點:
須要額外的空間維護引用計數
沒法解決循環引用問題
# 循環引用
A和B相互引用並且沒有外部引用A與B中的任何一個,也就是對象之間互相應用,致使引用鏈造成一個環
# 標記清除
標記清除主要是解決循環引用問題
標記清除算法是一種基於追蹤回收(tracing GC)技術實現的垃圾回收算法,它分爲兩個階段:第一階段是標記階段,GC會把全部的活動對象打上標記,第二階段是把那些沒有標記的對象非活動對象進行回收
對象之間經過引用(指針)連在一塊兒,構成一個有向圖,對象構成這個有向圖的節點,而引用關係構成這個有向圖的邊,從根對象(root object)出發,沿着有向邊遍歷對象,可達的(reachable)對象標記爲活動對象,不可達的對象就是要被清除的非活動對象,根對象就是全局變量、調用棧、寄存器
優勢:
解決類循環引用的問題
缺點:
清除非活動的對象前它必須順序掃描整個堆內存
# 分代回收
分代回收是一種以空間換時間的操做方式
分代回收思想將對象分爲三代(generation 0,1,2) 0表明幼年對象,1表明青年對象,2表明老年對象,隨着代數越高腳檢測頻率越低,每一代都有最大容納對象的個數,一旦超過容納個數就會出發垃圾回收機制,老年對象觸發將清理全部三代,青年對象觸發會清理青年和幼年,幼年對象觸發後只會清理本身
內存的結構
# 程序的內存分配
棧(stack):有編譯器自動分配和釋放,存放函數的參數、局部變量、臨時變量、函數返回地址等
堆(heap):通常有程序員分配和釋放,若是沒有手動釋放,在程序結束時可能由操做系統自動釋放,稍有不慎會引發內存泄漏
# Python、Go有回收機制的語言,C/CPP必須手動釋放開闢的堆內存)
# 申請後系統的響應
棧:只要棧的剩餘空間大於所申請的空間,系統將爲程序提供內存,不然將報異常提示棧溢出
堆:在記錄空閒內存地址的鏈表中尋找一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統會在這塊內存空間的首地址處記錄本次分配空間的大小,這樣代碼中的delete才能正確釋放本內存空間,系統會將多餘的那部分從新空閒鏈表中
# 申請大小限制
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域,這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在linux下默認棧大小爲8Mb,若是申請的空間超過棧的剩餘空間時,將提示overflow,所以,能從棧得到的空間較小(速度快,沒法控制)
堆:堆是向高地址擴展的數據結構,是不連續的內存區域,這是因爲系統是用鏈表來存儲的空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址,
堆的大小受限於計算機系統中有效的虛擬內存,因而可知,堆得到的空間比較靈活,也比較大(速度慢,操做方便)
# 分配效率
棧:由系統自動分配,速度較快,但程序員是沒法控制的
堆:通常速度比較慢,並且容易產生內存碎片,不過用起來最方便
# 存儲內容
棧:在棧中,第一個進棧的是主函數下一條指令的地址,而後是函數的各個參數,在大多數編譯器中,參數是由右往左入棧,而後是函數中的局部變量,注意,靜態變量不入棧,出棧則恰好順序相反
堆:通常在堆的頭部用一個字節存放堆的大小,具體內容由程序員安排
# 內存分紅5個區
棧:內存由編譯器在須要時自動分配和釋放,一般用來存儲局部變量和函數參數,(爲運行函數而分配的局部變量、函數參數、返回地址等存放在棧區),
棧運算分配內置於處理器的指令集中,效率很高,可是分配的內存容量有限
堆:內存使用new進行分配,使用delete或delete[]釋放,若是未能對內存進行正確的釋放,會形成內存泄漏,但在程序結束時,會由操做系統自動回收
自由存儲區:使用malloc進行分配,使用free進行回收,和堆相似
全局/靜態存儲區:全局變量和靜態變量被分配到同一塊內存中,C語言中區分初始化和未初始化的,C++中再也不區分了(全局變量、靜態數據、常量存放在全局數據區)
常量存儲區:存儲常量,不容許被修改
--
# 內存分爲3個區
靜態存儲區:內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在,它主要存放靜態數據、全局數據和常量
棧區:在執行函數時,函數內局部變量的存儲單元均可以在棧上建立,函數執行結束時這些存儲單元自動被釋放,棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限
堆區:亦稱動態內存分配,程序在運行的時候用malloc或new申請任意大小的內存,程序員本身負責在適當的時候用free或 delete釋放內存,動態內存的生存期能夠由咱們決定,
若是咱們不釋放內存,程序將在最後才釋放掉動態內存,可是,良好的編程習慣是:若是某動態內存再也不使用,須要將其釋放掉,不然,咱們認爲發生了內存泄漏現象
# 代碼示例
void fn()
{
int *p = new int[5]
}
new,分配了一塊堆內存,指針p分配的示一塊棧內存,因此這句話的意思,就是:在棧內存中存放了一個指向一塊堆內存的指針p
棧頂和棧底
ESP:棧指針寄存器(extended stack pointer),其內存放着一個指針,該指針永遠指向系統棧最上面一個棧幀的棧頂
EBP:基址指針寄存器(extended base pointer),其內存放着一個指針,該指針永遠指向系統棧最上面一個棧幀的底部
設計模式
單例模式
# __new__
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
# 由於__new__是一個靜態方法
setattr(cls, '_instance', instance)
return cls._instance
a = Singleton()
b = Singleton()
c = Singleton()
print(id(a)) # 4325283320
print(id(b)) # 4325283320
print(id(c)) # 4325283320
# 共享屬性
建立實例時把全部實例的`__dict__`指向同一個字典,這樣它們具備相同的屬性和方法.
class Singleton(object):
_local = {'name':'Allen'}
def __new__(cls, *args, **kwargs):
obj = super(Singleton, cls).__new__(cls, *args, **kwargs)
obj.__dict__ = cls._local
return obj
s1 = S()
s2 = S()
print(s1.name)
s1.name = 'Kevin'
print(s2.name)
# 裝飾器(類裝飾器)
def decorator(cls):
instances = {}
def wrapper(*args,**kwargs):
if cls not in instances:
obj = cls(*args,**kwargs)
instances[cls] = obj
return instances[cls]
return wrapper
@decorator
class MyClass(objcet):
a = 1
m1 = MyClass()
m2 = MyClass()
m3 = MyClass()
print(id(m1))
print(id(m2))
print(id(m3))
# import
# 做爲python的模塊是自然的單例模式
a.py
class MyClass(object):
a = 1
obj = MyClass()
b.py
import a
o1 = a.obj
o2 = a.obj
o3 = a.obj
網絡編程
ISO七層協議
# 物理層:比特
經過光纜、電纜、雙絞線、無線電波發送電信號,高電壓對應1,低電壓對應數字0
# 數據鏈路層:幀
工做於以太網協議,將一組電信號構成一個數據包,叫作幀,每一組數據幀分紅報頭head和數據data來個部分(head固定18字節,data最短46字節,最長1500字節)
以太網協議的接入internet的設備都必須具有網卡,發送端和接受端的地址即是mac地址而且該地址爲全球惟一的,經過廣播的的方式來進行通訊,向在同一局域網內的每個計算機發送消息來確認接受者
# 網絡層:報文
引入IP協議,該協議定義的地址成爲IP地址,普遍採用ipv4,規定網絡定製有32位2進製表示:0.0.0.0-255.255.255.0
ip地址分爲兩個部分:表示子網,表示主機
子網掩碼:經過子網掩碼,咱們就能判斷,任意兩個ip地址是否處於同一個子網內,IP地址和子網掩掩碼經過and運算得出結果,ip數據包分爲head和data部分,無需爲ip數據包定義單獨的欄位,直接放入以太網包的data部分,head(20-60字節),data(最長65515字節),而以太網數據包的data(數據)部分,最長只有15000字節,所以,若是ip數據包超過了1500字節,他就須要分隔成幾個以太網數據包,分開發送,
# 傳輸層:TPDU--傳輸協議數據單元
# 會話層:SPDU--會話協議數據單元
# 表示層:SPDU--表示協議數據單元
# 應用層:APDU--應用協議數據單元
wsgi
WSGI是 Web Server Gateway Interface 的縮寫。
它是 Python應用程序(application)或框架(如 Django)和 Web服務器之間的一種接口,已經被普遍接受。
它是一種協議,一種規範,其是在 PEP 333提出的,並在 PEP 3333 進行補充(主要是爲了支持 Python3.x)。這個協議旨在解決衆多 web 框架和web server軟件的兼容問題。有了WSGI,你不用再由於你使用的web 框架而去選擇特定的 web server軟件
WSGI 接口有服務端和應用端兩部分,服務端也能夠叫網關端,應用端也叫框架端。服務端調用一個由應用端提供的可調用對象。如何提供這個對象,由服務端決定。例如某些服務器或者網關須要應用的部署者寫一段腳本,以建立服務器或者網關的實例,而且爲這個實例提供一個應用實例。另外一些服務器或者網關則可能使用配置文件或其餘方法以指定應用實例應該從哪裏導入或獲取
WSGI 對於 application 對象有以下三點要求
必須是一個可調用的對象
接收兩個必選參數environ、start_response
返回值必須是可迭代對象,用來表示http body
一次http請求的過程
一個HTTP請求的過程分爲2個階段
#第一個階段是從客戶端到WSGI server
#第二個階段是從WSGI server到WSGI application
socket
併發編程
GIL全局解釋器鎖
# 一個python文件執行的過程
一、首先執行py文件,向操做系統發起請求,操做系統劃出了一塊內存空間(運行一個python程序只產生一個進程<兩份數據:一個py文件數據,一個是python解釋器數據>)
二、先把python解釋器數據送到內存中,而後在把py文件送到內存中,(他們都在一個進程中傳送)
三、最後把py文件看成普通字符串傳送到python解釋器中,而後解釋器對其進解釋(py程序脫離了解釋器就是普通的字符串)
# GIL的做用
GIL:global interpreter lock(全局解釋器鎖)
Python中一個線程對應於c語言中的一個線程
GIL使得同一個時刻只有一個線程運行在一個CPU上執行字節碼,在同一進程下沒法將多個線程映射到多個cpu上執行
GIL會根據執行的字節碼行數以及時間片釋放GIL,GIL在遇到IO操做時會主動釋放
GIL同步線程執行字節碼
# GIL的優缺點:
優勢:
保證CPython解釋器中內存管理的線程安全
缺點:
同一進程下的多線程沒法實現並行
# 全局解釋器鎖(Global Interpreter Lock)
Python爲了保證線程安全而採起的獨立線程運行的限制,也就是說一個核只能在同一時間運行一個線程,對於io密集型任務,python的多線程起到做用,io不佔用cpu資源,但對於cpu密集型任務,python的多線程發揮不出多核優點
# 注意:
GIL鎖不能保證數據安全,GIL只是保證解釋器數據安全,若是要保證本身的數據安全就須要本身加一把鎖
# 解決方法
1.多進程
2.協程(協程也只是單CPU,可是能減少切換代價提高性能)
進程和線程
# 進程:
定義:系統資源分配與調度的基本單位
優勢:獨佔操做系統與計算機資源,尤爲是獨佔內存地址空間,因此多進程場景中的單個進程異常不會讓整個應用程序崩潰
缺點:獨佔資源多,因此進程的建立、銷燬、通訊及切換的成本都比較高
# 守護進程:
對於主進程來講:運行完畢指的是主進程代碼運行完畢
主進程在其代碼執行完畢就算結束了(此時回收守護進程),可是主進程會等待全部非守護進程執行完畢並揮手資源才結束(防止產生殭屍進程);
# 線程
定義:處理器調度的基本單位
優勢:輕量基本不獨佔資源,因此線程的建立、銷燬、通訊及切換的成本都更低,更有利於在多線程場景中提升程序的併發性能
缺點:沒有隔離出私有內存,因此單個線程的崩潰可能會致使整個應用程序退出
# 守護線程:
對於主線程來講:主線程所在的進程中全部非守護線程執行完畢,主線程纔算執行完畢
主線程在全部非守護線程執行完畢才結束,對主線程來講,主線程的結束意味着主進程的結束,線程是執行單位
# 線程安全
進程間通訊
from multiprocessing import Process
from multiprocessing import Manager
from multiprocessing import Queue
from multiprocessing import Pool
from multiprocessing import Pipe
import time
def producer(q):
q.put("Hello")
def consumer(q):
result = q.get()
print(result)
def p(pi):
time.sleep(2)
pi.send("Hello")
def c(pi):
print(pi.recv())
def add(s_dict, key, value, lock):
lock.acquire()
s_dict[key] += value
lock.release()
if __name__ == '__main__':
# 共享變量在多進程不適用,在多線程適用
# q = Queue(10)
# p1 = Process(target=producer, args=(q,))
# p2 = Process(target=consumer, args=(q,))
# p1.start()
# p2.start()
# p1.join()
# p2.join()
# 進程池不能使用Queue,Pool使用Manager中Queue來傳遞參數
# o = Manager().Queue(10)
# pool = Pool(2)
# pool.apply_async(func=producer, args=(o,))
# pool.apply_async(func=consumer, args=(o,))
# pool.close()
# pool.join()
# pipe的性能要不queue要高
# 經過Pipe來進行進程間通訊
# Pipe只能適用於兩個進程間的通訊
# recv, send = pipe = Pipe() # 返回兩個connection,一個發送數據,一個接收數據
# px = Process(target=p, args=(send,))
# cx = Process(target=c, args=(recv,))
# px.start()
# cx.start()
# px.join()
# cx.join()
# Manager()共享內存,使用共享內存在修改數據要保證同步性
shareDict = Manager().dict()
mutex = Manager().Lock()
shareDict["key"] = 0
all_task = [Process(target=add, args=(shareDict, "key", 10000, mutex)) for _ in range(10)]
for t in all_task:
t.start()
for x in all_task:
x.join()
print(shareDict)
併發和並行
1.單線程下實現併發
併發指的是多個任務看起來好像是同時運行的
併發實現的本質:切換+保存狀態
併發、並行、串行:
併發:看起來是同時運行,切換+保存狀態
並行:真正意義上的同時運行,只有在多cpu的狀況下才能實現並行,4個cpu可以並行4個任務
串行:一個任務完完整整的執行完畢才能運行下一個任務
協程:
是單線程下的併發,又稱爲線程,纖程,英文名(Coroutine)。操做系統不會認協程
#1. python的線程屬於內核級別的,即由操做系統控制調度(如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其餘線程運行)
#2. 單線程內開啓協程,一旦遇到io,就會從應用程序級別(而非操做系統)控制切換,以此來提高效率(!!!非io操做的切換與效率無關)
優勢:
#1. 協程的切換開銷更小,屬於程序級別的切換,操做系統徹底感知不到,於是更加輕量級
#2. 單線程內就能夠實現併發的效果,最大限度地利用cpu
缺點:
#1. 協程的本質是單線程下,沒法利用多核,能夠是一個程序開啓多個進程,每一個進程內開啓多個線程,每一個線程內開啓協程
#2. 協程指的是單個線程,於是一旦協程出現阻塞,將會阻塞整個線程
特色:
必須在只有一個單線程裏實現併發
修改共享數據不需加鎖
用戶程序裏本身保存多個控制流的上下文棧
附加:一個協程遇到IO操做自動切換到其它協程(如何實現檢測IO,yield、greenlet都沒法實現,就用到了gevent模塊(select機制)
協程
# 協程(Coroutine):能夠被暫停並切換到其餘協程運行的函數(協程不一樣於進程和線程,有咱們程序(員)本身調度)
# 協程可讓咱們像寫同步代碼去寫異步代碼
# 在python有一個特例是能夠被暫停的那就是:生成器
# 無需線程上下文切換的開銷,協程避免了無心義的調度,由此能夠提升性能(但也所以,程序員必須本身承擔調度的責任,同時,協程也失去了標準線程使用多CPU的能力)
# 無需原子操做鎖定及同步的開銷
# 方便切換控制流,簡化編程模型
# 高併發+高擴展性+低成本,適合作併發處理
# 進行 阻塞 操做 會阻塞掉整個程序,如是要避免出現阻塞操做發生
def gen_func():
yield 1
yield 2
yield 3
gen = gen_func() # 建立一個生成器對象
v = gen.__next__()
print(v)
# 1.將生成器包裝成協程
from tornado.gen import coroutine
@coroutine
def cor1():
yield 1
yield 2
yield 3
# 2.(在python3.5中提供原生協程)就是async/awiat
async def cor2():
# yield和yield from 不能夠寫在async定義的協程中
await cor1() # 若是想要獲取其餘協程的結果就要使用await
Queue
# 使用Queue做爲線程間通訊的媒介,能夠不用去考慮資源競態的問題,他是線程安全的
# Queue內部使用的是deque雙端隊列(deque是線程安全的)
q.get() # 向隊列中取值,默認阻塞,能夠設置block=false異步執行
q.put() # 向隊列中放置
q.get_nowait() # 內部仍是調用get方法,執行傳遞了一個block=false參數
q.put_nowait()
q.qsize() # 隊列的長度
q.empty() # 隊列是否爲空
q.full() # 隊列是否爲滿
q.join() # 阻塞隊列,直到向隊列發送一個task_done
q.task_done() # 向隊列發出一個信號
線程同步
Lock:互斥鎖
RLock:可重入鎖
Condition:條件鎖
from threading import Condition
from threading import Thread
from threading import Lock
# condition條件變量,用於複雜的線程間同步
# 經過Condition完成協同讀詩
class Ai(Thread):
def __init__(self, cond: Condition):
super(Ai, self).__init__(name="小愛同窗")
self.cond = cond
def run(self):
with self.cond:
self.cond.wait()
print(f"{self.name}:在")
self.cond.notify()
self.cond.wait()
print(f"{self.name}:好啊")
self.cond.notify()
self.cond.wait()
print(f"{self.name}:君住長江尾")
self.cond.notify()
self.cond.wait()
print(f"{self.name}:共飲長江水")
self.cond.notify()
self.cond.wait()
print(f"{self.name}:此恨什麼時候已")
self.cond.notify()
self.cond.wait()
print(f"{self.name}:定不負相思意")
class TMall(Thread):
def __init__(self, cond: Condition):
super(TMall, self).__init__(name="天貓精靈")
self.cond = cond
def run(self):
with self.cond:
print(f"{self.name}:小愛同窗")
self.cond.notify()
self.cond.wait()
print(f"{self.name}:咱們來古詩吧")
self.cond.notify()
self.cond.wait()
print(f"{self.name}:我住長江頭")
self.cond.notify()
self.cond.wait()
print(f"{self.name}:日日思君不見君")
self.cond.notify()
self.cond.wait()
print(f"{self.name}:此水幾時休")
self.cond.notify()
self.cond.wait()
print(f"{self.name}:只願君心似我心")
self.cond.notify()
if __name__ == '__main__':
lock = Condition()
t = TMall(lock)
a = Ai(lock)
a.start()
t.start()
# 啓動順序很重要
# 在調用with cond(或者cond.acquire)以後才能調用wait或者notify方法 error:cannot notify on un-acquired lock
# condition有兩層鎖,cond.acquire和waiter,一把底層鎖會在線程調用了wait方法的時候釋放,上面的鎖會在每次調用wait的時候分配一把並放入cond的等待隊列中(deque)等待notify方法的喚醒
t.join()
a.join()
Semaphores:信號量
# Semaphore是用於控制進入數量的鎖
# 文件,讀,寫,寫通常只是用於一個線程寫,讀能夠容許有多個
import time
from threading import Semaphore
from threading import Thread
def task(index, sem: Semaphore):
time.sleep(2)
print(f"Get success {index}")
sem.release()
def handler(s: Semaphore):
for i in range(20):
s.acquire()
Thread(target=task, args=(i, s)).start()
if __name__ == '__main__':
semaphore = Semaphore(3)
handler(semaphore)
線程池和進程池
同步、異步、阻塞、非阻塞
一、阻塞和非阻塞指的是程序的兩種運行狀態(運行態、就緒態、阻塞態)
阻塞(就緒態和阻塞態):遇到IO操做就發生阻塞,程序一旦遇到阻塞操做就會停在原地,而且馬上釋放CPU資源
非阻塞(運行態):沒有遇到IO操做,或者經過某種手段讓程序即使是遇到IO操做也不會停在原地,執行其餘操做,力求儘量多的佔有CPU
二、同步與異步指的是提交任務的兩種方式
同步調用:提交完任務後,就在原地等待,直到任務運行完畢後,拿到任務的返回值,才能繼續執行下一行代碼
異步調用:提交任務後,不在原地等待,直接執行一下一行代碼,結果?
同步(調用/執行/任務/提交),發起任務後必須等待任務結束,拿到一個結果才能繼續執行
異步(調用/執行/任務/提交),發起任務後不須要關心任務的執行結果,能夠繼續往下運行
異步效率高於同步
可是並非說全部任務均可以異步,判斷一個任務是否能夠異步的條件是,任務發起是否當即須要執行結果
但使用異步方式發起任務是 任務中可能包含io操做 異步不可能阻塞
同步提交任務 也會卡主程序 可是不等同阻塞,由於任務中可能在作一對計算任務,cpu沒走
同步和異步關注的是獲取結果的方式,同步是獲取到結果以後才進行下一步操做,阻塞非阻塞關注的是調用接口時當前線程的狀態,同步能夠調用阻塞也可非阻塞,異步是調用非阻塞接口
# 同步是指代碼調用IO操做時,必須等待IO操做完成才返回的調用方式
# 異步是指代碼調用IO操做時,沒必要等IO操做完成就返回的調用方式
# 阻塞是指調用函數時候當前線程被掛起
# 阻塞是指調用函數時候當前線程不會被掛起,而是當即返回
select、poll、epoll
select、poll、epoll都是io多路複用的機制,i/o多路複用就是同一種機制,一個進程能夠監視多個描述符(能夠理解爲多個socket),一旦某個描述符就緒(通常是讀就緒或者寫就緒),可以通知程序進行相應的讀寫操做,可是select、poll、epoll本質上都是同步i/o,由於他們都須要在讀寫事件就緒後本身負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步i/o則無需本身負責進行讀寫,異步i/o的實現會負責把數據從內核拷貝到用戶空間
# select
select函數事件的文件描述符分爲3類,分別是writefds(可寫的文件描述符)、readfds(可讀的文件描述符)、exceptfds(異常文件描述符),調用後select函數會阻塞,直到有描述符就緒(可數據可讀、可寫、有異常),或者超時(timeout指定等待時間,若是當即返回設爲None便可),函數返回,當select函數返回後,能夠經過遍歷fdset,來找到就緒的描述符.
select目前幾乎在全部的平臺上支持,其良好跨平臺支持也是它的一個優勢,select的一個缺點在於單個進程可以監視的文件描述符的數量存在最大限制,linux上通常爲1024,能夠經過修改宏定義甚至從新編譯內核的方式提高這一限制,可是這樣也會形成效率的下降(select每個調用文件描述符都會去遍歷一次[性能比較低])
# poll
不一樣與select使用三個位圖表示三個fdset的方式,poll使用一個pollfd的指針實現
pollfd結構包含了監視的event和發生的event,不在使用select'參數-值'傳遞的方式,同時,pollfd並無最大數量限制(可是數量過大後性能也是會降低),和select函數同樣,poll返回後,須要輪訓pollfd來獲取就緒的描述符
從上面看,select和poll都須要在返回後,經過遍歷文件描述符來獲取已經就緒的socket,事實上,同時鏈接的大量客戶端在同一時刻只有不多的處於就緒狀態,所以隨着監視的描述符數量的增加,其效率也會線性降低
# epoll
epoll是在linux2.6內核中提出的,是以前的select和poll的加強版本,相對於selct和poll來講,epoll更加靈活,沒有描述符限制,epoll使用一個文件描述符管理多個描述符,將用戶關係的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次
FD_ZERO(int fd, fd_set* fds)
FD_SET(int fd, fd_set* fds)
FD_ISSET(int fd, fd_set* fds)
FD_CLR(int fd, fd_set* fds)
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout)
# select 機制提供一個fd_set的數據結構,其實是一個long類型的數組,每個數組元素能與一個打開的文件描述符創建聯繫,select中文件描述符分爲3類,分別是writefds(可寫的文件描述符)、readfds(可讀的文件描述符)、exceptfds(異常文件描述符),當調用select時,由內核根據IO狀態來修改fd_set的內容,由此來通知執行了select的進程那些文件/socket可讀可寫,而後遍歷fd_set找出可讀或可寫描述符,從內核空間拷貝到用戶空間( 時間複雜度O(n) ),select可以監聽的描述符有限一般爲1024個
struct pollfd {
int fd; //文件描述符
short events; //要求查詢的事件掩碼
short revents; //返回的事件掩碼
};
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
# poll 使用pollfd的數據結構來監聽一組文件描述符,和select相似無非就是沒有對監聽描述符的閒置
# epoll 提供三個函數epoll_create(建立一個epoll句柄),epoll_ctl(註冊要監聽的事件類型)和epoll_wait(等待事件的產生),epoll在每次註冊新的事件到epoll句柄中時,會把全部的描述符拷貝進內核空間,而不是在epoll_wait的時候重複拷貝,保證整個過程只拷貝一次,epoll在epoll_ctl(註冊監聽事件類型的時候)爲每個描述符指定一個回調函數,當描述符就緒時就會調用這個會調用函數,而這個函數會把就緒的fd加入一個就緒鏈表(rdlist)中,epoll_wait就從就緒鏈表中查看有沒有就緒的描述符,就不須要遍歷整個事件表
UNIX五種I/O模型
對於一個Network IO(read舉例),他會涉及到兩個系統對象,一個是調用這個IO的Process/Thread,另外一個就是系統內核(kernel),當一個read操做發生時,經歷兩個階段:等待數據準備(wait for data)、將數據從內核空間拷貝到用戶空間(copy data from kernel to user)
阻塞式IO
執行recvfrom系統調用,kernel等到數據的的到來,用戶進程此時會被阻塞,當kernel準備好數據,他將數據從kernel中拷貝到用戶內存,kernel返回結果,用戶進程解除阻塞
非阻塞式IO
用戶進程發出readfrom系統調用時,若是kernel的數據沒有準備好,那麼他並不會阻塞而是直接返回一個error,當用戶進程判斷結果爲error時,那麼用戶能夠再次向kernel發出read操做(用戶進程不停的向內核詢問,知道內核數據準備好了),一旦kernel中的數據準備好了,而且又再次用戶的系統調用,就將數據從內核空間拷貝到用戶空間,而後返回 # 非阻塞IO的這種輪訓方式會大量的消耗CPU資源
IO多路複用
信號驅動式I/O
異步I/O
io多路複用+回調+事件循環
# 例子
import time
from selectors import DefaultSelector
from selectors import EVENT_READ
from selectors import EVENT_WRITE
from urllib.parse import urlparse
import socket
selector = DefaultSelector()
event_status = True
urls = []
class Fetch(object):
def __init__(self, host, path, spider_url):
self.host = host
self.path = path
self.spider_url = spider_url
self.client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0)
self.client.setblocking(False)
self.data = b""
@classmethod
def parse_url(cls, url):
parsed_url = urlparse(url)
host = parsed_url.netloc
path = parsed_url.path
if not path:
path = "/"
return cls(host, path, url)
def connect(self):
try:
self.client.connect((self.host, 80))
except BlockingIOError:
selector.register(self.client.fileno(), EVENT_WRITE, self.writeable)
def writeable(self, key):
selector.unregister(key.fd)
self.client.send(f"GET {self.path} HTTP/1.1\r\nHost:{self.host}\r\nConnection:close\r\n\r\n".encode("utf8"))
selector.register(self.client.fileno(), EVENT_READ, self.readable)
def readable(self, key):
data = self.client.recv(1024)
if data:
self.data += data
else:
selector.unregister(key.fd)
html = self.data.decode("utf8").split("\r\n\r\n", maxsplit=1)[1]
urls.remove(self.spider_url)
print(html)
if len(urls) == 0:
global event_status
event_status = False
self.client.close()
def even_loop():
while event_status:
ready = selector.select()
for key, events in ready:
callback = key.data
callback(key)
if __name__ == '__main__':
start_time = time.time()
for i in range(1, 21):
u = f"http://shop.projectsedu.com/goods/{i}/"
urls.append(u)
Fetch.parse_url(u).connect()
even_loop()
end_time = time.time()
print(end_time - start_time)
異步編程和回調之痛
異步編程:
# 以進程、線程、協程、函數/方法做爲執行任務程序的基本單位,結合回調、事件循環、信號量等機制,以提升程序總體執行效率和併發能力的編程方式
# 若是在某程序的運行時,能根據已經執行的指令準確判斷它接下來要進行哪一個具體操做,那它是同步程序,反之則爲異步程序(無序與有序的區別)
# 同步/異步、阻塞/非阻塞並不是水火不容,要看討論的程序所處的封裝級別。例如購物程序在處理多個用戶的瀏覽請求能夠是異步的,而更新庫存時必須是同步的
異步之難:
# 控制不住「計幾」寫的程序,由於其執行順序不可預料,當下正要發生什麼事件不可預料,在並行狀況下更爲複雜和艱難
# 因此,幾乎全部的異步框架都將異步編程模型簡化:一次只容許處理一個事件,故而有關異步的討論幾乎都集中在了單線程內
# 若是某事件處理程序須要長時間執行,全部其餘部分都會被阻塞
# 因此,一旦採起異步編程,每一個異步調用必須"足夠小",不能耗時過久。如何拆分異步任務成了難題。
程序下一步行爲每每依賴上一步執行結果,如何知曉上次異步調用已完成並獲取結果?
回調(Callback)成了必然選擇,那又須要面臨"回調地獄"的折磨
同步代碼改成異步代碼,必然破壞代碼結構
解決問題的邏輯也要轉變,再也不是一條路走到黑,須要精心安排異步任務
若是回調函數執行不正常該如何
若是回調裏面還要嵌套回調怎麼辦,要嵌套不少層
若是嵌套了多層,其中某個環節出錯了會形成什麼後果
若是有個數據須要被每一個回調用都處理怎麼辦
怎麼使用單前函數中的局部變量
可讀性差
共享狀態管理困難
異常處理困難
協程
1.回調模式編碼複雜度高
2.同步編程的併發性不高
3.多線程編程須要線程間同步 # Lock使用鎖會下降性能
1.採用同步的方式去編寫異步的代碼
2.使用單線程去切換任務
1.線程是有操做系統切換的,單線程切換覺得者咱們須要程序員本身去調度任務
2.再也不須要鎖,併發性高,若是但線程內切換函數,性能遠高於線程切換,併發性更高
協程:有多個入口的函數,能夠暫停的函數,能夠暫停的函數(能夠向暫停的地方傳入值)
傳統的函數時調用棧的
協程也是一種IO複用的手段,相比於select + 回調,協程的優點是編碼結構同步。
所謂IO複用(以實現高併發),其實本質上是將CPU操做和IO操做分離,在全部的python代碼中都只進行CPU操做,將IO操做發送至內核完成,應用層不阻塞。從結果的角度來看這和多線程是相似的(由於python有gil鎖,而線程也是遇到IO操做就切換,只不過線程切換是由操做系統完成的,而協程是由程序員完成的,這點bobby老師屢次強調!)
爲何傳統的函數不能夠?
由於函數是基於棧進行調用的,只能運行完,而後退出。不可以暫停(沒法由程序員調度任務),而後運行別的程序,再回來!
每一個協程都有本身的棧(上下文)
next和send源碼
static PyObject *
gen_iternext(PyGenObject *gen)
{
return gen_send_ex(gen, NULL, 0);
}
static PyObject *
gen_send(PyGenObject *gen, PyObject *arg)
{
return gen_send_ex(gen, arg, 0);
}
static PyObject *
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
{
PyThreadState *tstate = PyThreadState_GET();
PyFrameObject *f = gen->gi_frame;
PyObject *result;
if (gen->gi_running) { // 判斷生成器是否已經運行
PyErr_SetString(PyExc_ValueError,
"generator already executing");
return NULL;
}
if (f==NULL || f->f_stacktop == NULL) { // 若是代碼塊爲空或調用棧爲空,
//則拋出StopIteration異常
/* Only set exception if called from send() */
if (arg && !exc)
PyErr_SetNone(PyExc_StopIteration);
return NULL;
}
if (f->f_lasti == -1) { // f_lasti==-1 表明首次執行
if (arg && arg != Py_None) { // 首次執行不容許帶有參數
PyErr_SetString(PyExc_TypeError,
"can't send non-None value to a "
"just-started generator");
return NULL;
}
} else {
/* Push arg onto the frame's value stack */
result = arg ? arg : Py_None;
Py_INCREF(result); // 該參數引用計數+1
*(f->f_stacktop++) = result; // 參數壓棧
}
/* Generators always return to their most recent caller, not
* necessarily their creator. */
f->f_tstate = tstate;
Py_XINCREF(tstate->frame);
assert(f->f_back == NULL);
f->f_back = tstate->frame;
gen->gi_running = 1; // 修改生成器執行狀態
result = PyEval_EvalFrameEx(f, exc); // 執行字節碼
gen->gi_running = 0; // 恢復爲未執行狀態
/* Don't keep the reference to f_back any longer than necessary. It
* may keep a chain of frames alive or it could create a reference
* cycle. */
assert(f->f_back == tstate->frame);
Py_CLEAR(f->f_back);
/* Clear the borrowed reference to the thread state */
f->f_tstate = NULL;
/* If the generator just returned (as opposed to yielding), signal
* that the generator is exhausted. */
if (result == Py_None && f->f_stacktop == NULL) {
Py_DECREF(result);
result = NULL;
/* Set exception if not called by gen_iternext() */
if (arg)
PyErr_SetNone(PyExc_StopIteration);
}
if (!result || f->f_stacktop == NULL) {
/* generator can't be rerun, so release the frame */
Py_DECREF(f);
gen->gi_frame = NULL;
}
return result;
}
生成器send、throw、close
# send
import inspect
# 定義一個生成器函數
def generator_function():
val = yield 1
print(val)
yield 1
return "Allen"
if __name__ == "__main__":
# 建立一個生成器對象
gen = generator_function()
# 在使用生成器以前須要啓動(這個動做稱爲預激)
# 啓動生成器的方式有兩種next()/send(None)
# gen.send(None) send(None)方法的值必須爲None
# 由於生成器尚未執行到第一個yield語句,send一個非None值,他沒有辦法接受,
# val = yield 1執行是從右到作,一旦遇到yield函數就被掛起,函數就暫停了,
# 也就是說先yield 1給調用方,而後接受調用方傳遞的值進行賦值,可是yield的特殊性,val = yield 1執行了一半函數就掛起了,等待下一個send(val),來接受調用方傳遞的值
next(gen)
gen.send("Hello")
# throw
# 經過throw能夠向生成器拋出異常
def gen_func():
try:
yield 1
except Exception as e:
print(e)
yield 2
if __name__ == '__main__':
g = gen_func()
print(g.send(None))
print(g.throw(Exception, "Error")) # 向生成器掛起處拋出一個異常,並接受下一個yield語句的值
# close
# 案例一
def gen_func():
try:
yield 1
except GeneratorExit:
pass
if __name__ == '__main__':
g = gen_func()
print(g.send(None))
print(g.close()) # 生成器對象的close方法會在生成器對象方法的掛起處拋出一個GeneratorExit異常
def gen_func():
try:
yield 1
except GeneratorExit:
# GeneratorExit繼承BaseException
# 或者拋出 StopIteration close方法就不會報錯了
raise StopIteration
yield 2 # 由於生成器已經關閉了
# 一旦產生了GeneratorExit異常,生成器方法後續執行的語句中,不能再有yield語句
# RuntimeError: generator ignored GeneratorExit
yield 3
if __name__ == '__main__':
g = gen_func()
print(g.send(None))
print(g.close()) # 生成器對象的close方法會在生成器對象方法的掛起處拋出一個GeneratorExit異常
# 生成器是有狀態的
import inspect
# 生成器是能夠暫停的函數
# 生成器是有狀態的
def gen_func():
yield 1
return "Allen"
# 1.用同步的方式編寫異步的代碼,在適當的時候暫停函數並在適當的時候啓動函數
if __name__ == '__main__':
gen = gen_func()
print(inspect.getgeneratorstate(gen)) # GEN_CREATED
gen.send(None)
print(inspect.getgeneratorstate(gen)) # GEN_SUSPENDED
try:
gen.__next__()
except StopIteration as e:
print(inspect.getgeneratorstate(gen)) # GEN_CLOSED
print(e.value)
# yield from 後面能夠跟一個生成器
def gen_func():
yield 1
yield 2
yield 3
yield 4
yield 5
def g1(gen):
yield from gen
def main():
gen = gen_func()
g = g1(gen)
print(g.send(None))
print(g.send(None))
print(g.send(None))
print(g.send(None))
print(g.send(None))
if __name__ == "__main__":
main()
# main:調用方
# g1:委託生成器
# gen:子生成器
# yield from會在調用方與子生氣之間創建一個通道
yield from
# yield from python3.3新增的語法
yield from 後面跟一個可迭代對象/生成器/協程
# 舉個例子
from itertools import chain
from collections import Iterable
# chain可將多個可迭代對象合併到一塊兒
my_list=[1,2,3,4,5,6,7,8,9,10]
my_dict={"key1":"value1","key2":"value2","key3":"value3","key4":"value4","key5":"value5"}
for i in chain(my_list, my_dict):
print(i)
# 經過生成器實現chain
def my_chain(*args):
if list(filter(lambda:x:not isinstance(x, Iterable),args))
raise ValueError("must be iterable")
for i in args:
for j in i:
yield j
# 經過yield from來實現
def my_chain(*args):
if list(filter(lambda:x:not isinstance(x, Iterable),args))
raise ValueError("must be iterable")
for i in args:
yield from i
yield from應用
def g1():
total = 0
while 1:
val = yield
if val is None:
break
total += 1
print(val)
return total # 結束後會拋出StopIteration,他會向上拋出異常
def g2():
while 1:
x = yield from g1() # x是g1的返回值
print("g1 return val:", x)
def main():
gen = g2()
gen.send(None)
for i in range(1, 101):
gen.send(i)
gen.send(None)
# g1:子生成器
# g2:委託生成器
# main:調用方
if __name__ == '__main__':
main()
pep380生成器yield from
import sys
"""
子生成器可能只是一個迭代器,並非一個做爲協程的生成器,因此他不支持throw()和close()方法
若是子生成器支持throw()和close()方法,可是在子生成器內部,這兩個方法都會拋出異常
調用方讓子生成器本身拋出異常
當調用方使用next()或者send(None)時,都要在子生成器調用next()函數,當調用方使用send()發送非None值時,纔會調用子生成器的send方法
yield from <expr>
RESULT = yield from EXPR
_i:子生成器
_y:子生成器生產的值
_r:yield from表達式最終值
_s:調用方經過send()發送的值
_e:異常對象
"""
def yield_from(EXPR):
_i = iter(EXPR) # yield from 後面接受一個可迭代對象(迭代器和生成器都是可迭代對象)
try:
_y = next(_i) # 調用_i的__next__方法預激生成器/開始迭代迭代器(_i能夠是迭代器也能夠是生成器)
except StopIteration as _e:
# 1.當迭代器迭代完會拋出StopIteration生成器return的時候會拋出StopIteration異常
_r = _e.value
# 生成器的return值會傳遞給StopIteration異常的第一個參數
else:
# 當預激活啓動生成器成功執行else
while 1: # 循環,阻塞委託生成器
try:
_s = yield _y
# 將子生成器產出的值yield給調用房,並等待接收調用方傳遞的值
except GeneratorExit as _e:
# 當關閉委託生成器(即調用了close()方法)或者傳遞了throw(GeneratorExit)異常
try:
_m = _i.close
# 會調用子生成器的close(首先判斷有沒有close方法)由於迭代器是沒有close方法的
except AttributeError:
pass # 若是是迭代器沒有close直接結束了
else:
_m() # 調用子生成器的close方法,關閉生成器
raise _e
except BaseException as _e:
# 除了GeneratorExit,任何異常經過throw傳遞給子生成器,好比調用方想委託生成器throw異常
_x = sys.exc_info() # BaseException是全部異常的基類,獲取異常的信息
try:
_m = _i.throw # 判斷是否有throw方法(迭代器是沒有throw方法)
except AttributeError:
raise _e # 沒有直接raise異常對象
else:
# 若是是_i是生成器,則向子生成器throw捕獲的異常
try:
_y = _m(*_x) # exc_info()返回的是一個元組
except StopIteration as _e: # 若是是拋出StopIteration則異常恢復生成器
# 當生成器return是會拋出StopIteratioe,返回值做爲StopIteratioe第一個參數傳入
_r = _e.value
break
else:
# 當接收調用方傳遞的值判斷是None仍是非None
# None調用next()
# 非None調用send()
# _y做爲接受值的變量
try:
if _s is None:
_y = next(_i)
else:
_y = _i.send(_s)
except StopIteration as _e:
# 若是生成器yield完了,還繼續傳遞值則會拋出StopIteration
_r = _e.value
break
RESULT = _r # _r就是yield from的結果
"""
1. 子生成器生產的值,都是直接傳給調用方的;調用方經過.send()發送的值都是直接傳遞給子生成器的;若是發送的是 None,會調用子生成器的__next__()方法,若是不是 None,會調用子生成器的.send()方法;
2. 子生成器退出的時候,最後的return EXPR,會觸發一個StopIteration(EXPR)異常;
3. yield from表達式的值,是子生成器終止時,傳遞給StopIteration異常的第一個參數;
4. 若是調用的時候出現StopIteration異常,委託生成器會恢復運行,同時其餘的異常會向上 "冒泡";
5. 傳入委託生成器的異常裏,除了GeneratorExit以外,其餘的全部異常所有傳遞給子生成器的.throw()方法;若是調用.throw()的時候出現了StopIteration異常,那麼就恢復委託生成器的運行,其餘的異常所有向上 "冒泡";
6. 若是在委託生成器上調用.close()或傳入GeneratorExit異常,會調用子生成器的.close()方法,沒有的話就不調用。若是在調用.close()的時候拋出了異常,那麼就向上 "冒泡",不然的話委託生成器會拋出GeneratorExit異常。#
"""
asyncio
# python3.4引入asyncio異步IO庫
# 包含各類特定系統實現的模塊化事件循環
# 傳輸和協議抽象
# 對TCP、UDP、SSL、子進程、延時調用以及其餘的具體支持
# 模仿furures模塊但適用於事件循環使用的Future類
# 基於yield from的協議和任務,可讓你用順序的方式編寫併發代碼
# 必須使用一個將產生阻塞IO的調用時,有接口能夠把這個事件轉移到線程池
# 模仿threading模塊中的同步原語、能夠用在單線程內的協程之間
# 事件循環+回調(驅動生成器/協程)+IO多路複用
# asyncio是python用於解決異步IO編程的一整套解決方案
# tornado、gevent、twisted(scrapy、django channel)
# tornado(實現web服務器),django+flask(uwsgi,gunicorn+nginx)
# tornado能夠直接部署,nginx+tornado(不能使用同步的數據庫驅動)
# event_loop(事件循環):程序開啓一個無限的循環,程序員會把一些函數(協程)註冊到事件循環上,當知足事件發生的時候,調用相應的協程函數
# coroutine 協程:協程對象,指一個使用async關鍵字定義的函數,它的調用不會當即執行函數,而是會返回一個協程對象,協程對象須要註冊到事件循環,由事件循環調用
# task(任務):一個協程對象就是一個原生能夠掛起的函數,任務則是對協程進一步封裝,其中包含任務的各類狀態,Task對象是Future的子類,它將coroutine和Future聯繫在一塊兒,將coroutine封裝成一個Future對象
# async/await關鍵字:python3.5 用於定義協程的關鍵字,async定義一個協程,await用於掛起阻塞的異步調用接口,其做用在必定程度上相似於yield/yield from
import asyncio
# 使用async定義一個原生協程
async def get_html(url):
print("Start get html")
await asyncio.sleep(1) # await後面跟一個awaitable對象/協程對象
print("End get html")
if __name__ == "__main__":
# 建立一個事件循環
loop = asyncio.new_event_loop()
# 也能夠直接使用get_event_loop獲取,一個線程只有一個事件循環,沒有他會自動建立
# loop = get_event_loop()
# 建立一個協程對象
coroutine = get_html("http://xxx.com")
# 將協程對象轉成task對象
task = loop.create_task(coroutine)
# 將task註冊到事件循環
loop.run_until_complete(task)
yield和await
import asyncio
# python3.5引入async和await關鍵字
# python爲了將語義變得更加明確,就引入async和await關鍵詞用於定義原生的協程
async def coroutine(): # async定義一個協程
# 在async關鍵字定義的協程中不能使用yield/yield from
await asyncio.sleep(1) # await用於掛起阻塞的異步調用接口,其做用相似yield/yield from,可是await後面只能跟一個Awaitable對象
if __name__ == '__main__':
cor1 = coroutine()
loop = asyncio.new_event_loop()
task = loop.create_task(cor1)
loop.run_until_complete(task)
asyncio:wait&gather
import asyncio
"""
asyncio.wait()
"""
async def coroutine():
print("Start")
await asyncio.sleep(2)
print("End")
if __name__ == '__main__':
tasks = [
asyncio.ensure_future(coroutine()),
asyncio.ensure_future(coroutine()),
asyncio.ensure_future(coroutine()),
asyncio.ensure_future(coroutine()),
]
loop = asyncio.get_event_loop()
# wait是一個協程,wait接受一個迭代對象,能夠指定結束的時機,經過wait能夠併發執行協程(將多個協程對象放到一個列表中)
# FIRST_COMPLETED、FIRST_EXCEPTION、ALL_COMPLETED:第一次完成、第一次異常、所有完成
loop.run_until_complete(asyncio.wait(tasks))
# gather
import asyncio
async def coroutine():
print("Start")
await asyncio.sleep(1)
print("End")
if __name__ == '__main__':
loop = asyncio.new_event_loop()
task1 = [loop.create_task(coroutine()) for c in range(10)]
task2 = [loop.create_task(coroutine()) for c in range(10)]
g1 = asyncio.gather(*task1)
g2 = asyncio.gather(*task2)
g2.cancel()
loop.run_until_complete(asyncio.gather(g1, g2))
"""
gather對於wait是一個更爲高級的抽象
gather能夠吧任務分組,而且能夠按組取消任務列表
"""
asyncio之task調度過程
import asyncio
async def compute(x, y):
print("Compute %s + %s ..." % (x, y))
await asyncio.sleep(1.0)
return x + y
async def print_sum(x, y):
result = await compute(x, y)
print("%s + %s = %s" % (x, y, result))
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()
數據結構
數據結構/算法 |
語言內置 |
內置庫 |
線性結構 |
list/tuple |
array/collections,namedtuple |
鏈式結構 |
|
collections.deque |
字典結構 |
dict |
collections.Counter/OrderdDict |
集合結構 |
set/frozenset |
|
排序算法 |
sorted |
|
二分算法 |
|
bisect模塊 |
堆算法 |
|
heapq模塊 |
緩存算法 |
|
functools.lru_cache(Least Recent Used,python3) |
列表和元組之間的區別
# 都是線形的數據結構,支持下表訪問
# 列表是可變對象,元組保存的引用不可變
# list無法做爲字典的key,tuple能夠(可變對象不可哈希)
dict底層結構
dict底層使用的哈希表
爲了支持快速查找使用了哈希表做爲底層結構
哈希表平均查找時間複雜度O(1)
CPython解釋器使用二次探查解決哈希衝突問題
在全部的線性數據結構中,數組的定位速度最快,由於它可經過數組下標直接定位到相應的數組空間,就不須要一個個查找,而哈希表就是利用數組這個可以快速定位數據的結構的特性
1.取數據元素的關鍵字key,計算其哈希函數值(地址)。若該地址對應的存儲空間尚未被佔用,則將該元素存入;不然執行step2解決衝突
2.根據選擇的衝突處理方法,計算關鍵字key的下一個存儲地址。若下一個存儲地址仍被佔用,則繼續執行step2,直到找到能用的存儲地址爲止
# 哈希擴容
# 哈希衝突
LRUCache
Least-Recently-Used替換掉最近最少使用的對象
緩存剔除策略,當緩存空間不夠用的時候須要一種方式剔除key
常見的有LRU,LFU等
LRU經過使用一個循環雙端隊列不斷把最新訪問的key放到表頭實現
# 實現LRUCache
from collections import OrderedDict
class LRUCache(object):
def __init__(self, capacity=128):
self.od = OrderedDict()
self.capacity = capacity
def get(self, key):
if key in self.od:
value = self.od[key]
self.od.move_to_end(key)
return value
else:
return None
def put(self, key, value):
if key in self.od:
del self.od[key]
self.od[key] = value
else:
if len(self.od) > self.capacity:
self.od.popitem(last=False)
self.od[key] = value
算法
排序算法
排序算法 |
最差時間分析 |
平均時間複雜度 |
穩定度 |
空間複製度 |
冒泡排序 |
O(n^2) |
O(n^2) |
穩定 |
O(1) |
選擇排序 |
O(n^2) |
O(n^2) |
不穩定 |
O(1) |
插入排序 |
O(n^2) |
O(n^2) |
穩定 |
O(1) |
快速排序 |
O(n^2) |
O(n^log2n) |
不穩定 |
O(log2n)~O(n) |
堆排序 |
O(n^log2n) |
O(n^log2n) |
不穩定 |
O(1) |
什麼是排序算法的穩定性
相同大小的元素在排序以後依然保持對象位置不變,就是穩定的
r[i]=r[j]且r[i]在r[j]以前,排序以後r[i]依然在r[j]以前
穩定性對於排序以惡搞複雜結構,而且須要保持原有排序纔有意義
快速排序
# 快速排序
快排常常問:分治法,快排三步走
Partition:選擇基準分隔數組爲來兩個子數組,小於基準和大於基準的
對兩個子數組分別快排
合併結果
# code 1
def partition(li, left, right):
tmp = li[left]
while left < right:
while left < right and li[right] >= tmp:
right -= 1
li[left] = li[right]
while left < right and li[left] <= tmp:
left += 1
li[right] = li[left]
li[left] = tmp
return left
def quick_sort(li, left, right):
if left < right:
mid =. partition(li, left, right)
quick_sort(li, left, mid-1)
quick_sort(li, mid + 1, right)
# code 2
def quick_sort(array):
# 遞歸出口
if len(array) < 2:
return array
else:
index = 0
tmp = array[index]
less_part = [
i for i in array[index + 1:] if i <= tmp
]
great_part = [
i for i in array[index + 1:] if i > tmp
]
return quick_sort(less_part) + [tmp] + quick_sort(great_part)
歸併排序
# 歸併排序(連個有序的列表)
def marge_sorted_list(sorted_a, sorted_b):
length_a, length_b = len(sorted_a), len(sorted_b)
a = b = 0
new_array = []
while a < length_a and b < length_b:
if sorted_a[a] > sorted_b[b]:
new_array.append(sorted_b[b])
b += 1
else:
new_array.append(sorted_a[a])
a += 1
if a > length_a:
new_array.extend(sorted_a[a:])
else:
new_array.extend(sorted_b[b:])
return new_array
# 歸併排序
def merge_sorted(seq):
if len(seq) <= 1: # 遞歸出口
return seq
else:
mid = int(len(seq) / 2)
left = merge_sorted(seq[:mid])
right = merge_sorted(seq[mid:])
new_seq = merge_sorted_list(left, right)
return new_seq
查詢算法
二分查找
# 二分查找
def binary_search(array, target):
if not array:
return -1
begin = 0
end = len(array) - 1
while begin <= end:
mid = (begin + end) // 2
if array[mid] > target:
end = mid
elif array[mid] < target:
begin += 1
else:
return mid
return -1
數據庫
事務
Transaction
事務數據庫併發控制的基本單位
事務能夠看做是一系列SQL語句的集合
事務必需要麼所有執行成功,要麼所有執行失敗(回滾)
Atomicity(原子性):一個事務中全部操做所有完成或失敗
Consistency(一致性):事務開始和結束以後數據完整性沒有被破壞
Isolation(隔離性):容許多個事務同時對數據庫修改和讀寫
Durability(持久性):一旦事務提交,則其所作的修改就會永久保存到數據庫中
事務的異常和隔離性
# 四種異常狀況:
幻讀(phantom read):一個事務第二次查出第一次沒有的結果
非重複讀(nonrepeatable read):一個事務重複讀兩次獲得不一樣結果
髒讀(dirty read):一個事務讀取到別一個事務沒有提交的修改
丟失修改(lost update):併發寫入形成其中一些修改丟失
# 四種隔離級別
讀未提交(read uncommitted):別的事務能夠讀取到爲提交改變
讀已提交(read committed):只能讀取已經提交的數據
可重複讀(repeatable read):同一個事務前後查詢結果同樣
串行化(serializable):事務徹底串行化的執行,隔離級別最高,執行效率最低
# 解決高併發場景下的插入重複
使用數據庫的惟一索引
使用隊列異步寫入
使用redis等實現分佈式鎖
# 樂觀鎖和悲觀鎖
悲觀鎖是先獲取鎖再進行操做,一鎖二查三更新 select for update
樂觀鎖先修改,更新的時候發現數據已經變了就回滾(check and set)
使須要根據相應速度、衝突頻率、重試代價來判斷使用哪種
數據類型
# 文本
char varchar text tinytext
# 數值
tinyint smallint mediumint int bigint float double decimal
# 日期
date datetime timestamp
存儲引擎
MyISAM不支持事務,InnoDB支持事務
MyISAM不支持外鍵,InnoDB支持外鍵
MyISAM只支持表鎖,InnoDB支持行鎖和表鎖
數據庫索引
索引的原理、類型、結構
建立索引的注意事項、使用原則
如何排查和消除慢查詢
爲何須要索引
索引是數據表中一個或者多個列進行排序的數據結構
索引可以大幅提高檢索速度
建立、更新索引自己也會耗費空間和時間
B-Tree
線性查找:一個個找,實現簡單,太慢
二分查找:有序簡單,要求是有序的插入,特別慢
HASH:查詢快,佔用空間,不太適合存儲大規模數據
二叉查找樹:插入和查詢很快(log(n)),沒法村大規模數據,複雜度退化
平衡樹:解決bst退化的問題,樹是平衡的,節點很是多的時候,依然樹高很高,一個父節點最多有兩個字節點
多路查找樹:一個父親多個孩子節點(度),節點過多樹高不會特別深
# B-Tree
多路平衡查找樹(B-Tree):每一個節點最多m(m>=2)個孩子,稱爲m階或者度
葉子節點具備相同的深度
節點中的數據key從左到右是遞增的
# B+Tree
MySQL時機使用的B+Tree做爲索引的數據結構
只在葉子節點帶有指向記錄的指針(爲何?能夠增長樹的度)
葉子節點經過指針相連,爲何?實現範圍查詢
數據只存放在葉子節點中
普通索引(create index)
惟一索引(create unique index)
聯合索引
主鍵索引(primary key)
全文索引(FULLTEXT INDEX),InnoDB不支持
何時建立索引
常常用作查詢條件的字段(where字段)
常常用作錶鏈接的字段
常常出如今 order by,group by 以後的字段
索引注意點
非空字段 NOT NULL,mysql很難對空值作查詢優化
區分度高、離散度大,做爲索引的字段值儘可能不要有大量相同值
索引的長度不要太長(比較耗費時間)
索引失效
模糊匹配、類型隱轉、最左匹配
以%開頭的LIKE語句,模糊搜索
出現隱式轉換(在Python這種動態語言查詢中須要注意)
沒有知足最左前綴原則(最左匹配)
彙集索引和非彙集索引
彙集仍是非彙集指的是B+Tree葉節點存的是指針仍是數據記錄
MyISAM索引和數據分離,使用的是非彙集索引
InnoDb數據文件就是索引文件,主鍵索引就是彙集索引
彙集索引和輔助索引
輔助索引先找到主鍵之後在根據主鍵找到數據
慢查詢
開啓滿查詢日誌
slow_query_log_file 開啓而且查詢滿查詢日誌
explain排查索引問題
調整數據修改索引,業務代碼層閒置不合理訪問
mysql分庫分表
分佈式鎖
Redis
# 使用場景
緩存系統
計數器
消息隊列系統
排行榜
社交網絡
實時系統
布隆過濾器
# 數據類型(Redis設計與實現)
字符串類型:用來實現簡單的KV簡直對存儲,好比計數器
String:整數或者sds(simple dynamic string)
哈希類型:
ziplist或者hashtable
列表類型:實現雙向鏈表,用戶的關注、粉絲列表
list:ziplist或者double linked list
集合類型:存儲不重複元素,不如用戶的關注着
intset或者hashtable
有序集合類型:實時信息排行榜
skiplist
# 數據一致性問題、緩存穿透、擊穿、雪崩問題
Redis特性
緩解關係型數據庫(Mysql)併發訪問的壓力:熱點數據
減小相應時間:內存IO速度遠比磁盤快
提高吞吐量:Redis單機支持很高的併發
10w OPS # 每秒10w次讀寫
數據存放在內存
使用C語言編寫
單線程
Redis持久化RDB
持久化方式:
快照
mysql dump
redis rdb
寫日誌
mysql binlog
hbase hlog
redis aof
# RDB(他也是一個複製媒介)
從內存建立一個rdb文件(二進制)到硬盤
啓動redis載入硬盤上rdb文件(二進制)
觸發機制
save(同步)
文件策略:如存在老的RDB文件,新的RDB文件替換老的RDB文件
複雜度:O(n)
bgsave(異步)
執行bgsave時,redis會fork一個子進程來建立RDB文件,不會阻塞其餘的命令執行
文件策略:如存在老的RDB文件,新的RDB文件替換老的RDB文件
複雜度:O(n)
自動
在配置文件中配置執行條件
save 900 1 # 900秒 1次操做觸發
save 300 10 # 300秒 10次操做出發
save 60 10000 # 60秒 1萬次操做出發
dbfilename dump.rdb
dir ./
stop-writes-on-bgsave-error yes # 當bgsave出現錯誤,是否中止寫入
rdbcompression yes # rdb文件是否採用壓縮格式
rdbchecksum yes # rdb校驗和
# 觸發機制
1.全量複製
2.debug reload
3.shutdown
rdb總結
rdb是redis內存到硬盤的快照,用於持久化
save一般會阻塞redis
bgsave不會阻塞redis,可是會fork新進程
save自動配置知足任一個條件就會被執行
有些觸發機制不容忽視
Redis持久化AOF
rdb有什麼問題
耗時、耗性能
O(N)數據耗時
fork():消耗內存,copy-on-write
disk I/O:IO性能
不可控、丟失數據
# AOF(append only file)只追加文件,記錄服務器執行的全部寫操做命令,並在服務器啓動時,經過從新執行這些命令來還原數據集。 AOF 文件中的命令所有以 Redis 協議的格式來保存,新命令會被追加到文件的末尾
三種策略
always
寫命令刷新到緩衝區,每條命令fsync到硬盤
不丟失數據
IO開銷較大,通常的sata盤只有幾百TPS
everysec
寫命令刷新到緩衝區,每秒把緩衝區fsync到硬盤
每秒一次fsync
丟一秒數據
no
寫命令刷新到緩衝區,os決定fsync
AOF重寫做用
減小硬盤佔用量
加速恢復速度
AOF重寫實現兩種方式
bgrewriteaof
向redis服務端發一條命令,執行bgrewriteaof,redis會fork一個子進程
AOF重寫配置
auto-aof-rewrite-min-size # AOF文件重寫須要的尺寸
auto-aof-rewrite-percentage # AOF文件增加率
# 統計
aof_current_size # AOF當前尺寸(單位:字節)
aof_base_size # AOF上次啓動和重寫的尺寸
自動觸發時機(同時知足)
aof_current_size > auto-aof-rewrite-min-size
(aof_current_size - aof_base_size)/aof_base_size > auto-aof-rewrite-percentage
配置
appendonly yes
appendfilename appendonly.aof
appendfsync everysec
dir ./
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
Redis事務
和mysql的事務有什麼不一樣?
將多個請求打包,一次性,按順序執行多個命令的機制
redis事務提交的命令中間不可插入
# python redis-py 實現事務
import redis
conn = redis.Redis()
with conn.pipeline(True) as pipe:
pipe.multi() # 開始事務
pipe.set("Hello", "World")
pipe.lpush("x", 1)
pipe.execute() # 執行
Redis實現分佈式鎖
# 基於redis單線程的特性和setnx
# setnx當key存在什麼都不操做並返回0,key不存在是設置value並返回1
# 經過expire添加超時時間防止死鎖問題
# 鎖的value值能夠使用一個隨機的uuid或者特定的命名
# 釋放鎖的時候,經過uuid判斷是不是該鎖,實則執行delete
緩存模式
Cache Aside:同時更新緩存和數據庫
Read/Write Through:先更新緩存,緩存負責同步更新數據庫
Write Behind Caching:先更新緩存,緩存按期異步更新數據庫
# 先更新數據庫,在更新緩存
# 先刪除緩存,在更新數據庫
# 先更新數據庫,在刪除緩存
緩存問題
緩存穿透
大量查詢不到的數據的請求落到後段數據庫,數據庫壓力增大
# 因爲大量緩存查不到就去數據庫取,數據庫也沒有要查的數據
# 解決:對於沒有查到的返回爲None的數據也緩存
# 插入數據的時候刪除相應緩存,或者設置較短的超時時間
緩存擊穿
某些很是熱點的數據key過時,大量請求打到後段數據庫
# 熱點數據key失效致使大量請求達到數據庫增長數據庫壓力
# 分佈式鎖:獲取鎖的線程從數據庫拉數據更新混存,其餘線程等待
# 異步後臺更新:後臺任務針對過時的key自動刷新
# 緩存雪崩
胡納村不可用或者大量緩存key同時失效,大量請求直接打到數據庫
多級緩存:不一樣級別的key設置不一樣的超時時間
隨機超時:key的超時時間隨機設置,方式同時超時
架構層:提高系統可用性,監控、報警完善