基礎(chǔ)篇
Jupyter Notebook
優(yōu)點
- 整合所有的資源
- 交互性編程體驗
- 零成本重現(xiàn)結(jié)果
實踐站點
- Jupyter 官方
- Google Research 提供的 Colab 環(huán)境
- 安裝
- 運行
列表與元組
列表和元組,都是 一個可以放置任意數(shù)據(jù)類型的有序集合 。
l = [1, 2, 'hello', 'world'] # 列表中同時含有 int 和 string 類型的元素
l
[1, 2, 'hello', 'world']
tup = ('jason', 22) # 元組中同時含有 int 和 string 類型的元素
tup
('jason', 22)
- 列表是動態(tài)的,長度大小不固定,可以隨意地增加、刪減或者改變元素 (mutable)
- 元組是靜態(tài)的,場地大小固定,無法增加刪除或者改變 (immutable)
- 都支持負(fù)數(shù)索引;
- 都支持切片操作;
- 都可以隨意嵌套;
-
兩者可以通過
list()
和tuple()
函數(shù)相互轉(zhuǎn)換;
列表和元組存儲方式的差異
由于列表是動態(tài)的,所以它需要存儲指針,來指向?qū)?yīng)的元素。增加/刪除的時間復(fù)雜度均為 O(1)。
l = []
l.__sizeof__() // 空列表的存儲空間為 40 字節(jié)
40
l.append(1)
l.__sizeof__()
72 // 加入了元素 1 之后,列表為其分配了可以存儲 4 個元素的空間 (72 - 40)/8 = 4
l.append(2)
l.__sizeof__()
72 // 由于之前分配了空間,所以加入元素 2,列表空間不變
l.append(3)
l.__sizeof__()
72 // 同上
l.append(4)
l.__sizeof__()
72 // 同上
l.append(5)
l.__sizeof__()
104 // 加入元素 5 之后,列表的空間不足,所以又額外分配了可以存儲 4 個元素的空間
使用場景
- 如果存儲的數(shù)據(jù)和數(shù)量不變,那么肯定選用元組更合適
- 如果存儲的數(shù)據(jù)和數(shù)量是可變的,那么則用列表更合適
區(qū)別
- 列表是動態(tài)的,長度可變,可以隨意的增加、刪除或者改變元素;列表的存儲空間略大于元組,性能略遜于元組;
- 元組是靜態(tài)的,長度大小固定,不可對元素進(jìn)行增加、刪除、修改操作,元組相對于列表更加的輕量級、性能稍優(yōu);
思考題
# 創(chuàng)建空列表
# option A:list()是一個function call,Python的function call會創(chuàng)建stack,并且進(jìn)行一系列參數(shù)檢查的操作,比較expensive
empty_list = list()
# option B:[]是一個內(nèi)置的C函數(shù),可以直接被調(diào)用,因此效率高
empty_list = []
字典與集合
字典是一系列無序元素的組合,其長度大小可變,元素可以任意的刪除和改變,相比于列表和元組,字典的性能更優(yōu),特別是對于查找、添加和刪除操作,字典都能在常數(shù)時間復(fù)雜度內(nèi)完成。而集合和字典基本相同,唯一的區(qū)別,就是集合沒有件和值的配對,是一系列無序的、唯一的元素組合。
# 定義字典
d = {'name': 'jason', 'age': 20}
# 增加元素對'gender': 'male'
d['gender'] = 'male'
# 增加元素對'dob': '1999-02-01'
d['dob'] = '1999-02-01'
d
{'name': 'jason', 'age': 20, 'gender': 'male', 'dob': '1999-02-01'}
# 更新鍵'dob'對應(yīng)的值
d['dob'] = '1998-01-01'
# 刪除鍵為'dob'的元素對
d.pop('dob')
'1998-01-01'
d
{'name': 'jason', 'age': 20, 'gender': 'male'}
# 定義集合
s = {1, 2, 3}
# 增加元素 4 到集合
s.add(4)
s
{1, 2, 3, 4}
# 從集合中刪除元素 4
s.remove(4)
s
{1, 2, 3}
d = {'b': 1, 'a': 2, 'c': 10}
# 根據(jù)字典鍵的升序排序
d_sorted_by_key = sorted(d.items(), key=lambda x: x[0])
# 根據(jù)字典值的升序排序
d_sorted_by_value = sorted(d.items(), key=lambda x: x[1])
可以使用 get(key,default) 函數(shù)來進(jìn)行字典索引。如果鍵不存在,調(diào)用該函數(shù)可以返回一個默認(rèn)的值。
集合不支持索引操作,因為集合本質(zhì)上是一個哈希表,和列表不一樣。
字典和集合性能
字典和集合是進(jìn)行性能高度優(yōu)化的數(shù)據(jù)結(jié)構(gòu),特別是對于查找、添加和刪除操作。
字典和集合的工作原理
字典和集合的內(nèi)部結(jié)構(gòu)都是一張哈希表
- 對于字典而言,這張哈希表存儲了哈希值,鍵和值這三個元素
- 對于集合而言,區(qū)別就是哈希表內(nèi)沒有鍵和值的配對,只有單一的元素了
插入操作
每次向字典或集合插入一個元素時,Python 會首先計算鍵的哈希值(hash(key)),再和 mask = PyDicMinSize - 1 做與操作,計算這個元素應(yīng)該插入哈希表的位置 index = hash(key) & mask。如果哈希表中此位置是空的,那么這個元素就會被插入其中。而如果此位置已被占用,Python 便會比較兩個元素的哈希值和鍵是否相等。
- 若兩者都相等,則表明這個元素已經(jīng)存在,如果值不同,則更新值。
- 若兩者中有一個不相等,這種情況我們通常稱為哈希沖突(hash collision),意思是兩個元素的鍵不相等,但是哈希值相等。這種情況下,Python 便會繼續(xù)尋找表中空余的位置,直到找到位置為止。
查找操作
先通過哈希值找到目標(biāo)位置,然后比較哈希表這個位置中元素的哈希值和鍵,與需要查找的元素是否相等,如果相等,則直接返回,否則繼續(xù)查找,知道為空或拋出異常為止
刪除操作
暫時對這個位置得到元素賦予一個特殊的值,等到重新調(diào)整哈希表的大小時,再將其刪除。
字符串
- Python 中字符串使用單引號、雙引號或三引號表示,三者意義相同,并沒有什么區(qū)別。其中,三引號的字符串通常用在多行字符串的場景。
- Python 中字符串是不可變的(前面所講的新版本 Python 中拼接操作’+='是個例外)。因此,隨意改變字符串中字符的值,是不被允許的。
- Python 新版本(2.5+)中,字符串的拼接變得比以前高效了許多,你可以放心使用。
- Python 中字符串的格式化(string.format,f)常常用在輸出、日志的記錄等場景。
輸入與輸出
輸入輸出基礎(chǔ)
生產(chǎn)環(huán)境中使用強制轉(zhuǎn)換時,請記得加上 try except
文件輸入輸出
所有 I/O 都應(yīng)該進(jìn)行錯誤處理。因為 I/O 操作可能會有各種各樣的情況出現(xiàn),而一個健壯(robust)的程序,需要能應(yīng)對各種情況的發(fā)生,而不應(yīng)該崩潰(故意設(shè)計的情況除外)。
JSON 序列化與實戰(zhàn)
- json.dumps() 函數(shù),接受 python 的基本數(shù)據(jù)類型,然后將其序列化為 string;
- json.loads() 函數(shù),接受一個合法字符串,然后將其序列化為 python 的基本數(shù)據(jù)類型;
條件與循環(huán)
- 在條件語句中,if 可以單獨使用,但是 elif 和 else 必須和 if 同時搭配使用;而 If 條件語句的判斷,除了 boolean 類型外,其他的最好顯示出來。
- 在 for 循環(huán)中,如果需要同時訪問索引和元素,你可以使用 enumerate() 函數(shù)來簡化代碼。
- 寫條件與循環(huán)時,合理利用+continue+或者+break+來避免復(fù)雜的嵌套,是十分重要的。
- 要注意條件與循環(huán)的復(fù)用,簡單功能往往可以用一行直接完成,極大地提高代碼質(zhì)量與效率。
異常處理
- 異常,通常是指程序運行的過程中遇到了錯誤,終止并退出。我們通常使用 try except 語句去處理異常,這樣程序就不會被終止,仍能繼續(xù)執(zhí)行。
- 處理異常時,如果有必須執(zhí)行的語句,比如文件打開后必須關(guān)閉等等,則可以放在 finally block 中。
- 異常處理,通常用在你不確定某段代碼能否成功執(zhí)行,也無法輕易判斷的情況下,比如數(shù)據(jù)庫的連接、讀取等等。正常的 flow-control 邏輯,不要使用異常處理,直接用條件語句解決就可以了。
自定義函數(shù)
- Python 中函數(shù)的參數(shù)可以接受任意的數(shù)據(jù)類型,使用起來需要注意,必要時請在函數(shù)開頭加入數(shù)據(jù)類型的檢查;
- 和其他語言不同,Python 中函數(shù)的參數(shù)可以設(shè)定默認(rèn)值;
- 嵌套函數(shù)的使用,能保證數(shù)據(jù)的隱私性,提高程序運行效率;
- 合理地使用閉包,則可以簡化程序的復(fù)雜度,提高可讀性;
匿名函數(shù)
優(yōu)點:
- 減少代碼的重復(fù)性;
- 模塊化代碼;
map(function,iterable)
表示對 iterable 中的每個元素,都運用 function 這個函數(shù),最后返回一個新的可遍歷的集合。
def square(x):
return x**2
squared = map(square, [1, 2, 3, 4, 5]) # [2, 4, 6, 8, 10]
filter(function,iterable)
表示對 iterable 中的每個元素,都使用 function 判斷,并返回 True 或者 False,最后將返回 True 的元素組成一個新的可遍歷的集合。
l = [1, 2, 3, 4, 5]
new_list = filter(lambda x: x % 2 == 0, l) # [2, 4]
reduce(function,iterable)
規(guī)定它有兩個參數(shù),表示對 iterable 中的每個元素以及上一次調(diào)用后的結(jié)果,運用 function 進(jìn)行計算,所以最后返回的是一個單獨的數(shù)值。
l = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, l) # 1*2*3*4*5 = 120
模塊化編程
- 通過絕對路徑和相對路徑,我們可以 import 模塊;
- 在大型工程中模塊化非常重要,模塊的索引要通過絕對路徑來做,而絕對路徑從程序的根目錄開始;
-
記著巧用
if __name__ == "__main__"
來避開 import 時執(zhí)行;
進(jìn)階篇
Python 對象的比較、拷貝
-
比較操作符
==
表示比較對象間的值是否相等,而is
表示比較對象的標(biāo)識是否相等,即它們是否指向同一個內(nèi)存地址。 -
比較操作符
is
效率優(yōu)于==
,因為is
操作符無法被重載,執(zhí)行is
操作只是簡單的獲取對象的 ID,并進(jìn)行比較;而==
操作符則會遞歸地遍歷對象的所有值,并逐一比較。 - 淺拷貝中的元素,是原對象中子對象的引用,因此,如果原對象中的元素是可變的,改變其也會影響拷貝后的對象,存在一定的副作用。
- 深度拷貝則會遞歸地拷貝原對象中的每一個子對象,因此拷貝后的對象和原對象互不相關(guān)。另外,深度拷貝中會維護(hù)一個字典,記錄已經(jīng)拷貝的對象及其 ID,來提高效率并防止無限遞歸的發(fā)生。
值傳遞與引用傳遞
常見的參數(shù)傳遞有 2 種:
- 值傳遞:通常就是拷貝對象的值,然后傳遞給函數(shù)里的新變量,原變量和新變量之間相互獨立,互不影響
- 引用傳遞:通常是指把參數(shù)的引用傳給新的變量,這樣,原變量和新變量就會指向同一塊內(nèi)存地址。
準(zhǔn)確來說, python 的參數(shù)傳遞是 賦值傳遞 ,或者叫做對象的 引用傳遞 ,python 里所有的數(shù)據(jù)類型都是對象,所以參數(shù)傳遞時,只是讓新變量與原變量指向相同的對象而已,并不存在值傳遞或引用傳遞一說。
需要注意的是,這里的賦值或?qū)ο蟮囊脗鬟f,不是指一個具體的內(nèi)存地址,二十指一個具體的對象。
- 如果對象是可變的,當(dāng)其改變時,所有指向這個對象的變量都會改變;
- 如果對象不可變,簡單的賦值只能改變其中一個變量的值,其余變量則不受影響;
裝飾器
函數(shù)也是對象
def func(message):
print('Got a message: {}'.format(message))
send_message = func
send_message('hello world')
# 輸出
Got a message: hello world
函數(shù)可以作為函數(shù)參數(shù)
def get_message(message):
return 'Got a message: ' + message
def root_call(func, message):
print(func(message))
root_call(get_message, 'hello world')
# 輸出
Got a message: hello world
函數(shù)可以嵌套函數(shù)
def func(message):
def get_message(message):
print('Got a message: {}'.format(message))
return get_message(message)
func('hello world')
# 輸出
Got a message: hello world
函數(shù)的返回值也可以是函數(shù)對象(閉包)
def func_closure():
def get_message(message):
print('Got a message: {}'.format(message))
return get_message
send_message = func_closure()
send_message('hello world')
# 輸出
Got a message: hello world
簡單使用裝飾器
def my_decorator(func):
def wrapper():
print('wrapper of decorator')
func()
return wrapper
def greet():
print('hello world')
greet = my_decorator(greet)
greet()
# 輸出
wrapper of decorator
hello world
更優(yōu)雅的寫法
def my_decorator(func):
def wrapper():
print('wrapper of decorator')
func()
return wrapper
@my_decorator
def greet():
print('hello world')
greet()
帶參數(shù)的裝飾器
def my_decorator(func):
def wrapper(message):
print('wrapper of decorator')
func(message)
return wrapper
@my_decorator
def greet(message):
print(message)
greet('hello world')
# 輸出
wrapper of decorator
hello world
帶自定義參數(shù)的裝飾器
def repeat(num):
def my_decorator(func):
def wrapper(*args, **kwargs):
for i in range(num):
print('wrapper of decorator')
func(*args, **kwargs)
return wrapper
return my_decorator
@repeat(4)
def greet(message):
print(message)
greet('hello world')
# 輸出:
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
上述 green() 函數(shù)被裝飾以后,它的元信息會發(fā)生改變,可勇敢
greet__name__
來查看。可通過內(nèi)置裝飾器來解決這個問題
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('wrapper of decorator')
func(*args, **kwargs)
return wrapper
@my_decorator
def greet(message):
print(message)
greet.__name__
# 輸出
'greet'
類裝飾器
class Count:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print('num of calls is: {}'.format(self.num_calls))
return self.func(*args, **kwargs)
@Count
def example():
print("hello world")
example()
# 輸出
num of calls is: 1
hello world
example()
# 輸出
num of calls is: 2
hello world
裝飾器支持嵌套使用
@decorator1
@decorator2
@decorator3
def func():
...
# 等價于
decorator1(decorator2(decorator3(func)))
裝飾器使用場景:
- 身份認(rèn)證
- 日志記錄
- 輸入合理性檢查
- 緩存(LRU cache)
metaclass
metaclass 是 Python 黑魔法級別的語言特性,它可以改變正常 Python 類型的創(chuàng)建過程。
- 所有 Python 的用戶定義類,都是 type 這個類的實例
- 用戶自定義類,只不過是 type 類的 __ call __ 運算符重載
- metaclass 是 type 的子類,通過替換 type 的 __ call __ 運算符重載機制,超越變形正常的類
class Mymeta(type):
def __init__(self, name, bases, dic):
super().__init__(name, bases, dic)
print('===>Mymeta.__init__')
print(self.__name__)
print(dic)
print(self.yaml_tag)
def __new__(cls, *args, **kwargs):
print('===>Mymeta.__new__')
print(cls.__name__)
return type.__new__(cls, *args, **kwargs)
def __call__(cls, *args, **kwargs):
print('===>Mymeta.__call__')
obj = cls.__new__(cls)
cls.__init__(cls, *args, **kwargs)
return obj
class Foo(metaclass=Mymeta):
yaml_tag = '!Foo'
def __init__(self, name):
print('Foo.__init__')
self.name = name
def __new__(cls, *args, **kwargs):
print('Foo.__new__')
return object.__new__(cls)
foo = Foo('foo')
迭代器和生成器
- 容器時可迭代對象,可迭代對象調(diào)用 iter() 函數(shù),可以得到一個迭代器。迭代器可以通過 next() 函數(shù)來得到下一個元素,從而支持遍歷
- 生成器時一種特殊的迭代器,合理使用生成器,可以降低內(nèi)存占用、優(yōu)化程序結(jié)構(gòu)、提高程序速度
- 生成器在 Python 2 的版本上,是協(xié)程的一種重要實現(xiàn)方式;而 Python 3.5 引入的 async、await 語法糖,生成器實現(xiàn)協(xié)程的方式就已經(jīng)落后了。
協(xié)程
協(xié)程是實現(xiàn)并發(fā)編程的一種方式
- 協(xié)程和多線程的區(qū)別,主要在于兩點,一是協(xié)程為單線程;二是協(xié)程由用戶決定,在哪些地方交出控制權(quán),切換到下一個任務(wù)
- 協(xié)程的寫法更加簡潔清晰;把 async/await 語法和 create_task 結(jié)合起來用,對于中小級別的并發(fā)需求已經(jīng)毫無壓力
生產(chǎn)者/消費者 模型
import asyncio
import random
async def consumer(queue, id):
while True:
val = await queue.get()
print('{} get a val: {}'.format(id, val))
await asyncio.sleep(1)
async def producer(queue, id):
for i in range(5):
val = random.randint(1, 10)
await queue.put(val)
print('{} put a val: {}'.format(id, val))
await asyncio.sleep(1)
async def main():
queue = asyncio.Queue()
consumer_1 = asyncio.create_task(consumer(queue, 'consumer_1'))
consumer_2 = asyncio.create_task(consumer(queue, 'consumer_2'))
producer_1 = asyncio.create_task(producer(queue, 'producer_1'))
producer_2 = asyncio.create_task(producer(queue, 'producer_2'))
await asyncio.sleep(10)
consumer_1.cancel()
consumer_2.cancel()
await asyncio.gather(consumer_1, consumer_2, producer_1, producer_2, return_exceptions=True)
%time asyncio.run(main())
########## 輸出 ##########
producer_1 put a val: 5
producer_2 put a val: 3
consumer_1 get a val: 5
consumer_2 get a val: 3
producer_1 put a val: 1
producer_2 put a val: 3
consumer_2 get a val: 1
consumer_1 get a val: 3
producer_1 put a val: 6
producer_2 put a val: 10
consumer_1 get a val: 6
consumer_2 get a val: 10
producer_1 put a val: 4
producer_2 put a val: 5
consumer_2 get a val: 4
consumer_1 get a val: 5
producer_1 put a val: 2
producer_2 put a val: 8
consumer_1 get a val: 2
consumer_2 get a val: 8
Wall time: 10 s
并發(fā)編程之 Futures
區(qū)別并發(fā)和并行
- 并發(fā)通常應(yīng)用與 I/O 操作頻繁的場景,比如你要從網(wǎng)站上下載多個文件, I/O 操作的時間可能比 CPU 運行處理的時間長得多,通過線程和任務(wù)之間互相切換的方式實現(xiàn),但同一時刻,只允許有一個線程或任務(wù)執(zhí)行
- 并行更多應(yīng)用于 CPU heavy 的場景,比如 MapReduce 中的并行計算,為了加快運算速度,一般會用多臺機器,多個處理器來完成。可以讓多個進(jìn)程完全同步同時的執(zhí)行
Python 中之所以同一時刻只運行一個線程運行,其實是由于全局解釋鎖的存在。但對 I/O 操作而言,當(dāng)其被 block 的時候,全局解釋器鎖便會被釋放,使氣體線程繼續(xù)執(zhí)行。
import concurrent.futures
import requests
import threading
import time
def download_one(url):
resp = requests.get(url)
print('Read {} from {}'.format(len(resp.content), url))
# 版本 1
def download_all(sites):
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
executor.map(download_one, sites)
# 版本 2
def download_all(sites):
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
to_do = []
for site in sites:
future = executor.submit(download_one, site)
to_do.append(future)
for future in concurrent.futures.as_completed(to_do):
future.result()
def main():
sites = [
'https://en.wikipedia.org/wiki/Portal:Arts',
'https://en.wikipedia.org/wiki/Portal:History',
'https://en.wikipedia.org/wiki/Portal:Society',
'https://en.wikipedia.org/wiki/Portal:Biography',
'https://en.wikipedia.org/wiki/Portal:Mathematics',
'https://en.wikipedia.org/wiki/Portal:Technology',
'https://en.wikipedia.org/wiki/Portal:Geography',
'https://en.wikipedia.org/wiki/Portal:Science',
'https://en.wikipedia.org/wiki/Computer_science',
'https://en.wikipedia.org/wiki/Python_(programming_language)',
'https://en.wikipedia.org/wiki/Java_(programming_language)',
'https://en.wikipedia.org/wiki/PHP',
'https://en.wikipedia.org/wiki/Node.js',
'https://en.wikipedia.org/wiki/The_C_Programming_Language',
'https://en.wikipedia.org/wiki/Go_(programming_language)'
]
start_time = time.perf_counter()
download_all(sites)
end_time = time.perf_counter()
print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))
if __name__ == '__main__':
main()
## 輸出
Read 151021 from https://en.wikipedia.org/wiki/Portal:Mathematics
Read 129886 from https://en.wikipedia.org/wiki/Portal:Arts
Read 107637 from https://en.wikipedia.org/wiki/Portal:Biography
Read 224118 from https://en.wikipedia.org/wiki/Portal:Society
Read 184343 from https://en.wikipedia.org/wiki/Portal:History
Read 167923 from https://en.wikipedia.org/wiki/Portal:Geography
Read 157811 from https://en.wikipedia.org/wiki/Portal:Technology
Read 91533 from https://en.wikipedia.org/wiki/Portal:Science
Read 321352 from https://en.wikipedia.org/wiki/Computer_science
Read 391905 from https://en.wikipedia.org/wiki/Python_(programming_language)
Read 180298 from https://en.wikipedia.org/wiki/Node.js
Read 56765 from https://en.wikipedia.org/wiki/The_C_Programming_Language
Read 468461 from https://en.wikipedia.org/wiki/PHP
Read 321417 from https://en.wikipedia.org/wiki/Java_(programming_language)
Read 324039 from https://en.wikipedia.org/wiki/Go_(programming_language)
Download 15 sites in 0.19936635800002023 seconds
并發(fā)編程之 Asyncio
import asyncio
import aiohttp
import time
async def download_one(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print('Read {} from {}'.format(resp.content_length, url))
async def download_all(sites):
tasks = [asyncio.create_task(download_one(site)) for site in sites]
await asyncio.gather(*tasks)
def main():
sites = [
'https://en.wikipedia.org/wiki/Portal:Arts',
'https://en.wikipedia.org/wiki/Portal:History',
'https://en.wikipedia.org/wiki/Portal:Society',
'https://en.wikipedia.org/wiki/Portal:Biography',
'https://en.wikipedia.org/wiki/Portal:Mathematics',
'https://en.wikipedia.org/wiki/Portal:Technology',
'https://en.wikipedia.org/wiki/Portal:Geography',
'https://en.wikipedia.org/wiki/Portal:Science',
'https://en.wikipedia.org/wiki/Computer_science',
'https://en.wikipedia.org/wiki/Python_(programming_language)',
'https://en.wikipedia.org/wiki/Java_(programming_language)',
'https://en.wikipedia.org/wiki/PHP',
'https://en.wikipedia.org/wiki/Node.js',
'https://en.wikipedia.org/wiki/The_C_Programming_Language',
'https://en.wikipedia.org/wiki/Go_(programming_language)'
]
start_time = time.perf_counter()
asyncio.run(download_all(sites))
end_time = time.perf_counter()
print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))
if __name__ == '__main__':
main()
## 輸出
Read 63153 from https://en.wikipedia.org/wiki/Java_(programming_language)
Read 31461 from https://en.wikipedia.org/wiki/Portal:Society
Read 23965 from https://en.wikipedia.org/wiki/Portal:Biography
Read 36312 from https://en.wikipedia.org/wiki/Portal:History
Read 25203 from https://en.wikipedia.org/wiki/Portal:Arts
Read 15160 from https://en.wikipedia.org/wiki/The_C_Programming_Language
Read 28749 from https://en.wikipedia.org/wiki/Portal:Mathematics
Read 29587 from https://en.wikipedia.org/wiki/Portal:Technology
Read 79318 from https://en.wikipedia.org/wiki/PHP
Read 30298 from https://en.wikipedia.org/wiki/Portal:Geography
Read 73914 from https://en.wikipedia.org/wiki/Python_(programming_language)
Read 62218 from https://en.wikipedia.org/wiki/Go_(programming_language)
Read 22318 from https://en.wikipedia.org/wiki/Portal:Science
Read 36800 from https://en.wikipedia.org/wiki/Node.js
Read 67028 from https://en.wikipedia.org/wiki/Computer_science
Download 15 sites in 0.062144195078872144 seconds
Asyncio 是單線程的,但其內(nèi)部 event loop 的機制,可以讓它并發(fā)地運行多個不同的任務(wù),并且比多線程享有更大的自主控制權(quán)。
Asyncio 中的任務(wù), 在運行過程中不會被打斷,因此不會出現(xiàn) race condition 的情況。尤其是在 I/O 操作 heavy 的場景下, Asyncio 比多線程的運行效率更高,因此 Asyncio 內(nèi)部任務(wù)切換的損耗,遠(yuǎn)比線程切換的損耗要小,并且 Asyncio 可以開啟的任務(wù)數(shù)量,也比多線程中的線程數(shù)列多得多。
但需要注意的是,很多情況下,使用 Asyncio 需要特定第三方庫的支持,而如果 I/O 操作很快,并不 heavy ,建議使用多線程來解決問題。
GIL(全局解釋器鎖)
CPython 引進(jìn) GIL 主要是由于:
- 設(shè)計者為了規(guī)避類似于內(nèi)存管理這樣的復(fù)雜的競爭風(fēng)險問題
- 因為 CPython 大量使用 C 語言庫,但大部分 C 語言庫都不是原生線程安全的(線程安全會降低性能和增加復(fù)雜度)
GIL 的設(shè)計,主要是為了方便 CPython 解釋器層面的編寫者,而不是 Python 應(yīng)用層面的程序員。
可以使用
import dis
的方式將代碼編譯成 bytecode
垃圾回收機制
- 垃圾回收是 Python 自帶的機制,用于自動釋放不會再用到的內(nèi)存空間
- 引用計數(shù)是其最簡單的實現(xiàn),不過切記,這只是充分非必要條件,因為循環(huán)引用需要通過不可達(dá)判定,來確定是否可以回收
- Python 的自動回收算法包括標(biāo)記清除和分代收集,主要針對的是循環(huán)引用的垃圾收集
- 調(diào)試內(nèi)存泄漏方便,objgraph 是很好的可視化分析工具
編程規(guī)范
閱讀者的體驗 > 編程者的體驗 > 機器的體驗
- 學(xué)會合理分解代碼,提高代碼可讀性
- 合理利用 assert(線上環(huán)境禁止使用)
- 啟用上下文管理器和 with 語句精簡代碼
- 單元測試
- pdf & cprofile
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
