python之旅

目錄前端

第一章 計算機基礎

1. 計算機概覽

1.1 計算機硬件

計算機的主要組成部分時主板、CPU、硬盤、內存及一些外設設備組成。java

1.2 常見的操做系統

​ 操做系統(OS),是最接近物理硬件的系統軟件。主要用來協調、控制、分配計算機硬件資源,使計算機各組件能夠發揮最優性能。python

  • windows
    • Win7,win vista,win10,win server(用於服務器)
  • linux(開源)
    • CentOs(免費,開源多用於中小企業)
    • RedHat(商業版,收費)
  • macos
    • 主要用於辦公

1.3 軟件(解釋器/編譯器)

​ 軟件是運行於操做系統之上的應用程序。mysql

​ 計算機語言主要是有解釋器/編譯器/虛擬機,再加上語法規則構成用來開發其餘軟件的工具。解釋型語言,一般是由解釋器邊解釋邊執行的計算機語言,例如:python、ruby、PHP、perl。編譯型語言,一般是由編譯器編譯整個代碼文件,生成計算機能夠識別的文件,交由計算機處理。linux

1.4 進制

  • 二進制. 0b
  • 八進制 0o(計算機內部使用)
  • 十進制 int(str, base=2/8/16)
  • 十六進制(表示). \x (通常用於計算機顯示)

第二章 python入門

2.1 環境的安裝

​ 首先,在官網下載py2與py3,2,3版本有不少不兼容全部會存在兩個版本共存的問題。目前,mac、ubuntu等一些系統已經內置了python2版本。git

​ 爲了開發須要,咱們須要下載並安裝python3。用於開發的軟件,一般安裝版本通常是找次新版本進行安裝。安裝pycharm IDE 開發工具。程序員

​ 環境變量的功能,及其配置。環境變量分爲,用戶環境變量(只對當前用戶生效)和系統環境變量(全部用戶均有效)。主要用於終端使用,不須要輸入python解釋器的完整路徑,只要輸入pythonx 就可以使用。web

PATH:方便用戶在終端執行程序。即,將可執行程序所在的目錄添加到環境變量,之後直接調用便可。正則表達式

​ mac的環境變量在~/.bash_profile文件中。一般在安裝的時候python會自動生成環境變量,無需手動配置。redis

2.2 編碼

​ 當前,比較通用的編碼有ASCII、Unicode、UTF-八、GB23十二、GBK。因爲計算機最初使用的是ASCII編碼,因此其餘編碼必須兼容ASCII碼。

  1. ASCII

    ASCII碼,7bits表示一個字符,最高位用0表示,後來IBM進行擴展,造成8bit擴展的ASCII碼。包含全部英文字母和經常使用的字符,最多能夠表示127或256種。

  2. Unicode

    Unicode(萬國碼),隨着計算機的普及,計算機須要兼容多國語言,Unicode編碼應運而生。32bits表示一個字符,總共能夠表示2**32種不一樣的符號,遠遠超出目前全部文字及字符,迄今使用21bits。通用的特色換來的是存儲空間的浪費,通常只用於計算機內部處理計算。

  3. utf-8

    爲彌補unicode的不足,utf-8針對unicode進行壓縮和優化,去掉前面多餘的0,只保留有效部分。完整保留ASCII碼,歐洲文字通常用2bytes表示,中文通常用3bytes表示。

  4. GBK

    全稱是GB2312-80《信息交換用漢字編碼字符集 基本集》,1980年發佈,是中文信息處理的國家標準,在大陸及海外使用簡體中文的地區(如新加坡等)是強制使用的惟一中文編碼。

    中文使用2bytes表示。GBK,是對GB2312的擴展,又稱GBK大字符集,簡而言之就是全部亞洲文字的雙字節字符。

# IDE:統一使用UTF-8, 全局和項目均如此

2.3 變量

​ 變量的主要做用是爲了屢次重複使用方便。

# 查看python關鍵字
import keyword
keyword.kwlist

常量:不容許修改的值,python中只是約定

2.4 python基礎語法

1. 多行顯示

Python語句中通常以新行做爲語句的結束符。

可是咱們可使用斜槓( )將一行的語句分爲多行顯示,以下所示:

total = item_one + \
        item_two + \
        item_three

2. 實現換行

input("按下 enter 鍵退出,其餘任意鍵顯示...\n")

# 不換行輸出
print(x, end='')
print(y, end='')

3. 多個變量賦值

a = b = c = 1
a, b, c = 1, 2, "john"

4. 成員運算符

# 若是在指定的序列中找到值返回 True,不然返回 False
in
# 若是在指定的序列中找到值返回 False,不然返回 True
not in

5. 身份運算符

is  
# is 是判斷兩個標識符是否是引用自一個對象 x is y 
相似 id(x) == id(y) , 若是引用的是同一個對象則返回 True,不然返回 False
is not  
# is not 是判斷兩個標識符是否是引用自不一樣對象 x is not y 
相似 id(a) != id(b)。若是引用的不是同一個對象則返回結果 True,不然返回 False。

6. 不換行輸出

  • print 默認輸出是換行的,若是要實現不換行須要在變量末尾加上 end=""

7. pyc 文件

​ 執行Python代碼時,若是導入了其餘的 .py 文件,那麼,執行過程當中會自動生成一個與其同名的 .pyc 文件,該文件就是Python解釋器編譯以後產生的字節碼

字節碼(Bytecode)是一種包含執行程序、由一序列 op 代碼/數據對 組成的二進制文件字節碼是一種中間碼。它比機器碼更抽象,須要直譯器轉譯後才能成爲機器碼的中間代碼。

機器碼(machine code),學名機器語言指令,有時也被稱爲原生碼(Native Code),是電腦的CPU可直接解讀的數據機器碼是電腦CPU直接讀取運行的機器指令。

ps:代碼通過編譯能夠產生字節碼;字節碼經過反編譯也能夠獲得代碼。

2.5 py2與py3的區別

  1. 字符串類型不一樣
v = u'henry'
print(v, type(v))  # unicode類型
# py2                  py3 數據類型對應關係
unicode<class>    <--> str
eg.u'alex'        <--> 'alex'
str               <--> bytes
eg.'alex'         <--> b'alex

Note

  • bytes類型通常用於文件存儲和網絡傳輸
  1. 其餘不一樣
Py2 Py3
1 字符串類型不一樣
2 py2py3默認解釋器編碼 ASCII UTF-8
3 輸入輸出 raw_input() ; print input() ; print()
4 int / long int 和 long,除法只保留整數 只用int,除法保留小數
5 range/xrange range/xrange 只有range,至關於py2的xrange
6 info.keys,info.values,info .items 數據類型是list 數據類型是<class 'dict_keys'>
7 map/filter 數據類型是list 返回的是iterator,能夠list()查看<map object at 0x108bfc668>
8 reduce 內置 移到functools
9 模塊和包 須要__init__.py
10 經典類和新式類 同時擁有 只有新式類

第三章 數據類型

3.1 int

# None 無操做
# bytes 類
# 只有str 能夠強轉
s = '99'
print(type(int(s)))

3.2 bool

# bool 布爾,主要用於條件判斷。 
# None, 0 , '', [], {}, set() 
# 以上都是False

3.3 str " "

1. 經常使用操做

str操做經常使用的 14 (9+5) 種
1.upper/lower 2.isdigit/isdecimal 3.strip 4.replace 5.split 6.startswith/endswith 7.encode 8.format 9.join

1. s.upper() / s.lower()

# 大小寫轉換
s = 'Henry'
print(s.upper(), s.lower())

2. s.isdigit() / s.isdecimal()

# 判斷是不是數字
s1 = '123'
s2 = '12.3'
print(s1.isdigit(), s2.isdigit())       # True Flase

# isdecimal只判斷是不是整數

3. s.strip()

# 默認去除兩邊空格+ \n + \t

s = '  asdfgh,      '
print('-->', s.strip(), '<--')
print('-->', s.strip().strip(','), '<--')

4. s.replace('a', 'b', n)

# repalce 中的 a 和 b 必須是str類型, n 必須是int類型
s = 'adsafga'
print(s.replace('a', '666'))
print(s.replace('a', '666', 1))

5. s.split('_')

# str的分割
s = 'henry_echo_elaine_'
li = s.split('_')
print(li)  # 分割後的元素永遠比分隔符號多一個

6. s.startswith() / s.endswith()

# 判斷開始/結束位置是不是指定元素
s = 'abghjkdc'
print(s.startswith('ab'), s.endswith('cd'))
# True  Flase

7. str 的格式化輸出(2種)

# 若是使用格式化輸入,打印%須要使用 %%
# %s,%d  :表示佔位符

# way1 一般用於函數
"***{0}***{1}**".format(a, b)
# % (a, )  :這裏表示tuple,建議加逗號 

# way2 
"***%s, ***%s***" % (a, b,)
# 示例
# way1 '{0} ***{1}'.format(a, b)
a = 'abcd'
b = '666'
s = '{0} ***{1}'.format(a, b)
print(s)
# way2 
s1 = '%s***%s' % (a, b,)
print(s1)

擴展使用示例:

# %s ,只能是tuple
msg = '我是%s, 年齡%s' % ('alex', 19)
msg = '我是%(name)s, 年齡%(age)s' % {'name': 'alex', 'age': 19}
# format格式化
v1 = '我是{name}, 年齡{age}'.format(name = 'alex', age = 18)
v1 = '我是{name}, 年齡{age}'.format(** {'name': 'alex', 'age': 19})
v2 = '我是{0}, 年齡{1}'.format('alex', 18) 
v2 = '我是{0}, 年齡{1}'.format(*('alex', 18) )

8. encode

# 指定編碼類型
s = '你好'
print(s.encode('utf-8'))   # 6個字節
print( s.encode('gbk'))      # 4個字節

9. '_'.join(s)

# 用於循環加入指定字符
# s 必須是iterable
# s 但是str,list,tuple,dict,set(str + 容器類)
# s 中元素值必須是str類型
'_'.join(s)
# 示例
# 指定元素循環鏈接str中的元素
s = 'henry'
print('_'.join(s))    # 下劃線鏈接個字符

2. 其餘操做

  1. s.find('a') / s.rfind()
    - 返回第一個 a 的索引值,沒有則返回 -1
  2. s.index('a') / s.rindex() /s.lindex()
    - 返回第一個 a 的索引值,沒有報錯
  3. s.isupper() / s.islower()
  4. s.capitalize()
  5. s.casefold()
  6. s.center(20, "*")
    • 能夠爲空
  7. s.ljust(20, "*")/s.rjust()
  8. s.zfill()
    • 用0填充
  9. s.count('a', [start], [end])
    • 查找'a' 的個數
  10. s.isalnum()
  11. s.isalpha()
  12. s.isnumeric()
  13. s.isprintable()
  14. s.istitle()
  15. s.partition('a') / s.rpartition()
    • 分紅三部分,a左邊,a右邊
  16. s.swapcase()


3. 公共方法

1. len(s)

# 返回s長度
s = '1234567890'
print(len(s))     # 10

2. s[index]

# 索引取值
s = '123456789'
print(s[3])   # 4

3. 切片

s = '123456789'
print(s[3:5])   # 45

4. setp

# 根據step進行切片
s = '123456789'
print(s[3::2])   # 468

5. for循環

s = '123456789'
for i in s:
    print(i)

3.4 list []

1. 經常使用操做

list操做目前一共有15(8+7)種, 1.append 2.insert 3.remove 4.pop 5.clear 6.reverse 7.sort 8.extend

1. li.append('666')

# 任意類型數據,li操做不能直接放在print()中
li = [1, 2, 3, 4, 5, 6]
li.append('666')
print(li)
li.append(['henry'])
print(li)

2. li.insert(2, 'henry')

# 按照index位置插入指定內容
li = [1, 2, 3, 4, 5, 6]
li.insert(3, 'henry')
print(li)

3. li.remove('aa')

# 刪除指定list中的元素
li = ['aa', 'a', 'aacde']
li.remove('aa')
print(li)
li.remove('bb')
print(li)  # 會報錯

4. li.pop(index)

# 按index刪除list中的元素
li = [1, 2, 3, 4, 5, 6]
li.pop()
print(li)
li.pop(3)
print(li)

5. li.clear()

# 清空list中的全部元素
li = [1, 2, 3, 4, 5, 6]
li.clear()
print(li)

6. li.reverse()

# 反轉list中的元素
li = [1, 2, 3, 4, 5, 6]
li.reverse()
print(li)

7. li.sort(reveres = True)

# reverse = True 從大到小
# 只能是同一類型的元素
# dict,tuple不支持排序
li = [6, 2, 3, 1, 5, 4]
li.sort()
print(li)
li = ['ba', 'ab', 'c', 'd']
li.sort(reverse=True)
print(li)

li = [[6], [2, 3], [1, 5, 4]]
li.sort()
print(li)

li = [(6, 2, 3, 1, 5, 4)]
li.sort()
print(li)

8. li.extend(s)

# 把s中的元素,循環取出,逐個追加到list中
# s能夠是str, list, tuple, dict, set
# dict只取keys 追加到list中
s = 'henry'
li = [1, 2, 3, 4, 5, 6]
li.extend(s)
print(li)
s = ['a', 'b', 'c', 'd']
li.extend(s)
print(li)

2. 其餘

  1. li.count('a')
  2. li.copy()
    - 淺拷貝
  3. li.count()
    - 只計算第一層,不考慮嵌套
  4. li.index('val')


3. 公共方法

1. len(s)

li = [1, 2, 3, 4, 5, 6]
print(len(li))

2. index 取值

li = [1, 2, 3, 4, 5, 6]
print(li[2])

3. 切片

li = [1, 2, 3, 4, 5, 6]
print(li[2:5])

4. step

li = [1, 2, 3, 4, 5, 6]
print(li[2::2])

5. for循環

li = [1, 2, 3, 4, 5, 6]
for i in li:
  print(i)

6. 修改

# 使用index修改,若是隻是一個值,則正常修改
li = [1, 2, 3, 4, 5, 6]
li[2] = 'henry'
print(li)
# 使用index修改,若是是多個值,認爲是一個tuple
li[2] = 'a', 'b', 'c'
print(li)
# 使用切片[]修改,則循環取值加入
li[2:3] = 'a', 'b', 'c'
print(li)

7. 刪除del li[]

# del 也不能放在print()裏面
li = [1, 2, 3, 4, 5, 6]
del li[2]
print(li)
del li[2:]
print(li

3.5 tuple ()

# 沒有獨有操做,目前只有5種
# tuple裏,最後一個值最好加一個 逗號 ,以區別於運算符

1. 經常使用操做

1. len()

t = (1, 2, 3,)
print(len(t))

2. index

t = (1, 2, 3,)
print(t[2])

3. 切片

t = (1, 2, 3,)
print(t[1:])

4. step

t = (1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
print(t[1::2])

5. for 循環

t = (1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
for i int t:
  print(i)

2. 內置函數

  • max(tup). # 返回最大值
  • min(tup). # 返回最小值
  • tuple(li). # list 轉tuple

3.6 dict {}

1. 經常使用操做

dict 目前一共有 14(9 + 5) 種操做, 1.keys 2.values 3.items 4.get 5.pop 6.update 7.setdefault 8.popitem 9.clear
# {key1: value1, k2:value2}
# key不能重複

1. info/ info.keys()

  • 相似 list ,但不是list,例如:dict_keys(['name', 'age', 'gender'])
# 取全部key
info = {1: 'henry', 2: 'echo', 3: 'eliane'}
for key i info:
  print(key)

2. Info.values()

# 取全部value
info = {1: 'henry', 2: 'echo', 3: 'eliane'}
for v in info.values():
    print(v)

3. info.items()

# 取全部key值對
# 取出的是 tuple 類型
info = {1: 'henry', 2: 'echo', 3: 'eliane'}
for pair in info.items():
    print(pair)

4. Info.get(key, 666)

# 有key則取出, 沒有則返回指定 值
# 若是沒有指定值,則返回 None
info = {1: 'henry', 2: 'echo', 3: 'eliane'}
print(info.get(1, 666))
print(info.get(4, 666))

5. info.pop(key)

info = {1: 'henry', 2: 'echo', 3: 'eliane'}
print(info.pop(1))
print(info.pop(4))

6. info.update(info1)

# 只能用dict類型更新
info = {}
info1 = {1: 'henry', 2: 'echo', 3: 'eliane'}
info.update(info1)
print(info)

7. info.setdefalut(key, value)

# 查詢key,有則取出,沒有則添加
info = {1: 'henry', 2: 'echo', 3: 'eliane'}
info.setdefault(4, 'hello')
print(info)
# 取出須要賦值給其餘變量
val = info.setdefault(4, 'i hate you')
print(val)

8. info.popitem()

# 不能加參數,刪除最後一個key值對
info = {1: 'henry', 2: 'echo', 3: 'eliane'}
v = info.popitem()
print(v,info)   # v是tuple

9. info.clear()

# 清空全部元素
info = {1: 'henry', 2: 'echo', 3: 'eliane'}
info.clear()
print(info)

2. 其餘

  1. info.copy() # 淺拷貝
  2. info.fromkeys()
li = [1, 2, 3, 4, 5]
info = {'a': 1, 'b': 2}
v = info.fromkeys(li, 'hello')
print(v, info)


3. 公共方法

1. len(info)

info = {1: 'henry', 2: 'echo', 3: 'eliane'}
print(len(info))

2. Index 取值

info = {1: 'henry', 2: 'echo', 3: 'eliane'}
print(info[1])

3. for 循環

info = {1: 'henry', 2: 'echo', 3: 'eliane'}
for i in info:
  print(i)
for v in info.values():
  print(v)
for pair in info.items():
  print(pair)

4. 修改

# key同樣則修改,不同則追加
info = {1: 'henry', 2: 'echo', 3: 'eliane'}
info[1] = 'hello'
print(info)
info[4] = 'you are smart'
print(info)

5. 刪除

info = {1: 'henry', 2: 'echo', 3: 'eliane'}
del info[1]
print(info)

4. 有序字典

# __getitem__ set, del 
from collections import OrderdDict

info = OrderedDict()
info['k1'] = 123
info['k2'] = 456

3.7 set() /{}

1. 經常使用操做

set 目前一共有11(10 + 2)種操做,空集合用set()表示。 
1.add 2.update 3.pop 4.discard 5.remove 6. clear 7.intersection 8.union 9.difference 10.symmetric_difference
# 無序,不重複

1. s.add('a')

s = {1, 'henry', 2, 'echo', 3, 'eliane'}
s.add(5)
print(s)

2. s.update(s1)

# 能夠用str , list, tuple, dict, set, 也能夠混合多種類型放入update中
s = {1, 'henry', 2, 'echo', 3, 'eliane'}
s1 = {5, 6, 7, 8, 1, 2, 3}
s2 = [5, 6, 7, 8, 1, 2, 3]
s3 = (5, 6, 7, 8, 1, 2, 3)
s4 = {5: 6, 7: 8, 1: 2}
s.update(s1)
print(s)

s = {1, 'henry', 2, 'echo', 3, 'eliane'}
s.update(s2)
print(s)

s = {1, 'henry', 2, 'echo', 3, 'eliane'}
s.update(s3)
print(s)

s = {1, 'henry', 2, 'echo', 3, 'eliane'}
s.update(s4)
print(s)

3. s.pop()

# 隨機刪除,此時pop中不能有任何參數
s = {1, 'henry', 2, 'echo', 3, 'eliane'}
s.pop()  # 默認刪除第一個元素/隨機
print(s)

4. s.discard()

# 必須有一個參數,沒有不報錯, 不會返回值
s = {1, 'henry', 2, 'echo', 3, 'eliane'}
val = s.discard(3)
print(s)
print(val)

5. s.remove('a')

# 必須有一個參數,沒有會報錯
s = {1, 'henry', 2, 'echo', 3, 'eliane'}
s.remove(3)
print(s)

6. s.clear()

s = {1, 'henry', 2, 'echo', 3, 'eliane'}
s.clear()
print(s)

7. s.intersection(s1)

# 取v1 和v2 的交集
v1 = {1, 'henry', 2, 'echo', 3, 'eliane'}
v2 = {1, 3, 5, 7}
v = v1.intersection(v2)
print(v)

8. v.union(v1)

# 取並集
v1 = {1, 'henry', 2, 'echo', 3, 'eliane'}
v2 = {1, 3, 5, 7}
v = v1.union(v2)
print(v)

9. v.difference(v1)

v1 = {1, 'henry', 2, 'echo', 3, 'eliane'}
v2 = {1, 3, 5, 7}
v = v1.difference(v2)
print(v)

10. v.symmetric_difference(v1)

v1 = {1, 'henry', 2, 'echo', 3, 'eliane'}
v2 = {1, 3, 5, 7}
v = v1.symmetric_difference(v2)
print(v)
# 集合的運算
    方法
    運算法

2. 其餘

  1. v.copy()
  2. v.difference_update(v2)
  3. v.symmetric_difference_update(v2)
  4. v.intersection.update(v2)
  5. v.isdisjoint(v2) # 返回 bool值
  6. v.issubset(v2)/ v.issuperset(v2)


3. 公共方法

1. len(v)

v = {1, 'henry', 2, 'echo', 3, 'eliane'}
print(len(v))

2. for 循環

# 無序輸出
v = {1, 'henry', 2, 'echo', 3, 'eliane'}
for i in v:
    print(i)

3.8 公共方法

int bool str list tuple dict set
len
index
切片
step
for循環/ iterable
修改
刪除

3.9 內存相關&深淺拷貝

0. 可嵌套的數據類型

  • 全部的容器類例如:list,tuple, dict,set 均可以嵌套,但set(), 只能嵌套可hash(int, bool, str, tuple 4種)的數據類型。

1. 內存相關

小數據池

  • ==判斷值
  • is判斷內存地址
  • python中默認會對int,str,bool進行緩存

緩存規則:

  • int型-5 — 256之間會被緩存
  • str:空和單個字符默認緩存;只包含字母、數字、下劃線,也緩存; 乘數>1時,str只包含Num、ALp、_時緩存(最終長度不能超過20)
  • str:手動指定緩存
  • bool

2. 深淺拷貝

  1. 使用格式import copy(模塊)
  2. 4 個結論
    • 淺拷貝只是拷貝第一層可變類型
    • 深拷貝拷貝全部可變類型的數據
    • 只要 copy ,必定會爲數據開闢新的內存空間
    • tuple 淺copy地址同樣, 有嵌套的可變類型,deepcopy也會拷貝tuple數據

第四章 文件操做

​ 文件操做主要用來讀取、修改、和建立指定文件。

4.1 文件基本操做

1. 文件打開格式

f = open('文件路徑',mode='r/w/a...',encoding='utf-8')

2. 文件寫入格式

  • file.write(str)將字符串寫入文件,返回的是寫入的字符長度
# mode= 'w'
# 打開文件時,會先清空歷史文件,沒有則建立
f.write('a')

3. 文件讀取格式

# mode= 'r'
# way1 整個文件直接讀取到RAM
f.read()
# 若是指定編碼格式,會讀出 1 個字符
# 若是是 mode= rb 會讀出 1 個字節
f.read(1) 
                    

# way2 按行讀取文件
# 通常用於for循環中,可用來讀取 GB 級別的文件
f.readline() 只讀取一行
f.readlines()  # 一次性加載全部內容到內存,並根據行分割成字符串
# 讀取一行時也可使用
for line in v:
    line = line.strip('\n')

4. 文件的關閉

# 當對文件操做完成後必須關閉,不然不會存儲到本地磁盤
f.colse()
# 刷新緩衝區裏任何還沒寫入的信息

4.2 打開模式

1. mode 分類

​ mode常見的有r/w/a(只讀/寫/追加),r+/w+/a+(讀寫/寫讀/追加讀),rb/wb/ab(以二進制方式進行讀/寫/追加),r+b/w+b/a+b

模式 r r+ w w+ a a+
+ + + +
+ + + + +
建立 + + + +
覆蓋 + +
指針在開始 + + + +
指針在結尾 + +

4.3 其餘操做

​ 斷點續傳,經過終端與服務器之間的交互,找到文件斷點位置,從而實現文件的單次傳輸。此種操做可使用file.seek(n)實現,n表示字節數。

1. 文件定位

seek(offset [,from])方法改變當前文件的位置。Offset變量表示要移動的字節數。From變量指定開始移動字節的參考位置。若是from被設爲0,這意味着將文件的開頭做爲移動字節的參考位置。若是設爲1,則使用當前的位置做爲參考位置。若是它被設爲2,那麼該文件的末尾將做爲參考位置。

  • file.seek(n)
    • 光標移動到指定字節位置。注意
  • file.tell()
    • 返回當前光標位置。能夠用於斷點續傳技術。

2. 重命名和刪除文件

  • rename()方法須要兩個參數,當前的文件名和新文件名。
# 示例
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import os
# 重命名文件test1.txt到test2.txt。
os.rename( "test1.txt", "test2.txt" )
  • 你能夠用remove()方法刪除文件,須要提供要刪除的文件名做爲參數。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import os
# 刪除一個已經存在的文件test2.txt
os.remove("test2.txt")

3. f.flush()

  • 強制把內存中的數據,刷到硬盤中。主要用於操做文件時間過長,沒法自動保存的問題。
v = open('a.txt',mode='a',encoding='utf-8')
while True:
    val = input('請輸入:')
    v.write(val)
        # 強制把內存中的數據,刷到硬盤中
     v.flush()                        
v.close()

4.4 一次性操做文件

v = open('a.txt', mode='a', encoding='utf-8')
v1 = v.read()
v.close()
# 縮進中的代碼執行完畢後,自動關閉文件
with open('a.txt', mode='a', encoding='utf-8') as v:
    data = v.read()

4.5 文件修改(示例)

Note:

  1. 文件在存儲的時候,是連續存儲的。文件較大時能夠按行讀取,修改。
  2. 按行讀取時,必定要注意一行結束位置有 \n
# 示例1 
# 文件的修改,須要先把內容讀到內存,修改後再存儲
with open('a.txt', mode='r', encoding='utf-8') as v:
    data = v.read()
print(data)

new_data = data.replace('a', 666)
with open('a.txt', mode='w', encoding='utf-8') as v:
    data = v.wirte(new_data)
# 示例2  修改指定字符
# 大文件的修改
f1 = open('a.txt', mode='r', encoding='utf-8')
f2 = open('b.txt', mode='r', encoding='utf-8')
for line in f1:
    line = line.replace('a', 'b')
    f2.write(line)
f1.close
f2.close
# 一次性打開修改和關閉
with open('a.txt', mode='r', encoding='utf-8') as f1,open('b.txt', mode='r', encoding='utf-8') as f2:
    for line in f1.readlines():
# 或者以下寫法
    for line in f1:   # 這種寫法,也會一行行讀取,包括 \n 也會單獨一行讀出
    line = line.replace('a', 'b')
    f2.write(line)

第五章 函數

​ 以雙下劃線開頭的 **__foo** 表明類的私有成員,以雙下劃線開頭和結尾的 **__foo__** 表明 Python 裏特殊方法專用的標識,如 init() 表明類的構造函數。

5.1 三元運算

又稱爲三目運算

和預算符相關

val = v if v else 666
val = v or 666 # 源碼中會見到

Note:爲了賦值

# 簡單條件賦值時使用
v = 前面 if 條件 else 後面
# 用戶輸入,若是是整數,則轉換,不然賦值爲None
data = input('>>>')
value = int(data) if data.isdecimal() else Non

5.2 函數基礎

面向過程【可讀性差、可重用性差】—> 函數式編程—>面向對象

# 若是給其餘人發送郵件,能夠把發送程序進行封裝起來,可縮減代碼長度和提升重複利用性。

函數式編程

  • 將n行代碼放在別處,並取別名,之後能夠調用
  • 場景:
    • 代碼重複執行
    • 代碼量特別多,超過一屏,能夠選擇函數編程(通常控制在一屏之內

1. 定義函數

能夠定義一個由本身想要功能的函數,如下是簡單的規則:(5)

  • 函數代碼塊以 def 關鍵詞開頭,後接函數標識符名稱和圓括號()
  • 任何傳入參數和自變量必須放在圓括號中間。圓括號之間能夠用於定義參數。
  • 函數的第一行語句能夠選擇性地使用文檔字符串—用於存放函數說明。
  • 函數內容以冒號起始而且縮進
  • return [表達式] 結束函數,選擇性地返回一個值給調用方。不帶表達式的return至關於返回 None。
def functionname( parameters ):   
  "函數_文檔字符串"
  function_suite  # 函數體  
    return [expression]
# 默認狀況下,參數值和參數名稱是按函數聲明中定義的順序匹配起來的。
# 函數定義
# way1
def 函數名():
    # 函數體
    pass
# 函數的執行
函數名()    # 會自動執行

# way2 
# 形參能夠是多個
def 函數名(形參):
    # 函數體
    pass
函數名(實參)

Note1(1)

  • None方法相似函數,但不是(方法操做是s.upper(),方式,而函數是直接調用,len(),open())

2. 兩種參數(示例)

形參(形式參數)與實參(實際參數)的位置關係。

def 函數名(形參):
    # 函數體
    pass
函數名(實參)
# 無形參示例
def get_sum_list()
    sum = 0
  for i in li:
    sum += i
print(get_sum_list())
# 有形參示例
# 請寫一個函數,函數計算列表 info = [11,22,33,44,55] 中全部元素的和。
info = [11, 22, 33, 44]
def get_list_sum(li):
    sum = 0
    for i in li:
        sum += i
    print(sum)
get_list_sum(info)

3. 函數的返回值

Note2(4)

  1. return [表達式] 結束函數,選擇性地返回一個值給調用方(func()爲返回值)。
  2. return 1, 2 ,3 會返回tuple:(1, 2, 3)
  3. 函數默認返回值是 None ,有時可使用其做爲flag
  4. 可變類型(list)的基本上都是None, 不可變(str)基本上會返回新值
def func(arg):
    return 9 # 返回值爲9,默認爲None,能夠返回任何類型的數據
val = def func(v)
# 示例2
# 讓用戶輸入一段字符串,計算字符串中有多少A,就在文件中寫入多少‘echo’
def get_char_count(arg):
    count = 0
    for i in arg:
        count += 1

def write_file(data):
    open('a.txt', mode='w', encoding='utf-8') as v:
        if len(data) == 0:或者 if not bool(data):
            return '寫入失敗'
                v.write(data)
        return '寫入成功'
    
print(count)
content = input()

4. 函數的四種方式

# way1 無形參,無return
def fun1():
    pass
fun()
# way2 有形參,無return
def fun2(arg):
    pass
fun2(v)
# way3 無形參,有return(指定值)
def fun3():
    pass
    return 9
val = fun3(v)
# way4 有形參,有return(變量)
def fun4(arg1, arg2):
    pass
    return arg1 + arg2
val = fun4(v1 + v2)

5. 練習(3)

# 1. 寫函數,計算一個list中有多少個數字,打印,有%s個數字
# 判斷數字:type(a) == int
# 2. 寫函數,計算一個列表中偶數索引位置的數據構形成另一個列表,並返回。
# 3. 讀取文件,將文件的內容構形成指定格式的數據,並返回。
a.log文件
    alex|123|18
    eric|uiuf|19
    ...
目標結構:
a. ["alex|123|18","eric|uiuf|19"] 並返回。
b. [['alex','123','18'],['eric','uiuf','19']]
c. [
    {'name':'alex','pwd':'123','age':'18'},
    {'name':'eric','pwd':'uiuf','age':'19'},
]

5.3 變量做用域&嵌套

1. 函數傳參方式(2+1)

​ 參數傳遞方式分爲位置傳參、關鍵字傳參、函數做爲參數進行傳遞

  • 位置傳參
    • 嚴格按照位置進行傳參
# 示例
def func(a1, a2):
  pass

func(1, 3)
func(1, [1, 2, 3])
  • 關鍵字傳參
# 示例
def func(a1, a2):
    pass

func(a1 = 1, a2 = [1, 2, 3])

func(a1 = 1, 2 )     # 此時會報順序錯誤

Note3(1)

  1. 關鍵字傳參能夠和位置傳參混合使用,但 位置參數 必須在 關鍵字傳參

2. 函數定義參數(3)

​ 函數定義中,def func() 括號中能夠省略默認參數和 *args/**kwargs。

  1. 省略參數
# 示例
def func():
    pass
  1. 默認參數
    • 注意默認參數必定要在位置參數以後
    • 在定義默認參數時,慎用可變類型變量
# 示例 
def func(a1, a2=2):
    pass
# 調用方法,有默認參數時,能夠不用省略,採起默認值,也能夠從新賦值
func(1)
func(1, 3)
# 默認形參時,若是默認是可變類型的須要謹慎使用
# 若是想要給values設置默認是空list
def func(data, value=[]):
  pass
# 推薦
def func(data, value=None):
  if not value:    
    valu=[]
  1. *args/**kwargs(萬能參數)
  • *args 表示接收全部由實參經過位置傳參方式,傳遞過來的數據,能夠和位置參數一塊兒使用。
    • 接收到的參數值,會經過循環加入tuple
# 示例 : 能夠傳遞任意類型數據
def func(*args):
    pass

# [1, 2, 3]會被當成總體變成tuple中的一個元素
func(1, 2, 3, [1, 2 ,3])
# 直接賦值, [1, 2, 3]也會循環取出追加到tuple中
func(4, 5 ,6 ,*[1, 2 ,3])
  • **kwargs 表示接收全部關鍵字傳參的數據,也能夠經過**{'k1': 1, 'k2':2},循環取出keys對,加入形參dict中
# 示例 :只能經過關鍵字傳參,或者dict賦值
def func(**kwargs):
    print(kwargs)

# [1, 2, 3]會被當成總體變成dict中 'd': [1, 2, 3]
func(a=1, b=2, c=3, d=[1, 2 ,3])
# 直接賦值, {'k1': 4, 'k2': 5}也會循環取出追加到形參的dict中
func(a=1, b=2, c=3, d=[1, 2, 3], **{'k1': 4, 'k2': 5})

Note4(4)

  1. 形參中的默認參數,也可使用位置傳參方式
  2. 傳參時,有*(**)時,會直接賦值 (循環加入) 給形參
  3. 不帶* / **的實參,會轉換爲tuple / dict
  4. 傳入的數據都是循環加入tuple / dict中

3. 做用域&函數嵌套

​ 變量做用域時是變量的有效做用範圍,在python中函數就是一個局部做用域。因爲做用域的不一樣,變量的有效範圍也不一樣,根據做用範圍能夠把變量分爲,全局變量和局部變量。

全局變量:可供任何函數進行使用,修改,在python文件第一層的變量。在python中通常把全局變量命名爲所有大寫(規範),例如:USRE_NAME = 'henry'。

局部變量:能夠把函數中的變量視爲局部變量。函數體中變量爲函數所私有(只能被其子函數進行使用)。

# 示例1
a   = 'henry'
def func():
  print(a)
a = 123
func()                                                # 此時使用的是全局變量, 結果是 123

# 示例2
a = 'henry'
def func1():
    def func2():
            a = 'echo'
        print(a)
    func2()
    print(a)

a = 123
func1()
print(a)
# echo 123 123

Note5(4)

  1. python文件就是一個全局做用域
  2. 函數是一個 (局部) 做用域
  3. 局部做用域中的數據歸本身私有
  4. 做用域中查找數據規則
    • 優先查找本身做用域,本身沒有,去父籍做用域查找直到找到全局做用域
    • 查找不到會報錯,默認只能使用父籍做用域中的變量值不能賦值(可變類型能夠修改
# 示例
# 對於可變變量能夠進行修改
a = [1, 3, 5, 7]
def fun1():
    a.append('henry')
fun1()
print(a)                                                 # 此時a會被修改
# 兩種賦值的方法
# 可使用 global 關鍵字對全局變量進行從新賦值
global name
name = 'alex'  # 給全局變量從新賦值

# 可使用 nolocal 關鍵字對父籍變量進行從新賦值, 在父籍找不到時,會報錯
nonlocal name 
name = 'alex'  # 給父籍變量從新賦值

Note6(3)

  1. 對於可變變量能夠進行修改
  2. global 關鍵字對全局變量進行從新賦值
  3. nolocal 關鍵字對父籍變量進行從新賦值, 在父籍找不到會報錯

5.4 函數進階

  • 高階函數(3)
    1. 對函數賦值
    2. 函數看成參數傳遞
    3. 把函數看成返回值
# <class 'function'>
def func ():
  pass
print(type(func))
# 函數能夠認爲是一變量

1. 函數賦值

def func():
  print(123)
v = fun # 指向相同的地址
v()
# 示例
def func():
  print(123)
v1 = [func, func, func]
v2 = [func(), func(), func()]
print(v1)
print(v2)

2. 函數傳參

def func(arg):
  print(arg)
def show():
  return 999

func(show)   # 999 None

3. 函數做爲返回值

def func():
  print(1,2,3)
def bar():
  return func
v = bar()  # func
v()

Note7(3)

  1. 注意funcfunc() 的區別
  2. 函數(實際是內存地址)能夠放入set()中(不經常使用), 或dict中(通常用於values,也能夠放在key中但不經常使用)
  3. 函數一旦定義,只要進行加載,就是不可變,可 hash
# 10 個函數, 通常是創建字典
def func():
  print('話費查詢')
def bar():
  print('***')
def base():
  print('***')

info = {
    'f1': func,
    'f2': bar,
    'f3': base
  }
choice = input('please input your choice: ')
name = info.get('choice')
 if not name:
    print('輸入不存在')
 else:
        name()

3. lambda表達式

# 三目運算,爲了解決簡單的if...esle的狀況
# lambda,爲了解決簡單函數的狀況
eg:
def func(a1, a2):
  return a1 + a2
# 能夠改寫爲,a1 + 100 即爲return 值
func = lambda a1, a2: a1 + 100
# way1 直接使用
func = lambda : 100
func = lambda a: a*10
func = lambda *args, **kwargs: len(args) + len(kwargs)
# way2 使用全局變量
DATA = 100
func = lambda a: a + DATA
func(1)
# way3 使用父籍變量
DATA = 100
def func():
  DATA = 1000
  func1 = lambda a: a + DATA
  v = func1(1)
  print(v)
func()
# way4 使用條件判斷 ########
func = lambda n1, n2: n1 if n1 > n2 else n2
# 練習1
USER_LIST = []
func1 = lambda x: USER_LIST.append(x)
v1 = func1('alex')
print(v1)                                    # None
print(USER_LIST) # ['alex']

# 練習2
func1 = lambda x: x.strip()
v1 = func1('   alex')
print(v1)                                   # 'alex'

# 練習3
func_list = [lambda x: x.strip(), lambda y: y+100, lambda x,y: x+y]
v1 = func_list[0]('   alex')
print(v1)                                    # 'alex'

Note8(2)

  1. 用於表示簡單函數(一行解決的函數)。
  2. lambda 表達式會默認返回冒號:以後的值

4. 內置函數(30)

  • 自定義函數
  • 內置函數(31)
  1. 強制轉換(7):int(),str, bool, list,dict,tuple,set
  2. 輸入輸出(2):print, input
  3. 其餘(5):type, id, range, open, len
  4. 數學(7)
    • abs,round,float(int(55.5)保留整數部分)
    • max,min,sum,
    • divmod(兩數相除,得商和餘數, 兩個值)
  5. 面向對象相關(4):dir,super,issubclass,isinstance
# divmod. 練習
USER_LIST = []
for i in range(1, 836):
  tem = {name':'hello-%s' % i, 'email':'XXXX%s@qq.com' %s}
  USER_LIST.append(tem)
"""
    要求:
        每頁展現10條
        根據用戶輸入的頁碼,查看
"""
  1. 進制轉換(3):bin(0b,int<—>bin),oct(0o,int<—>oct),int(其餘進制轉int),hex(0x,int<—>hex)
# base 默認爲 10
v1 = '0b1101'
result = int(v1, base = 2)
# 轉8進制
v1 = '0o1101'
result = int(v1, base = 8)
# 轉16進制
v1 = '0x1101'
result = int(v1, base = 16)
# ip 點分二進制,將十進制轉爲二進制並經過 . 鏈接ip = '192.168.12.79'
  ip = '192.168.12.79'
  li = ip.split('.')
  l = []
  for i in li:
      i = int(i)
      i = bin(i)
      i = str(i).replace('0b', '')
      i = i.rjust(8, '0')
      l.append(i)
  s = '.'.join(l)
  print(s)
  1. 編碼相關
  • chr() :把int型數據,轉換爲unicode編碼

    • ord():把unicode轉換爲字符
    # 生成驗證碼
    import random # 導入一個模塊
    def get_random_data(length=6):
      data = []
      for i in range(length):
          v = random.randint(65,90) # 獲得一個隨機數
          data.append(v)
      return ' '.join(data)
    
    code = get_random_data()
    print(code)
  1. map / filter / reduce(py2)/zip

    • map,循環每一個元素(第二個參數),而後讓元素執行函數(第一個參數),將每一個函數結果保存到新的list中,並返回。(批量修改數據)
    # map操做的 func 是一個函數 v 必須是可迭代,
    v = [11, 22, 33]
    def func(arg):
      return arg + 100 
    result = map(func, v) # 將函數的返回值添加到空list中[111, 122, 133]
    print(list(result))
    # 使用lambda 改寫
    result = map(lambda x: x+100, v)
    print(result)                               # py2直接返回
    print(list(resutl))                              # py3會返回一個object,可用list()查看
    • filter
    v = [1, 2, 3, 'welcome', 4, 'hello']
    result = filter(lambda x: type(x) == int, v) # 生成新list
    print(list(result)
    • reduce
    import functools
    v = [1, 2, 3, 4]
    result = functools.reduce(lambda x,y: x*y, v)
    print(result)
    • zip
    a = [1,2,3]
    b = [4,5,6]
    c = [4,5,6,7,8]
    zipped = zip(a,b)     # 打包爲元組的列表
    [(1, 4), (2, 5), (3, 6)]
    zip(a,c) 
    
    # 與 zip 相反,*zipped 可理解爲解壓,返回二維矩陣式
    [(1, 2, 3), (4, 5, 6)]
    # 兩組序列,轉字典
    list1 = ['key1','key2','key3']
    list2 = ['1','2','3']
    info = dict(zip(list1,list2))
    print(info)

5.5 函數閉包

1. 函數閉包

def func(name):
  def inner():
    print(name)
  return inner

v1 = func('henry')
v1()
v2 = func('echo')
v2()
# 不是閉包
def func(name):
  def inner():
    return 123
  return inner

# 閉包:封裝值 + 內層函數須要使用
def func(name):
  def inner():
    print(name)
    return 123
  return inner

Note9(5)

  1. 閉包,爲函數建立一塊區域(內部變量供本身使用),爲之後執行提供數據;
  2. 函數內部數據不會混亂
  3. 執行完畢+內部數據不被其餘程序使用會被銷燬
  4. 應用:裝飾器,SQLAlchemy源碼
  5. 由函數及其相關的引用環境組合而成的實體(即:閉包=函數+引用環境)

2. 遞歸(效率較低)

  • 遞歸限制爲1000次
def func(i):
  print(i)
  func(i+1)
# 斐波那契數列
def func(a, b):
  print(b)
  func(a, a+b)
# 遞歸
def fun(a):
  if a == 5:
    return 100
  result = func(a+1) + 10
  return result
v = func(1)

# 注意
def fun(a):
    if a == 5:
            return 100
    result = func(a+1) + 10

v = func(1)

5.6 裝飾器和推導式

  • 使用**func.__name__**獲取被調用的func函數名,func爲形參

1. 裝飾器(重點)

def func():
  def inner():
    pass 
    return inner

v = func()
print(v)   # inner 函數
# ##############################
def func(arg):
  def inner():
    print(arg) 
    return inner

v1 = func(1)   # 1
v2 = func(2)   # 2
# ##############################
def func(arg):
  def inner():
    arg()
    return inner

def f1():
  print(123)
  
v = fucn(f1)
v()     # 123
# ##############################
def func(arg):
    def inner():
        arg()
return inner
def f1():
    print(123)
return 666
v1 = func(f1)
result = v1() 
# 執行inner函數 / f1含函數 -> 123 print(result)    
# None

# ##############################
def func(arg):
    def inner():
        return arg()
return inner
def f1():
    print(123)
return 666
v1 = func(f1)
result = v1()   # 123 666
  • 裝飾器示例
def func(arg):
  def inner():
        print('before')
    v = arg()
    print('after')
    return v
  return inner

def index():
  print('123')
  return 666

# 示例
v1 = index()   # 123

v2 = func(index)  # before 123 after
v3 = v2()

v4 = func(index)   # before 123 after
index = v4
index()

index = func(index)  # before 123 after
index()
# 第一步,執行func函數,並將下面的函數看成函數傳遞,至關於func(index)
# 第二部,將func返回值,從新賦值爲下面的函數名,index = func(index)
def func(arg):
    def inner():
        return arg()
    return inner

@func
def index():
    print(123)
    return 666

print(index)  # <function func.<locals>.inner at 0x1054a16a8>

應用示例

# 計算函數執行時間
def wrapper(func):
    def inner():
        start_time = time.time()
                func()
                end_time = time.time()
                print(end_time - start_time)
        return func()
      return inner

import time
 @ warpper   
def func():
    time.sleep(2)
    print(123)
  
 @ warpper   
def func():
    time.sleep(1.5)
    print(123)

# 判斷用戶是否登錄

Note10(2)

  1. 目的:在在不改變原函數的基礎上,在執行函數先後自定義一些操做
  2. 場景:想要爲函數擴展功能時
  • 編寫裝飾器
# 裝飾器的編寫(示例)
def wrapper(func):                        # 必須有一個參數
  def inner():
    ret = func()
    return ret
    return inner
# 應用 index = wrapper(index)
@wapper
def index():
  pass
@wapper
def manage():
  pass 
# 在執行函數,自動觸發裝飾器
v = index()
print(v)
# 導入本目錄下的其餘py文件
import a
a.f1()
a.f2()
a.f3()
  • 編寫格式
def wrapper(func):
    def inner(*args, **kwargs):
                return func(*args, **kwargs)
    return inner

爲何要加*args,**kwargs?

2. 關於參數

# 讓參數統一的目的:爲裝飾的函數傳參
def x(func):
  def inner(a, b):
    return func()
  return inner

@x
def index():
  pass

index(1, 2)
  • 返回值
# 裝飾器建議寫法
def wrapper(function):
    def inner(*args, **kwargs):
            v = funtion(*args, **kwargs)
            return v
    return inner

@wrapper
def func():
        pass
  • 帶參數的裝飾器
# 第一步:v = wrapper(9)
# 第二步:ret = v(index)
# 第三步:index = ret
def x(counter):
    def wrapper(function):
        def inner(*args, **kwargs):
                v = funtion(*args, **kwargs)
                return v
        return inner
     return wrapper

@x(9)
def index():
    pass
# 示例:
# 寫一個帶參數的裝飾器,實現,參數是多少,被裝飾器就要執行多少次,最終返回一個list
def x(*args):
  def wrapper():
    def inner():
      li = [index() for i in range(args[0])]
      return li
    return inner
  return wrapper

@x(9)
def index():
  return 8

v = index()
print(v)
  • 元數據
  • 多個裝飾器:@x1 @x2

3. 推導式

  1. list推導式(生成式)

    • 格式(生成一個list)
    vals = [i for i in 'henry']
    v = [i for i in 可迭代對象 if 條件]  # 知足條件生成list
    v = [i if i > 5 else i+1 for i in 可迭代對象 if 條件]  
    # 知足條件生成list
    # 新浪
    def num():
         return [lambda x: x * i for i in range(4)]
    print([m(2) for m in num()])
  2. set推導式

    • 格式
    # 知足條件生成set,會去重,條件判斷能夠省略
    v = {i for i in 可迭代對象 if 條件}
  3. dict推導式

    • 格式
    # 知足條件生成dict,但須要key值和冒號:,條件判斷能夠省略
    v = { 'k' + str(i): i for i in 可迭代對象 if 條件}

5.7迭代器&生成器

:int ,str, list…. / bytes(b'xxx'), datetime

對象:由建立的數據

類和對象

1. 迭代器(class:iterator)

  • 展現list中全部數據

    1. 迭代器:對某種(str/list/tuple/dict/set) (序列)對象中的元素,進行逐個獲取。表象:具備**__next__()**方法,
    • list —> 迭代器:
      • v = iter([1, 2, 3, 4])
      • val = [1, 2, 3, 4].__iter__()
    • 迭代器獲取每個元素:v.next
    • 直到報錯:StopIteration,表示迭代終止
    v = [1, 2, 3, 4]
    val = iter(v)
    value = val.__next__()
    print(value)
    1. 甄別:數據中是否包含**__next__()**方法
    2. for循環的內部,首先把數據轉化爲iter,反覆執行iter.__next__(),取完不報錯

2. 可迭代對象

  • 具備**__iter__()方法,必須返回一個迭代器(生成器)**
  • for循環

3. 生成器(函數的變異)(class:generator)

  • yiled能夠保存狀態,yield的狀態保存與操做系統的保存線程狀態很像,可是yield是代碼級別控制的,更輕量級
  • send能夠把一個函數的結果傳給另一個函數,以此實現單線程內程序之間的切換
  1. 生成器基礎
def func():
  pass 
func()
# 生成器函數(內部是否包含yield)
def func(arg):
  arg = arg + 1
  yield 1
  yield 2
  yield 100
# 函數內部代碼不執行,返回一個生成器
val = func(100) 

# 生成器:能夠被for循環的,一旦開始循環,函數內部代碼就開始執行
for i in val:
  print(i)
# 遇到第一個yield會把後面的值賦值給 i
# 若是yield已經執行完畢,則意味着for循環結束
# 邊使用邊執行
def func():
  count = 1 
  while True:
    yield count
    count += 1

# v 只取yield值,是一個生成器對象
v = func()
for i in v:
  print(i)
  
# 查看v中有哪些方法
dir(v)

Note11(4)

  1. 函數中若是存在yield,則該函數爲生成器函數,調用生成器函數會返回一個生成器
  2. 只有被for循環時,生成器內部代碼纔會執行,每次循環都會獲取yield的返回值
  3. 即便函數內部的yield函數永遠執行不到,也是生成器
  4. 生成器也是一種特殊的迭代器,也是一個可迭代對象
class Foo(object):
  def __iter__(self):
    return iter([1, 2, 3])
    yield 1 
    yield 2
    
obj = Foo(object)
  1. 生成器中send

Note12(2)

  1. send方法,會觸發一次next操做,yiled結果爲其返回值
  2. 總的來講,send方法和next方法惟一的區別是在執行send方法會首先把上一次掛起的yield語句的返回值經過參數設定,從而實現與生成器方法的交互。
  3. 須要注意,在一個生成器對象沒有執行next方法以前,因爲沒有yield語句被掛起,因此執行send方法會報錯
def func():
    print(123)
    n = yield('aaa')
    print('----->', n)
    yield 'bbb'

data = func()
next(data)
v = data.send('太厲害了,直接傳進去了')
print(v)
  1. 生成器示例
# 示例:讀取文件
def func():
    curse = 0
    while True:
        f = open('db','r','utf-8')
        f.seek(curse)
        data_list = []
        for i in range(10):
                line = f.readline()
            if not line:
                return
            data_list.append(line)
        curse = f.tell()
        f.close
        for row in data_list:
            yield row
# redis 示例
import redis
coon = redis.Redis(host='192.168.12.12')
  1. yield from 關鍵字
# yield from (py3.3以後)
def base():
  yield 88
  yield 99
 
def bar():
  return 123

def func():
  yield 1
  yield from base()
  yield from bar()   # 報錯,int 不可迭代,若是可迭代,則循環取出
  yield 2
  yield
  1. 生成器推導式
v1 = [i for i in range(10)] # list推導式,當即產生數據
def func():
  for i in range(10):
    yield i
v2 = func()  # 與下面v2相同
v2 = (i for i in range(10)) # 生成器推導式,不會當即產生數據
  1. 酸爽生成器
    • 全部生成器取一次就沒有了
    • 不取不會執行,惰性運算
# 示例1
ret = filter(lambda n: n%3==0, range(10))
print(len(list(ret)))                                   # 4
print(len(list(ret)))                      # 0
# 示例2
def add(n, i):
  return n + i

def test():
  for i in range(4):
    yield i
    
g = test()
for n in [1, 10]:
  g = (add(n, i) for i in g)
  
print(list(g))
# [20 21 22 23 24]
# 示例3
def add(n, i):
  return n + i

def test():
  for i in range(4):
    yield i
    
g = test()
for n in [1, 10, 5]:
  g = (add(n, i) for i in g)
  
print(list(g))
# [15, 16, 17, 18]

5.8 異常處理

1. 示例

# 示例1
try:
  val = input('請輸入數字:')
  num = int(val)
except Exception as e:
  print('操做異常')
# 示例2
import requests
try:
  ret = requests.get('http://www.baidu.com')
    print(ret.text)
except Exception as e:
  print('請求異常')
# 示例3
def func(a):
  try:
    return a.strip()
  except Exception as e:
    pass
  return False

v = func([1, 2, 3])
print(v)
# 練習1,函數接收一個list將list中的元素每一個都加100
def func(arg):
    li = []
    for items in arg:
        if items.isdecimal():
            li.append(int(items) + 100)
     return li
# 寫函數,接收一個list, 中全是url 訪問地址,並獲取結果
import requests
def func(url_list):
  li = []
  try:
    for i in url_list:
      reponse = requests.get(i)
      li.append(reponse.text)
  except Exception as e:
    pass
  return li

func(['http://www.baidu.com', 'http://www.google.com', 'http://www.bing.com'])
# 比較異常 try 的位置不一樣,效果也不一樣
import requests
def func(url_list):
  li = []
  for i in url_list:
      try:
          reponse = requests.get(i)
          li.append(reponse.text)
        except Exception as e:
                pass
  return li

func(['http://www.baidu.com', 'http://www.google.com', 'http://www.bing.com'])


# 獲得的結果是text格式的文本文檔
reponse = requests.get('url', useragent:xxxxxx)

2. 基本格式

try:
  pass
except ValueError as e:
  pass
except IndexErro as e:
  pass
except Exception as e:
  print(e)
finally: 
  print('final')  # 不管對錯都要執行的代碼
# e 表明異常信息,是Exception類的對象,有一個錯誤信息
try:
  int('asdf')
except Exception as e:
  print(e)

try:
  int('asdf')
except ValueError as e:
  print(e)
  
try:
  int('asdf')
except IndexError as e:
  print(e)
  
# 即便遇到return 也會執行finally 
def func():
  try:
    int('1')
    return
  except Exception as e:
    print(e)
  finally:
    print('final')

3. 主動觸發異常

try:
  int('123')
  raise Exception('錯誤信息')   # 主動拋出異常
except Exception as e:
  print(1)
# 打開一個文件,
def func():
  resutl = True
  try:
      with open('x.log', mode='r', encoding='utf-8') as f:
            data = f.read()
      if 'henry' not in data:
            raise Exception()
   except Exception as e:
        result = False 
   return result

4. 自定義異常

# 示例1
class MyException(Exception):
  pass
try:
  raise MyException('haha,錯了吧')
except MyException as e:
  print(e)
class MyException(Exception):
  def __init__(self, message):
      self.message = message
try:
  raise MyExceptoin('123')
except MyException as e:
  print(e.message)

第六章 模塊

6.1 模塊的導入

  1. 模塊:能夠是py文件也能夠是文件夾
    • py文件,寫好了的對程序員直接提供某方面功能
    • import / from xxx import xx
    • :存儲了多個py文件的文件夾,pickle,json,urlib
    • 若是導入一個包,包裏默認模塊是不能使用的
      • 導入一個包至關於執行**__init__.py**文件內容
  2. 定義模塊時,能夠把一個py文件或一個包看成一個模塊,以便於之後其餘py文件使用。
  3. **__ init__.py** 在文件夾中建立此py文件, python packages
    • py2:文件夾中必須有__ init__.py
    • py3:不須要,推薦加上
  4. 導入模塊
    1. 導入模塊—>調用模塊中的函數(import 文件名)
    2. import 會把模塊中的文件加載到內存
    3. **from py文件名 import func,show… (*)**:只導入指定函數,也會把模塊中的內容加載一遍
      • 模塊中的函數名可能和本地函數重名
      • from 模塊 import func as f(模塊中的函數重命名) f()
# test爲文件夾,在當前工做目錄中,jd爲py文件,f1爲jd中的函數
import test.jd
test.jd.f1()
# test爲文件夾,在當前工做目錄中,jd爲py文件,f1爲jd中的函數
from test import jd
jd.f1()
# 導入(絕對導入、相對. /..導入:相對導入必須有父籍包
# import
# from 模塊.模塊 import 模塊
# from 模塊.模塊.模塊 import 函數
# 調用:模塊.函數(),函數()
# 主文件:運行的文件(print(__name__)). 
if __name__ == '__main__

Note1(4)

  • 模塊在和要執行的py文件在同一路徑且須要不少功能時,推薦使用import 模塊
  • 其餘推薦:from 模塊 import 模塊
  • from 模塊1.模塊2 import 函數 執行:函數()
  • 文件(夾)命名不可與模塊相同,不然就會用當前目錄中的文件(夾)
# __file__ python命令行中獲取的參數
import os
import sys
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
sys.path.append(BASE_DIR)

6.2 模塊的基本知識

  1. 分類

    • 內置模塊(py內部提供的功能)
    • 第三方模塊
    # pip 安裝模塊
    pip install module_name
    # 安裝成功,若是導入不成功,須要重啓pycharm
    • 自定義模塊
    # a.py
    def f1():
      pass
    def f2():
      pass
    # 調用自定義模塊中的功能
    import a
    a.f1()

6.3 內置模塊(10)

​ 內置模塊目前有randomhashlibgetpasssys相關,os相關,shutiljsontime&datetime, import lib, logging10個。

1. random(7)

# random.randint(a, b)
import random
def get_random_data(length=6):
    data = []
    for i in range(length):
        v = chr(random.randint(65, 90)).lower()  # 獲得一個隨機數
        data.append(v)
    return ' '.join(data)
  1. random.randint(1,5)
  2. random.choice([1, 2, 3]):隨機選擇一個:驗證碼,抽獎≥
  3. random.sample([1, 2, 3, 4, 5], 3):隨機選3個不重複,抽獎多我的
  4. random.uniform(1, 5):隨機1-5中的隨機小數
  5. random.shuffle([1,2,3,4]):洗牌,算法
  6. random.random():隨機生成[0-1)之間的數
  7. random.randrange(1,5):randint基於randrange

2. hashlib(1) / getpass

摘要算法模塊,密文驗證/校驗文件獨立性

note1(3)

  1. md5 / sha
  2. 摘要文件內容同樣,不管怎麼分割,md5摘要後一致(大文件一致性校驗)
  3. 通常在服務端進行加鹽,給每一個用戶使用不一樣的salt,能夠藉助用戶名
# 將指定的**str**摘要,可使用sha1/md5
# md5經常使用來文件完整性校驗
# hashlib.md5()/ .update() /.hexdigest()
import hashlib
def get_md5(data):
    obj = hashlib.md5()
    obj.update(data.encode('utf-8'))
    return obj.hexdigest()
val = get_md5('123')
print(val)

加鹽

import hashlib
def get_md5(data):
    obj = hashlib.md5('adsfg12fsg'.encode('utf-8'))
    obj.update(data.encode('utf-8'))
    return obj.hexdigest()
val = get_md5('123')
print(val)

密碼不顯示

import getpass
pwd = getpass.getpass('please input pwd: ')
print(pwd)

3. time(2)

import time
v = time.time() # 獲取從1970年開始到目前的時間,單位爲秒
time.sleep(2)   # 休眠時間,2秒

4. sys (5)

  • 解釋器相關
  1. sys.getrefcount(a)
  2. sys.recursionlimit() / sys.setrecursionlimit()
  3. sys.stdout.write(). print—>進度條
  4. sys.argv:獲取命令行參數
    • shutil(shutil.rmtree(path)
  5. sys.path:模塊導入路徑
  6. sys.modules:存儲當前程序中用到的全部模塊
# 引用計數器
import sys  
a = [1, 2, 3]
print(sys.getrefcount(a))

# python默認支持的遞歸數量
v = sys.getrecrusionlimit()

# 輸入輸出,默認換行
sys.stdout.write('hello')
# \n \t 
# \r: 回到當前行的起始位置,通常於end=‘’連用
print('123\r', end='')
print('hello', end='')   
# 在輸出的時候,回到123前,從新打印
# 應用:進度條
  • sys.argv / shutil
# sys.argv  shutil
# 刪除 目錄 的腳本, 只能是directory
import sys
import shutil

path = sys.argv[1]
shutil.rmtree(path)
print('remove the %s' % path)
  • sys.path(是個list)
    • paython解釋器會按sys.pathon的路徑查找
# sys包含python 和 工做目錄
# 當前py文件所在路徑會加載到 sys.path中
# pycharm也會 自動添加工做目錄 和 項目路徑加入
# python導入模塊時默認查找路徑
# 只能導入目錄下的第一層文件

sys.path.append('module_path')

5. os(操做系統相關)(16)

  1. os.path.exist(file_name)
  2. os.stat(file_name).st_size
  3. os.path.abspath(file_name)
  4. os.path.dirname(file_name) # 獲取上級目錄
  5. os.path.join() # 路徑拼接
  6. os.listdir() # 指定目錄下的第一層文件,默認path = '.'
  7. os.walk(r'path')
  8. os.mkdir() / os.makedirs()
  9. os.rename(a, b)
  10. os.remove(a)
  11. os.path.isdir()
  12. os.path.isfile()
  13. os.path.isabs()
  14. os.path.basename():獲取絕對路徑下的文件名
  15. os.getpid():獲取進程的id
  16. os.getppid():獲取其父進程的id
import os
1. 獲取文件大小
fiel_size = os.stat('filename').st_size       # 單位爲字節
2. 讀取文件
chunk_size = 1024
with open('filename', mode='rb') as f1:
  
v = r'path'                                            # r 表示轉義,包括全部
os.path.dirname(v)
轉義
v = 'al\\nex'
v = r'al\nex'                                            # 推薦
import os
v = 'test.txt'
path = 'user/henry/desktop'
new_path = os.path.join(path, v)
# 當前目錄下第一層文件
import os
result = os.listdir(r'path')
print(result)

# 當前目錄下的全部文件
import os
result = os.walk(r'path')   # 生成器
for a, b, c in result: 
  for i in c:                                           # a 是目錄;b 是目錄下的文件夾;c 是目錄下的文件
    path = os.path.join(a, i)
      print(path)

6. shutil(4)

  1. shutil.make_archive()
  2. shutil.unpack_archive()
  3. shutil.rmtree()
  4. shutil.move()
import shutil
shutil.rmtree(r'path')
import shutil
 # 沒有返回值
shutil.rmtree('dir_name')
# 重命名,能夠是文件/目錄
shutil.move('file_name1', 'new_file_name')
# 壓縮文件(c_file_name.zip), 若是隻給定文件名,壓縮到py腳本所在目錄
shutil.make_archive('c_file_name', 'zip', 'dir_name')
# 解壓文件,默認是當前路徑, 指定目錄不存在會建立文件目錄
shutil.unpack_archive('c_file_name.zip', extra=r'dir_paths', format='zip', )
from datetime import datetime
# 當前時間
ctime = datetim.now().strftime('%Y-%m-%d %H:%M:%S')
# 1.壓縮test文件夾
# 2.放到code目錄(默認不存在)
# 3.將文件解壓到/User/henry/Desktop/t中

6.4 json

序列化:將本來的字典、列表等內容轉換成一個字符串的過程就叫作序列化

目的

  1. 以某種存儲形式使自定義對象持久化
    • 對象持久化是指將內存中的對象保存到可永久保存的存儲設備中(如磁盤)的一種技術。
  2. 將對象從一個地方傳遞到另外一個地方。
  3. 使程序更具維護性。

  • json, 全部語言通用,只能序列化指定的基本數據類型
    • dumps/loads/ dump/load
    • 全部字符串必須都是雙引號
    • 最外層只能是dict/list
    • 不能支持load屢次
    • dict中key只能是str
  • pickle,幾乎支持全部python東西(socket對象),序列化的內容只能用python
    • dumps/loads/ dump/load
    • 支持連續load屢次

1. json

# 只能包含,int,bool,str,list,dict
# 最外層必須是list/dict
# json 中若是包含str,必須是 雙引號
# 若是是tuple類型數據,則會轉換爲list
- 特殊的字符串(list和dict嵌套的string)
- 不一樣語言間的數據交互
- 序列化/反序列化:把其語言的數據轉化成json格式/ 相反
import json
v = [12, 3, 4, {'k1': 1}, True, 'adsf']
# 序列化
v = json.dumps(v)
# 反序列化
json.loads(v)
# 可轉爲json的數據中包含中文,讓中文徹底顯示
v = {'k1': 'alex', 'k2': '你好'}
val = json.dumps(v, ensure_ascii=False)
print(val, type(val))

val = json.dumps(v)
print(val, type(val))

2. pickle

# 使用pickle序列化後,結果是編碼後的二進制
import pickle
v = {1, 2, 3}
val = pickle.dumps(v)
print(val, typ(val))
val = pickle.loads(v)
print(val, typ(val))
# json dump 獲得的是str, pickle獲得的是bytes

Note(2)

  1. 通過編碼事後的數據,一般稱爲 字節類型/bytes,字符串,格式爲:b‘XXXXXXXX'
  2. 壓縮後的0101

6.5 time&datetime

1. time

UTC/GMT:世界協調時間

本地時間:本地時區的時間

  • time.time() # 獲取時間戳 1970.1.1 00:00-至今 的秒數
  • time.sleep(10) # 等待的秒數
  • time.timezone # 和標準時間的差距,和電腦的設置有關

2. datetime

# 獲取datetime格式時間
from datetime import datetime, timezone, timedelta
v1 = datetime.now()
v2 = datetime.utcnow()
tz = timezone(timedelta(hours = 7))       # 東7區
v3 = datetime.now(tz)                          # 當前東7區時間

<class 'datetime.datetime'>
# 將datetime格式時間轉化爲str
v1 = datetime.now()
v1.strftime('%Y-%m-%d')                    # 鏈接不能使用漢字(Mac,linux沒問題),可使用.format()方法
# str轉datetime,時間加減
val = datetime.strptime('2019-04-18', '%Y-%m-%d')
v = val +/- timedelta(days=40)              # 當前時間加/減40天
# 時間戳和datetime關係
import time, datetime
ctime = time.time()
datetime.fromtimestamp(ctime,tz)       # 當前時間,tz和上述相同
v = datetime.now()
val = v.timestamp()
print(val)

6.6 模塊importlib

做用:根據字符串形式導入模塊

開放封閉原則:配置文件開放,代碼封閉

  1. 使用str導入模塊
  2. __import__(和importlib.import_module('模塊名'))
  3. os = __import__('os')和2等價
# 用字符串形式,去對象中找到其成員
import importlib
redis = importlib.import_module('utils.redis')
getattr(redis, 'func')()
import importlib
path = 'utils.redis.func'
module_path, func_name = path.rsplit('.', 1)
getattr(module_path, func_name)()
# 導入模塊
import importlib
middleware_classes = [
    'utils.redis.Redis',
    'utils.mysql.MySQL',
    'utils.mongo.Mongo'
]
for path in middleware_classes:
    module_path,class_name = path.rsplit('.',maxsplit=1)
    module_object = importlib.import_module(module_path) # from utils import redis
    cls = getattr(module_object,class_name)
    obj = cls()
    obj.connect()

# 用字符串的形式導入模塊。
# redis = importlib.import_module('utils.redis')
# 用字符串的形式去對象(模塊)找到他的成員。
# getattr(redis,'func')()

6.7 日誌(模塊logging)

日誌等級(level) 描述
DEBUG 最詳細的日誌信息,典型應用場景是 問題診斷
INFO 信息詳細程度僅次於DEBUG,一般只記錄關鍵節點信息,用於確認一切都是按照咱們預期的那樣進行工做
WARNING 當某些不指望的事情發生時記錄的信息(如,磁盤可用空間較低),可是此時應用程序仍是正常運行的
ERROR 因爲一個更嚴重的問題致使某些功能不能正常運行時記錄的信息
CRITICAL 當發生嚴重錯誤,致使應用程序不能繼續運行時記錄的信息

1. 日誌示例

Note(2)
  • 屢次配置logging模塊,只有第一次配置有效
  • 在應用日誌時,保留堆棧信息需加上exc_info=True
  • 用戶:記錄日誌(銀行流水)
  • 程序員:統計、故障排除的 debug、錯誤完成代碼優化
# 方法1, 
# basicConfig 不能實現中文編碼,不能同時向文件和屏幕輸出
import logging
# logging.Error 默認級別
logging.basicConfig(fielname='cmdb.log',
                    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s'
                    datefmt = '%Y-%m-%d-%H-%M-%S'
                    level=logging.WARNING)
logging.log(10, '日誌內容')                    # 不寫
logging.debug('asdfgh')
logging.log(30, 'asdfgh')                      # 寫
logging.warning('asdfgh')

應用場景:對於異常處理捕獲的內容,使用日誌模塊將其保存到日誌

try:
  requests.get('http://www.google.com')
except Exception as e:
  msg = str(e)  # 調用e.__str__方法
  logging.error(msg, exc_info=True)        # 線程安全,支持併發

2. logging本質

# 方法2
import logging
# 對象1:文件 + 格式
file_handler = logging.FileHandler('xxxxx', 'a', encoding='utf-8')
fmt = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(module)s: %(message)s')
file_handler.setFormatter(fmt)

# 對象2:寫(封裝了對象1 )
logger = logging.Logger('xxx(在log中會顯示)', level=logging.ERROR)
logger.addHandler(file_handler)

logger.error('你好')

3. 示例

# 推薦
import logging

file_handler = logging.FileHandler(filename='x1.log', mode='a', encoding='utf-8',)
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
    handlers=[file_handler,],
    level=logging.ERROR
)

logging.error('你好')

logger對像

  1. 建立一個logger對象、文件操做符屏幕操做符格式
  2. 給logger綁定文件操做和屏幕操做
  3. 給屏幕操做符和文件操做符設置格式
  4. 用logger對象操做
# warning和error寫入不一樣文件,須要建立不一樣對象
import logging
# 須要加入name參數
logger = logging.getLogger() 
fh = logging.FileHandler('log.log')            # 寫入文件
sh = logging.StreamHander()                  # 不須要參數,輸出到屏幕
logger.addHander(fh)
logger.addHander(sh)
# asctime:日誌寫入時間, name:logger對象名稱, levelname:日誌級別, module:模塊名稱
fmt=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(module)s: %(message)s')
fh.Setformatter(fmt)
logger.waring('message')

4. 日誌切割

import time
import logging
from logging import handlers
# file_handler = logging.FileHandler(filename='x1.log', mode='a', encoding='utf-8',)
file_handler = handlers.TimedRotatingFileHandler(filename='x3.log', when='s', interval=5, encoding='utf-8')
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
    handlers=[file_handler,],
    level=logging.ERROR
)

for i in range(1,100000):
    time.sleep(1)
    logging.error(str(i))
# 在應用日誌時,若是想要保留異常的堆棧信息,exc_info=True
    msg = str(e)  # 調用e.__str__方法
    logging.error(msg,exc_info=True)

6.8 collections(python核心模塊)

  • OrideredDict()
# dict建立過程
info = dict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
  • defaultDict
  • deque:雙端隊列
  • namedtuple:默認dict,能夠給dict的value設置一個默認值
from collections import namedtuple
# 可命名tuple(time 結構化時間)
# 建立了一個Course類,這個類沒有方法,全部屬性值不能修改
Course = namedtuple('Course', ['name',  'price', 'teacher'])
python = Course('python', 999, 'alex')

print(python)
print(python.name)
print(python.price)

6.9 struct模塊

  • unpack的結果是元組
  • 第一參數是數據類型
# 把數據轉換爲四個字節
import struct
a = struct.pack('i', 1000)
b = struct.pack('i', 78)

a1 = struct.unpack('i', a)
b1 = struct.unpack('i', b)

第七章 面向對象

7.1 面向對象基礎

面向對象編程(Object Oriented Programming,OOP,面向對象程序設計)

優勢和應用場景

  1. 業務功能較多時,經過面向對象歸類
  2. 數據封裝(建立字典存儲數據)
  3. 遊戲示例:建立一些角色,並根據角色須要再建立任務
  • 封裝思想:將同一類的函數封裝到同一個py文件中,之後方便使用
  • 面向對象:將同一類的函數封裝到同一個class中,之後方便使用
  • 對象名:命名首字母大寫

Note1(1)

  • 函數式的應用場景 --> 各個函數之間是獨立且無共用的數據

1. 基礎概念

  • :具備相同方法和屬性的一類事物
  • 對象實例:一個擁有具體屬性值和動做的具體個體
  • 實例化:從一個類獲得一個具體對象的過程
# 定義一個類,Account
class Account:
    # 方法, 哪一個對象調用方法,其就是self
    def login(self,name):
            print(123)
        return 666
    def logout(self):
            pass
# 調用類中的方法 
x = Account()                
# 實例化(建立)Account類的對象,開闢一塊內存
val = x.login('henry')                            # 使用對象調用class中的方法
print(val)

Note2(2)

  • 應用場景:用於不少函數,須要對函數進行歸類和劃分(封裝)
  • self:哪一個對象操做,self表明類的實例,而非類

2. 對象的封裝

  • 做用:存儲一些值,將數據封裝到對象,方便使用
  • 屬性調用對象.屬性名進行數據的調用
  • 廣義封裝:類中成員
  • 狹義封裝:私有成員:_類名__名字:命名
class File:
  def read(self):
    with open(self.path, mode='r', encoding='utf-8') as f:
      data = f.read()
  def write(self, content):
    with open(self.path, mode='a', encoding='utf-8') as f:
      data = f.write()

obj = File()                    # 建立對象,並使用   
obj.path = 'test.txt'                              # 往obj對象中寫入一個私有對象
obj.write(content)
# 定義私有屬性,私有屬性在類外部沒法直接進行訪問
obj2 = File('info.txt')
obj2.write(content)
class Person:
# __init__初始化方法(構造方法),給對象內部作初始化
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender
    def show(self):
        temp = 'i am %s, age:%s, gender:%s ' % (self.name, self.age, self.gender)
      print(temp)
# 類(),會執行__init__         
obj = Person('henry', 19, 'male') 
obj.show()

obj2 = Person('echo', 19, 'female')
obj2.show()

Note3(3)

  1. 函數和數據的封裝
    • 若是寫代碼時,函數較多,能夠將函數歸類,並放入同一類中。(函數的封裝)
    • 函數若是有一個反覆使用的公共值,則能夠封裝到類中(數據的封裝)
  2. 面向對象三大特性:封裝、繼承、多態
  3. 執行類中的方法時,須要經過self間接調用被封裝的內容

2.1 查看對象的類

# 類有一個名爲 __init__() 的構造方法,該方法在類實例化時會自動調用,通常經過object類進行格式化
# 類的方法與普通的函數只有一個特別的區別——它們必須有一個額外的第一個參數名稱, 按照慣例它的名稱是 self。
# self.__class__:查看實例所在的類
class Test:
    def prt(self):
        print(self)
        print(self.__class__)
t = Test()
t.prt()

2.2 類的方法

​ 在類的內部,使用 def 關鍵字來定義一個方法,與通常函數定義不一樣,類方法必須包含參數 self,且爲第一個參數,self 表明的是類的實例。self 的名字並非規定死的,也可使用 this,可是最好仍是按照約定是用 self

​ 類的私有方法**__private_method:兩個下劃線開頭,聲明該方法爲私有方法,只能在類的內部調用 ,不能在類的外部調用。self.__private_methods**。

2.3 示例

# 循環讓用戶輸入:用戶名,密碼,郵箱,輸入完成後在打印
class Person():
  def __init__(self, user, pwd, email):
    self.username = user
    self.password = pwd
    self.email = email
  def info(self):
    return  temp = 'i am %s, pwd:%s, email:%s ' % (self.username, self.password, self.email,)

USER_LIST = []
while 1:
  user = input('please input user name: ')
  pwd = input('please input user pwd: ')
  email = input('please input user email: ')
  p = Person(user, pwd, email)
  USER_LIST.append(p)

for i in USER_LIST:
    data = i.info()
  print(i)

3. 繼承

場景:多個類中,若是有公共的方法能夠放到基類中,增長代碼的重用性。

繼承:能夠對基類中的方法進行覆寫

3.1 繼承的查找方法

# 父類(基類)
class Base:
  def f1(self):
    pass
  
# 單繼承,子類,Foo類繼承Base類 (派生類)
class Foo(Base):
  def f2(self):
    pass
# 建立了一個子類對象
obj = Foo()
# 執行對象.方法時,優先在本身類中找,沒有則找其父類
obj.f2()    
obj.f1()

# 建立了一個父類對象
obj = Base()
obj.f1()
obj.f2()   # 會報錯

繼承關係中的查找方法

  1. self 指的是哪一個對象
  2. 當類是經典類時,多繼承狀況下,會按照深度優先方式查找
  3. 當類是新式類時,多繼承狀況下,會按照廣度優先方式查找

3.2 經典類和新式類

  • 新式類:繼承object,super,多繼承(廣度優先c3),具備mro方法
    • super(新式類支持,遵循mro順序)
  • 經典類:py2不繼承object,無super/mro , 深度優先

​ 從字面上能夠看出一個老一個新,新的必然包含了跟多的功能,也是以後推薦的寫法,從寫法上區分的話,若是當前類或者父類繼承了object類,那麼該類即是新式類,不然即是經典類。

class D(object):
    def bar(self):
        print 'D.bar'
        
class C(D):
    def bar(self):
        print 'C.bar'

class B(D):

    def bar(self):
        print 'B.bar'

class A(B, C):

    def bar(self):
        print 'A.bar'
        
a = A()
# 執行bar方法時
# 首先去A類中查找,若是A類中沒有,則繼續去B類中找,若是B類中麼有,則繼續去C類中找,若是C類中麼有,則繼續去D類中找,若是仍是未找到,則報錯
# 因此,查找順序:A --> B --> C --> D
# 在上述查找bar方法的過程當中,一旦找到,則尋找過程當即中斷,便不會再繼續找了
a.bar()

4. 多態(多種形態/類型)

多態:一個類變現出來的多種狀態—>多個類表現出類似的狀態。

Pyhon不支持Java和C#這一類強類型語言中多態的寫法,可是原生多態,Python崇尚「鴨子類型」。list,tuple,python的多態是經過鴨子類型實現的

# 多態,鴨子模型
def func(arg):                                        # 多種類型,不少事物
  v = arg[-1]                                  # 必須具備此方法,呱呱叫
  print(v)
# 對於一個函數,python對參數類型不會限制,傳入參數時能夠是各類類型,在函數中若是有例如:arg.append方法,就會對傳入類型進行限制。
# 這就是鴨子模型,相似於上述的函數,咱們認爲只要能呱呱叫的就是鴨子,只要有append方法,就是咱們想要的類型

5. 類的專有方法

  • **__init__ :** 初始化,在生成對象時調用
  • **__del__ :** 析構函數,釋放對象時使用
  • **__repr__ :** 打印,轉換
  • **__setitem__ :** 按照索引賦值
  • **__getitem__:** 按照索引獲取值
  • **__len__:** 得到長度
  • **__cmp__:** 比較運算
  • **__call__:** 函數調用
  • **__add__:** 加運算
  • **__sub__:** 減運算
  • **__mul__:** 乘運算
  • **__truediv__:** 除運算
  • **__mod__:** 求餘運算
  • **__pow__:** 乘方

6. 運算符重載

Python一樣支持運算符重載,咱們能夠對類的專有方法進行重載

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b
 
   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)
   
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)
 
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

7.2 類成員(6)

  • 實例化對象時,對在對象中存儲類對象指針,指向其類

1. 類變量(靜態字段/屬性)

  • 寫在類的下一級,和方法同級
  • 訪問:類.變量名/ 對象.變量名
  • 繼承關係中,本身類中沒有的變量能夠去基類中找
  • 只能賦值、修改本身的變量

對象成員實例變量(字段)

Note:屬於誰的只容許誰去取,python容許對象去其類中取變量

2. 方法

2.1 綁定/普通方法

  1. 定義:必須有self參數
  2. 執行:先建立對象,由 對象.方法 調用

2.2 靜態方法

  1. 定義:@staticmethod, 參數無限制
  2. 執行:類.靜態方法名() / python對象也能夠調用
class Foo:
    def __init__(self):
            self.name = 123
    
    def func(self, a, b):
        print(self.name, a, b)
# python內部裝飾器
    @staticmethod
    def f():
        print(1,2)

Foo.f()
obj = Foo()
obj.func(1, 2)
obj.f()

2.3 類方法

  1. 定義:@classmethod, 必須有cls參數,當前類
  2. 執行:類.類方法() / python對象也能夠調用
class Foo:
    def __init__(self):
            self.name = 123
    
    def func(self, a, b):
        print(self.name, a, b)
# python內部裝飾器
    @classmethod
    def f(cls, a, b):
        print(a, b)

Foo.f(1, 2)
obj.f(1, 2)                                           # 不推薦

3. 屬性

  1. 定義:@property 只能有一個參數self
  2. 執行:對象.屬性名( 無括號
class Foo:
    @property
    def func(self):
        print(123)
        print(666)
       
obj = Foo()
ret = obj.func
print(ret)
# 示例:屬性
class Page:
        def __init__(self, total_count, current_page, per_page = 10):
        self.total_count = total_count
        self.current_page = current_page
        self.per_page = per_page
    
    @proporty
    def start_index(self):
        return(self.current_page -1 ) * self.per_page
    @property
    def end_index(self):
        returno self.current_page * self.per_page_count
         
USER_LIST = []
for i in range(321):
    USER_LIST.append('henry-%s' % (i,))

# 請實現分頁
current_page = int(input('請輸入要查看的頁碼:'))
p = Page(321, current_page)
data_list = USER_LIST[p.start_index:p.end_index]
for i in data_list:
    print(i)

4. 成員修飾符

  • 公有:全部位置都能訪問
  • 私有:__開頭(只有本身才能訪問)
class Foo:
    def __init__(self, name):
        self.__name = name    
    def func(self):
        print(self.name)        
obj = Foo('alex')
print(obj.__name)                        # 會報錯
obj.func()                                            # 能夠訪問
class Foo:
    __x = 1 
    @staticmethod
    def func():
        print(Foo.__x)
obj = Foo()  
print(Foo.__x)                                    # 會報錯
print(obj._Foo__x)                              # 強制訪問私有成員

7.3 特殊方法

  • 特殊方法/魔術方法/內置方法/雙下方法
  • 特殊成員(方法)__init__
  • type / isinstance / issubclass / super
  • 異常處理
  1. 類和對象的關係:對象是類的一個實例
  2. self:本質就是一個形式參數,對象調用方法時,python內部會將該對象傳給這個參數
  3. 類/方法/對像均可以看成變量或嵌套到其餘類中
class School(object):
    def __init__(self,title):
      self.title = title
    def rename(self):
      pass
   
class Course(object):
    def __init__(self, name, school_obj):
      self.name = name
      self.school = school_obj
    def reset_price(self):
      pass
      
class Classes(object):
    def __init__(self,cname, course_obj):
      self.cname = cname
      self.course = course_obj
    def sk(self):
      pass

s1 = School('北京')
c1 = Course('Python', s1)
cl1 = Classes('全棧1期', c1)

1. 嵌套

  • 函數:參數能夠是任意類型
  • dict:函數、類和對像均可以做爲字典的key, 即都是可hash的
  • 繼承的查找關係
# 示例1
class StarkConfig(object):
  pass

class AdminSite(object):
  def __init__(self):
    self.data_list = []
  def register(self, arg):
    self.data_list.append(arg)
  
site = AdminSite()
obj = StarkConfig()
site.regisetr(obj)
# 示例2
class StarkConfig(object):
  def __init__(self, name, age):
    self.name = name
    self.age = aeg
    
class AdminSite(object):
  def __init__(self):
    self.data_list = []
    self.sk = None
    
  def set_sk(self, arg=StarkConfig):
    self.sk =arg
     
site = AdminSite()
site.set_sk(StarkConfig)
site.sk('henry', 19)
# 示例3
class StarkConfig(object):
  list_display = 'henry'
  
  def changelist(self):
    print(self.list_display)
    
class UserConfig(StarkConfig):
  list_display = 'echo'
  
  
class AdminSite(object):
  def __init__(self):
    self._register = {}
    
  def registry(self, key, arg=StarkConfig):
    self._register[key] = arg
  
  def run(self):
    for key, val in self._register.items():
      obj = val()
      obj.changelist()
    
site = AdminSite()
site.registry(1)
site.registry(2, StackConfig)
site.registry(3, UserConfig)                   # 易錯點 echo
site.run()

2. 特殊成員

特殊成員:爲了可以給快速實現某些方法而生。

2.1 __init__(初始化方法)

# 填充數據,通常稱爲初始化
class Foo:
  """
  此類的做用
  """
  def __init__(self):
  """
  初始化方法
  """
    pass

2.2 __new__(構造方法)

Note

  1. new方法是靜態方法,在使用__new__方法時,構造的對象值爲 new 方法的返回值
  2. 建立的是一塊內存和指針
#  __new__ 建立一個空對象
# 經過 __init__ 初始化對像
class Foo(object):
  def __new__(cls, *args, **kwargs):   # 在 __init__ 以前
    return 'henry'/ object.__new__(cls)
  
  obj = Foo()
  print(obj)

2.3 __call__

# 對象() 會執行類中的 __call__ 方法
class Foo:
    def __init__(self):
        pass
    def __call__(self, *args, **kwargs):
        print('哈哈,你變成我了吧')

Foo()()
# 第三方模塊。寫一個網站,用戶只要來訪問,就自動找到第三個參數並執行
make_server('ip', port, Foo())

2.4 __getitem__ __setitem__ __delitem__

obj = dict()
obj['k1'] = 123
class Foo(object):
  def __setitem__(self, key, values):
    print(key, value)
  def __getitem__(self, item):
    return item + 'uuu'
  def __delitem__(self, key):
    print(key)
 
obj1 = Foo()
obj1['k1'] = 123  # 內部會自動調用__setitem__方法
obj1['xxx']       # 內部會自動調用__getitem__方法
del obj1['ttt']   # 內部會自動調用__delitem__方法

2.5 __str__

# 只有在打印時,會自動調用此方法,並將返回值顯示出來
# type 查看
class Foo:
    def __str__(self):
        print('變樣是否是不認識我了')
        return 'henry'
      
obj = Foo()
print(obj)

2.6 __dict__

做用: 查看對象中有哪些變量

class Foo(object):
  def __init__(self, name, age, email):
    self.name = name
    self.age = age
    self.email = email

obj = Foo('henry', 19, '123@qq.com')
val = obj.__dict__                  # 去對象中找到全部變量並將其轉換爲字典
print(val)

2.7 __enter__(上下文管理

做用:使用with語法時,須要

class Foo(object):
    def __enter__(self):
    self.x = open('a.txt', mode='a', encoding='utf-8')
    return self.x
  def __exit__(self, exe_type, exc_val, exc_tb):
    self.x.close()
  
with Foo() as f:                # 須要 __enter__ 和 __exit__ 方法
  f.write('henry')
  f.write('echo')

2.8 __add__ 兩個對像相加

class Foo(object):
  def __init__(self, v):
    self.v = v
    
    def __add__(self, other):
    return self.v + other.v

obj1 = Foo()
obj2 = Foo()
val = obj1 + obj2                                 # obj1觸發,把obj1傳給self

2.9 __iter__

# 可迭代對象
class Foo:
    def __iter__(self):
            return iter([1, 2, 3, 4])
  
obj = Foo()
# 示例2
class Foo:
    def __iter__(self):
        yield 1
        yield 2
        ...
 
obj = Foo()

3. 內置函數

3.1 type(對象)

class Foo(object):
  pass

obj = Foo()
print('obj是Foo的對象,開心吧') if type(obj) == Foo else print('哪涼快呆哪去')

3.2 issubclass(子類,基類)

# 能夠多級繼承
class Base(object):
    pass
class Bar(Base):
    pass
class Foo(Bar):
    pass
print(issubclass(Foo, Base))

3.3 isinstance(obj, Foo)

# 判斷某個對象是否時 某個類 或 基類 的實例(對象)
class Base(object):
    pass
class Foo(Base):
    pass
obj = Foo()
print(isinstance(obj, Foo))
print(isinstance(obj, Base))

4. super()

# super().func(),根據 self所屬類的繼承關係進行查找,默認找到第一個就中止
class Bar(object):
  def func(self):
      print('bar.func')
      return 123
class Base(Bar):
     def func(self):
      super().func()
      print('bar.func')
      return 123
  
class Foo(Base):
  def func(self):
    v = super().func()
    print('foo.func', v)
  
obj = Foo()
obj.func()

7.4 接口類和抽象類(約束)&反射

1. 擴展

# 會打印 hello
# 類裏的成員會加載,代碼會執行
# 函數只有在調用時執行
class Foo(object):
  print('hello')
  def func(self):
    pass
# 類的嵌套
class Foo(object):
    x = 1
    def func(self):
        pass

    class Meta(object):
        y = 123
        print('hello')
        def show(self):
            print(y.self)

2. 可迭代對象

  • 表象:可被for循環的對象
  • 做用:組合搜索
  • 可迭代對象:在類中實現**__iter__方法並返回迭代器/生成器**
# 可迭代對象示例1
class Foo:
    def __iter__(self):
            return iter([1, 2, 3, 4])
  
obj = Foo()
# 示例2
class Foo:
    def __iter__(self):
        yield 1
        yield 2
        ...''
 
obj = Foo()

3. 抽象類/接口類(約束 源碼)

# python的約束,易錯點
# 約束子類中必需要有send方法,若是沒有則會拋出:NotImplementedError
class Interface(object):
  def send(self):
    raise NotImplementedError()

class Foo(Interface):
  def send(self):
    pass

class Base(Interface): 
  def func(arg):
    arg.send(arg)
# 應用場景示例
class BaseMassage(object):
  def send(self):
    raise NotImplementedError('子類中必須有send方法')
    
class Msg(BaseMassage):
  def send(self):
    print('發送短信')
  
class Email(BaseMassage):
  def send(self):
    print('發送郵件')
  
class Wechat(BaseMassage):
    def send(self):
    print('發送微信')
  
class DingDing(BaseMassage):
    def send(self):
        pass

obj = Email()
obj.send()

4. 反射

反射的概念是由Smith在1982年首次提出的,主要是指程序能夠訪問、檢測和修改它自己狀態或行爲的一種能力(自省)。這一律唸的提出很快引起了計算機科學領域關於應用反射性的研究。它首先被程序語言的設計領域所採用,並在Lisp和麪向對象方面取得了成績。

反射:經過字符串的形式操做對象相關的屬性。python中的一切事物都是對象(均可以使用反射)

  • getattr('對象', 字符串):根據字符串的形式,去某個對象中獲取其成員。
  • hasattr('對象', 字符串):根據字符串的形式,去某個對象中判斷是否有該成員。
  • setattr('對象', '變量',值):根據字符串的形式,去某個對象中設置成員。
  • delatttr('對象', '變量'):根據字符串的形式,去某個對象中刪除成員。
# getattr示例
class Foo(object):
  def __init__(self, name):
    self.name = name
    
obj = Foo('alex')
obj.name
v1 = getattr(obj, 'name')
# setattr示例
obj.name = 'eric'
setattr(obj, 'name', 'eric')
  • getattr:反射當前文件內容
# 反射當前文件內容
import sys
getattr(sys.modules[__name__], 'ab')
# 經過對象獲取、示例變量、綁定方法
# 經過類來獲取類變量、類方法、靜態方法
# 經過模塊名獲取模塊中的任意變量(普通變量、函數、類)
# 經過本文件反射任意變量
# 應用示例
class Foo(object):
   def login(self):
      pass
    
   def regiseter(self):
      pass
  
obj = Foo()
func_name = input('please input method name: ')
# 獲取方法
getattr(obj, func_name)()
# setattr 示例
class Foo(object):
  pass

obj = Foo()
setattr(obj, 'k1', 123)
print(obj.k1)
# delattr 示例
class Foo(object):
  pass

obj = Foo()
obj.k1 = 999
delattr(obj, 'k1')
print(obj.k1)

Note(2)

  • python中一切皆對象(py文件,包,類,對象),能夠經過getattr獲取
  • 經過字符串操做內部成員均可以經過反射的機制實現
import x

v = x.NUM
# 等價於
v = getattr(x, 'NUM')
print(v)

v = getattr(x, 'func')
v()

v = getattr(x, 'Foo')
val = v()
val.x

示例:

# 瀏覽器兩類行爲
# way1: 輸入地址+回車
get....
# way2: 表單(輸入框+按鍵)
post....

# 瀏覽器都會有get,post,dispatch方法
class View(object):
  def get(self):
    pass 
  def Post(self):
    pass
  def Dispatch(self):                              # 請求第一步來這,在進行分發
    pass
# 推薦使用性能較好
class Foo(object):
  def post(self):
    pass

# 方式1
if hasattr(obj, 'get'):
  getattr(obj, 'get')
# 方式2:推薦使用
v = getattr(obj, 'get', None)
print(v)

7.5 單例&項目結構

1. 單例模式

1.1 單例

場景數據庫鏈接和數據庫鏈接池(數據一致時)

設計模式:23種設計模式

class Foo(object):
  pass 
# 每實例化一次,就建立一個新對象,內存地址 不同
obj1 = Foo()
obj2 = Foo()
# 單例(Singleton)模式,不管是實例化多少次,都用第一次建立的那個對象,內存地址同樣
class Singleton(object):
  instance = None
  def __new__(cls, *args, **kwargs):
    if not cls.instance:
        cls.instance = object.__new__(cls)
    return cls.instance
 
obj1 = Singleton()                                   # 內存地址一致
obj2 = Singleton()

1.2 標準

# 須要加鎖,多線程,併發
class FileHelper(object):
  instance = None
    def __init__(self, path):
    self.file_object = open(path, mode='r', encoding='utf-8')
  
  def __new__(cls, *args, **kwargs):
    if not cls.instance:
      cls.instance = object.__new__(cls)
    return cls.instance

obj1 = FileHelper('x')                             # 內存地址一致
obj2 = FileHelper('x')

2. 模塊導入

# 導入模塊,只是保留模塊內存
# 思考角度:函數名不能重複、內存溢出
from jd import n1

# 屢次導入,模塊只會加載一次,即便模塊中包含其餘模塊
import jd
import jd
print(456)
# 屢次導入,模塊只會加載一次,即便模塊中包含其餘模塊
import importlib
import jd
# 手動加載,會覆蓋第一次導入
importlib.reload(jd)  
print(456)
  • 經過模塊導入特性,也能夠實現單例模式
# jd.py
class Foo(object):
  pass
obj = Foo()
# app.py
import jd                                             # 加載jd.py,加載最後會實例化一個Foo對象並賦值給obj
print(jd.obj)

3. 項目開發規範

  1. binstart
  2. config:配置文件settings
  3. src:業務邏輯
  4. db:數據文件
  5. lib:擴展模塊
  6. log:日誌文件

3.1 腳本

import os
import re
import datetime

import xlrd
import requests

3.2 單可執行文件

# app(程序入口)/src(業務相關)/lib(公共的類庫)/db(文件)/config(配置)
app.py 越簡單越好,少於10行

3.3 多可執行文件

# app(程序入口)/src(業務相關)/lib(公共的類庫)/db(文件)/config(配置)
# bin(多個可執行文件例如:student.py,teacher.py,admin.py)
# log   (存儲日誌文件)
# seetings(BASE_PATH,LOG_FILE_NAME...)
path = sys.path.os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(path)

番外篇之正則

1. 基本概念

  1. re模塊自己只是用來操做正則表達式的和正則自己無關
  2. 正則表達式:是一種匹配字符串的規則
  3. 爲何要有正則:應用場景
    • 匹配字符串
    • 表單驗證:11位,全數字,1開頭,第二個數 3-9,綁定銀行卡
    • 爬蟲:從網頁源碼中獲取連接,重要數據

2. 規則

2.1 元字符

  • 是哪一個一字符就匹配字符串中的哪一個字符
  1. 字符組(3)
    • [ad] ,匹配a/d,單字符匹配
    • [0-9], [a-z], [A-Z] (範圍是從小到大),遵循ASCII碼
    • [a-zA-Z], [0-9x]
  2. 轉義字符(7 )
    • [0-9] 等價於 \d (\轉義符,轉義d使得其匹配0-9之間的數)
    • \w:(word,數字,大小寫字母,下劃線)
    • \s:(space, 空格,換行,製表符) (\t(table) \n(next))
    • \D \W \S(對以上結果取反)
    • \b:匹配\w\W之間,即匹配單詞邊界匹配一個單詞邊界,也就是指單詞和空格間的位置。例如, 'er\b' 能夠匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
  3. 特殊符號的含義(4)
    • . 除了換行符以外的任意內容
    • [\d] [0-9] \d 沒有區別。 [\d\D] 匹配全部
    • [^abc]:非字符組,[abc] 取反
    • ^:表示一個字符的開始。 $:表示一個字符的結束 (^abc$)
  4. | 和()eg. abc|edf。 abc|ab
# 若果規則有重疊,須要長的在前面
www.(baidu|google).com
# () 表示分組,給一部分正則規定爲一組,

2.2 量詞(6)

1[3-9]\d{9}                                           # 量詞前面一個重複次數,9次
1[3-9]\d{9,}                                          # 量詞前面一個重複次數,9次以上
1[3-9]\d{n,m}                                        # 量詞前面一個重複次數,n-m次
?                                                         # ? 匹配到0次或1次,沒匹配上也算一次,匹配上算2次
                               #(無關緊要,只能有一個)
+                              # + 匹配1次或屢次
*                                                         # * 匹配0次或屢次
# 匹配任意小數,保留兩位
\d+\.\d{2}
# 匹配任意整數或小數
\d+\.?\d*                                           # 有bug
\d+(\.\d+)?                                            # 分組實現

2.3 貪婪匹配/惰性匹配

\d{7-12}                                                 # 默認是貪婪匹配,儘可能多匹配
                                                             # 回溯算法
  
# 非貪婪匹配,惰性匹配,老是匹配符合條件範圍內儘可能小的字符串
\d{2,3}?                                          # 匹配兩位數
\d+?3                                                     # 儘可能多取,遇到3結束
元字符 量詞 ?x                                        # 按照元字符規則在量詞範圍內匹配,一旦遇到x中止
.*?x                                                        # 經常使用,先找x找到匹配結束
# 身份證號匹配(正則表達式,斷言)
[1-9](\d{16}[\dx]|\d{14})
[1-9]\d{14}(\d{2}[\dx])

^([1-9]\d{16}[0-9x]|[1-9]\d{14})$
. 是任意字符
* 是取 0 至 無限長度
? 是非貪婪模式。
.*?x                                                  # 就是取前面任意長度的字符,直到一個x出現

2.4 示例

# 匹配郵箱
\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}

# url
^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+

3. re模塊

3.1 compile()

  • 編譯正則表達式模式,返回一個對象的模式。(能夠把那些經常使用的正則表達式編譯成正則表達式對象,這樣能夠提升一點效率。)
  • 格式:re.compile(pattern,flags=0),pattern: 編譯時用的表達式字符串。flags 編譯標誌位,用於修改正則表達式的匹配方式,如:是否區分大小寫,多行匹配等。經常使用的flags有:
標誌 含義
re.S(DOTALL) 使匹配包括換行在內的全部字符
re.I(IGNORECASE) 使匹配對大小寫不敏感
re.L(LOCALE) 作本地化識別(locale-aware)匹配,法語等
re.M (MULTILINE) 多行匹配,影響^和$
re.X (VERBOSE) 該標誌經過給予更靈活的格式以便將正則表達式寫得更易於理解
re.U 根據Unicode字符集解析字符,這個標誌影響\w,\W,\b,\B
import re
tt = "Tina is a good girl, she is cool, clever, and so on..."
rr = re.compile(r'\w*oo\w*')
print(rr.findall(tt))   
# 查找全部包含'oo'的單詞
執行結果以下:
['good', 'cool']

3.2 re.match()

格式:re.match(pattern, string, flags=0)

  • 在search前的正則前加了一個:^
  • 想要徹底匹配,能夠在表達式末尾加上邊界匹配符$,沒有匹配到,則返回 None
# 從字符串開頭匹配,匹配上則返回一個match對像,有group()方法
   import re 
   ret = re.match('\d', '8alex83')  
   print(ret)

格式:re.search(pattern, string, flags=0)

  • re.search只要找到第一個匹配而後返回,若是字符串沒有匹配,則返回None
print(re.search('\dcom','www.4comrunoob.5com').group())
   # 執行結果以下:4com

:match和search一旦匹配成功,就是一個match object對象,而match object對象有如下方法:

  • start() 返回匹配開始的位置
  • end() 返回匹配結束的位置
  • span() 返回一個元組包含匹配 (開始,結束) 的位置
  • group() 返回re總體匹配的字符串,能夠一次輸入多個組號,對應組號匹配的字符串。

a. group()返回re總體匹配的字符串,

b. group (n,m) 返回組號爲n,m所匹配的字符串,若是組號不存在,則返回indexError異常
c.groups()groups() 方法返回一個包含正則表達式中全部小組字符串的元組,從 1 到所含的小組號,一般groups()不須要參數,返回一個元組,元組中的元就是正則表達式中定義的組。

import re
   a = "123abc456"
    print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(0))   #123abc456,返回總體
    print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(1))   #123
    print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(2))   #abc
    print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(3))   #456
   ###group(1) 列出第一個括號匹配部分,group(2) 列出第二個括號匹配部分,group(3) 列出第三個括號匹配部分。###

3.4 findall()

**格式**:re.findall(pattern, string, flags=0)
  • 能夠獲取字符串中全部匹配的字符串,返回一個列表,沒有匹配則爲空。
p = re.compile(r'\d+')
   print(p.findall('o1n2m3k4'))
   執行結果以下:
   ['1', '2', '3', '4']
import re
   tt = "Tina is a good girl, she is cool, clever, and so on..."
   rr = re.compile(r'\w*oo\w*')
   print(rr.findall(tt))
   print(re.findall(r'(\w)*oo(\w)',tt))        # ()表示子表達式 
   執行結果以下:
   ['good', 'cool']
   [('g', 'd'), ('c', 'l')]

3.5 finditer()

格式:re.finditer(pattern, string, flags=0)

  • 搜索string,返回一個順序訪問每個匹配結果(Match對象)的迭代器。找到 RE 匹配的全部子串,並把它們做爲一個迭代器返回。
# 匹配到結果爲 迭代器,每一項都是match對象,經過group取值
   import re
   ret = re.finditer('\d', 'safh123ghakjdsfg234'*2000000)
   for i in ret:
     print(i.group())

3.6 split()

**格式**:re.split(pattern, string[, maxsplit],maxsplit用於指定最大分割次數,不指定將所有分割。

- 按照可以匹配的子串將string分割後返回列表。
- 可使用re.split來分割字符串,如:re.split(r'\s+', text);將字符串按空格分割成一個單詞列表。

​```python
import re
ret = re.split('\d+', 'henry18')
print(ret)
# 保留分組中內容
ret = re.split('(\d+)', 'henry18')
print(ret)
​```

3.7 sub()/ subn()

**格式**:re.sub(pattern, repl, string, count=0)
   
**格式**:subn(pattern, repl, string, count=0, flags=0)
  • 不返回/返回替換次數

import re
text = "JGood is a handsome boy, he is cool, clever, and so on..."
print(re.sub(r'\s+', lambda m:'['+m.group(0)+']', text,0))        # flags=0默認參數
執行結果以下:
JGood[ ]is[ ]a[ ]handsome[ ]boy,[ ]he[ ]is[ ]cool,[ ]clever,[ ]and[ ]so[ ]on...

python # 替換 n 次 ret = re.sub('\d', 'G', 'henry18',n) print(ret) # 返回替換次數(tuple類型) ret = re.subn('\d', 'G', 'henry18') print(ret) # 返回值爲tuple類型 ​

```

4. 分組

4.1 特殊分組用法

語法 含義 示例
(?P ) 分組,除了原有的編號外再指定一個額外的別名 (?P abc){2} abcabc
(?P=name) 引用別名爲 的分組匹配到字符串 (?P \d)abc(?P=id) 1abc15abc5
<number> 引用編號爲 的分組匹配到字符串 (\d)abc\1 1abc15abc5
(?<=….) 以…開頭,並不包括開頭
(?<!….) 不以…結尾,並不包括開頭

Note(3)

  1. [^] 帶有特殊意義的元字符到字符組中大部分會取消它特殊意義
  2. [()+*.]:取消特殊含義,恢復本來意義
  3. [-]:第一個或最後表示橫槓,中間位置表示範圍

4.2. group()

  • 括號中默認爲0,即取第0個分組
s = '<h1>wahaha</h1>'
ret = re.search('(\w+)>(.*?)</\w+>', s)
print(ret.group())
print(ret.group(1))
print(ret.group(2))

4.3 分組命名

  • (?P 正則表達式)
  • name:不須要加引號,自己就是字符串
ret = re.search('<(?P<tag>\w+)>(?P<content>.*?)</\w+>', s)
print(ret.group('tag'))
print(ret.group('content'))

4.4 引用分組

  • (?P=name)
s = '<h1>wahaha</h1>'
ret = re.search('(?P<tag>\w+)>.*?</(?P=tag)>', s)
print(ret.group())
s = '<h1>wahaha</h1>'
# \1 在python中有特殊含義
ret = re.search(r'(\w+)>.*?</\1>', s)
print(ret.group())

4.5 取消分組優先

  • (?:)
# findall 遇到正則中的分組 優先 顯示分組中的內容
import re
ret = re.findall('\d(\d)', 'henry18')
print(ret)
# 取消分組優先(?:正則表達式)
ret = re.findall('\d+(?:\.\d+)?', '1.234+2')
print(ret)

4.6 split,保留分割符

  • ()
# 保留分組中內容
ret = re.split('(\d+)', 'henry18')
print(ret)

5. 練習

# 示例1:匹配單個數字,findall方法會有屏蔽全部其餘匹配項,只顯示分組中內容
import re
ret = re.findall(r'\d+\.\d+|(\d)', '2+23*3.42/3.2')
print(ret)
while True:
    if '' not in ret:break
    ret.remove('')
print(ret)
# 示例2:匹配以...開頭的數據,不包括開頭
import re
m = re.findall('(?<=>)\w+', '\<a>wahaha\</a>\<b>banana\</b>\<h1>qqxing\</h1>')
 for i in m:
     print(i)
# 匹配不以...開頭的數據,不包括結尾
m = re.findall('(?<!>)\w+', '\<a>wahaha\</a>\<b>banana\</b>\<h1>qqxing\</h1>')
print(m)
  • | :或只負責把兩個表達式分開,若是是整個表達式中只對一部份內容進行或,須要分組
  • ():限定一組正則的量詞約束 (\d\w)?
# 示例3:以a開頭,由至少一個字母組成的字
^a[a-zA-Z]+
^a[a-zA-Z]*
# 以1開頭,中間3-5個數字,若是中間位置超過5個數字,則整個字符串不匹配
^1\d{3,5}$
# 示例4:匹配用戶輸入的身份證號
import re
content = input('用戶輸入:')
ret = re.match('[1-9]\d{14}(\d{2}[\dx])?$', content)
# 示例5:第一個乘除法
import re
ret = re.search('\d+(\.\d+)?[\*]-?\d+(\.\d+)?', s)

第八章 網絡編程

  1. 網絡基礎
  2. 基於tcp協議和udp協議的socket
  3. 解決tcp協議的粘包問題
  4. 併發

8.1 網絡基礎

1. 基本概念(2)

  1. 兩個運行中的程序傳遞信息?

    • 經過文件,經過網絡
  2. 網絡應用開發架構

    • C/S:client/server(須要安裝對應的客戶端)

  • B/S:browser/server(不須要任何客戶端)
    • 統一程序的入口
  • B/S是特殊的C/S

2. 網絡中的概念(7)

  1. 網卡(3)
    • mac地址:48位, 6位冒分十六進制
    • head中包含的源和目標地址由來
      • ethernet規定接入internet的設備都必須具有網卡,發送端和接收端的地址即是指網卡的地址,即mac地址。
    • mac地址:每塊網卡出廠時都被燒製上一個世界惟一的mac地址,長度爲48位2進制,一般由12位16進制數表示(前六位是廠商編號,後六位是流水線號)
  2. 交換機(4)
    • 功能:負責局域網通訊(只認識mac地址,經過arp/ rarp協議)
    • 通訊方式:廣播、單播、組播(交換機只使用前面的兩種)
    • ARP協議:地址解析協議,經過ip地址,獲取其mac地址
    • 保留網段(私有IP):192.168.0.0-92.168.255.255 /172.16.0.0-172.31.255.255 /10.0.0.0-10.255.255.255
    • 廣播限制在二層交換機的局域網範圍內,禁止廣播數據穿過路由器,防止廣播數據影響大面積的主機
  3. 路由器(2)
    • 負責局域網間通訊
    • 網關ip局域網的網絡出口,訪問局域網以外的區域都須要通過gateway
    • 路由器(Router)又稱網關設備(Gateway)是用於鏈接多個邏輯上分開的網絡
  4. 協議
    • server和client獲得的內容都是二進制,雙發預先約定好的一套語法規則
    • 語法、語義、時序
  5. IP地址
    • Ipv6:冒分16進制,0:0:0:0:0:0:0:0- FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF(128bits, 16x8)
    • 每個ip地址要想被全部人訪問到,那麼這個ip地址必須是申請的
  6. port 端口(4)
    • 端口號0 - 65535
    • 一個端口同一時刻只能被一個服務佔用
    • ip+port:能夠確認一臺機器上的一個應用
    • 1024之內只能是root才能啓用

8.2 OSI&TCP/IP

1. TCP協議(6)

  1. TCP(Transmission Control Protocol)可靠的、面向鏈接的協議(eg:打電話)、傳輸效率低全雙工通訊(發送緩存&接收緩存)、面向字節流
  2. 應用場景:文件上傳下載(郵件、網盤、緩存電影)、Web瀏覽器;電子郵件、文件傳輸程序。
  3. 特色(3):面向鏈接、可靠、流式傳輸的全雙工通訊
  4. 三次握手:請求(SYN)—> 確認(ACK)+請求—>確認
    • server端 accept 接收過程當中等待客戶端的鏈接
    • connect 會發送一個syn鏈接請求
      • 若是收到了server響應ack和由server端發來的syn鏈接請求
      • client端進行回覆ack信息後,創建了一個tcp鏈接
    • 三次握手過程在代碼過程當中是由acceptconnect共同完成,具體細節在socket沒有體現
  5. 四次揮手:請求(FIN)—> 確認—>請求—>確認
    • server 端和clinet端在代碼中都有close方法,每一端發起的close操做都是斷開fin請求,獲得斷開確認ack以後,就能夠結束一端的數據發送
    • 若是兩端發起close請求,那麼就是兩次請求兩次確認,一共是四次操做
    • 能夠結束數據發送,表示鏈接斷開
  6. 長鏈接:會一直佔用雙方port
  7. I/O:相對於內存來講
    • write 是 send
    • read 是 recv
    • i input 向內存中輸入 input,read,recv, recvfrom, accept, connect, close
    • output 從內存中輸出 print,write,send,sendto, accept, connect, close

2. UDP協議

  1. UDP(User Datagram Protocol)不可靠的、無鏈接的服務,傳輸效率高(發送前時延小),一對1、一對多、多對1、多對多、面向報文,盡最大努力服務,無擁塞控制。
  2. 場景**:即時通信(qq,wechat),域名系統 (DNS);視頻流;IP語音(VoIP)。
  3. 特色:面向數據報,不可靠,傳輸速度快,能完成一對多,多對一,和一對一的高效通訊
  4. UDP 是User Datagram Protocol的簡稱, 中文名是用戶數據報協議

Note1(3)

  • TCP傳輸數據幾乎沒有限制,UDP可以傳遞數據長度是有限的,是根據數據傳遞設備的設置有關
  • Tcp可靠鏈接,udp不可靠無鏈接
  • 三次握手時,確認信息和請求鏈接信息合併爲一幀,四次揮手,主動斷開端,不能肯定另外一端是否還須要傳輸信息,因此不能合併。

3. OSI(Open System Interconnection)

  1. 應用層
  2. 表示層
  3. 會話層
  4. 傳輸層
  5. 網絡層
  6. 數據鏈路層
  7. 物理層

OSI五層協議(簡化)

  1. 應用層:代碼
  2. 傳輸層:tcp/udp 端口號四層路由器、四層交換機
  3. 網絡層:ipv4/ipv6,三層路由器、三層交換機
  4. 數據鏈路層:mac地址,arp協議,(二層)交換機
  5. 物理層:二進制流

TCP/IP(arp在tcp/ip中屬於網絡層)

  1. 應用層
  2. 傳輸層
  3. 網絡層
  4. 鏈路層

Note2(4)

  1. 家用路由器集成了交換功能
  2. 網絡協議
    • 網際層協議:IP協議、ICMP協議、ARP協議、RARP協議。
    • 傳輸層協議:TCP協議、UDP協議。
    • 應用層協議:FTP、Telnet、SMTP、HTTP、RIP、NFS、DNS

4. socekt(套接字)

  1. 工做在應用層傳輸層之間的抽象層,幫助咱們完成全部信息的組織拼接
    • Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。
  2. socket歷史
    1. 套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 所以,有時人們也把套接字稱爲「伯克利套接字」或「BSD 套接字」。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通信。這也被稱進程間通信,或 IPC套接字有兩種(或者稱爲有兩個種族),分別是基於文件型的和基於網絡型的。
    2. 基於文件類型的套接字家族
      • 套接字家族的名字:AF_UNIX,unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊
    3. 基於網絡類型的套接字家族
      • 套接字家族的名字:AF_INET,(還有AF_INET6被用於ipv6,還有一些其餘的地址家族,不過,他們要麼是隻用於某個平臺,要麼就是已經被廢棄,或者是不多被使用,或者是根本沒有實現,全部地址家族中,AF_INET是使用最普遍的一個,python支持不少種地址家族,可是因爲咱們只關心網絡編程,因此大部分時候我麼只使用AF_INET)
  3. 同一機器上的兩個服務之間的通訊(基於文件)
    • 基於網絡的多臺機器之間的多個服務通訊

socket

  1. 其實站在你的角度上看,socket就是一個模塊。咱們經過調用模塊中已經實現的方法創建兩個進程之間的鏈接和通訊。也有人將socket說成ip+port,由於ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程序。因此咱們只要確立了ip和port就能找到一個應用程序,而且使用socket模塊來與之通訊。

8.3 Socket模塊

socket使用

1. socket參數的詳解

import socket
socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
# 建立socket對象的參數說明:
參數 含義
family 地址系列應爲AF_INET(默認值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。 (AF_UNIX 域其實是使用本地 socket 文件來通訊)
type 套接字類型應爲SOCK_STREAM(默認值),SOCK_DGRAM,SOCK_RAW或其餘SOCK_常量之一。 SOCK_STREAM 是基於TCP的,有保障的(即能保證數據正確傳送到對方)面向鏈接的SOCKET,多用於資料傳送。 SOCK_DGRAM 是基於UDP的,無保障的面向消息的socket,多用於在網絡上發廣播信息。
proto 協議號一般爲零,能夠省略,或者在地址族爲AF_CAN的狀況下,協議應爲CAN_RAW或CAN_BCM之一。
fileno 若是指定了fileno,則其餘參數將被忽略,致使帶有指定文件描述符的套接字返回。 與socket.fromfd()不一樣,fileno將返回相同的套接字,而不是重複的。 這可能有助於使用socket.close()關閉一個獨立的插座。

2. TCP信息傳輸

type = socket.SOCK_STREAM  # 表示tcp協議
# server 端
import socket 
sk = socket.socket()
sk.bind(('127.0.0.1'), port號)
sk.listen(n)               # 監聽連接,n 表示容許多少個客戶端等待,3.7以後無限制可省略
con,cli_addr = sk.accept() # 接受客戶端連接,阻塞,服務端須要一直監聽,不能關閉
con.recv(size)                   # 接收字節數
con.send('content'.encode('utf-8')) 
# socket 發送接收都是字節流,即二進制
con.close()                  #關閉客戶端套接字
sk.close()                               #關閉服務器套接字(可選)

# client 端
import socket
sk = socket.socket()
sk.connet(('ip', port號))
sk.send('content'.encode('utf-8'))
sk.recv(size)
sk.close()
# ip和端口占用解決方法,針對macos
import socket
from socket import SOL_SOCKET,SO_REUSEADDR # 加入一條socket配置,重用ip和端口
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)        # 就是它,在bind前加
sk.bind(('127.0.0.1',8898))                                 # 把地址綁定到套接字

3. TCP黏包問題

1. 黏包現象

  • 提升效率:爲了減小tcp協議中的確認收到的網絡延遲時間
  • 發送端:因爲兩個數據的發送時間間隔短+數據長度小,tcp協議的優化機制將兩條信息做爲一條信息發送出去
  • 接收端:因爲tcp協議中所傳輸的數據無邊界,來不及接收的多條數據會在接收端內核的緩存端黏在一塊兒
  • 本質:接收信息的邊界不清晰,主要是接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的

2. 成因機制

  1. tcp協議的拆包機制
    • 當發送端緩衝區的長度大於網卡的MTU時,tcp會將此次發送的數據拆成幾個數據包發送出去。
    • MTU是Maximum Transmission Unit的縮寫。意思是網絡上傳送的最大數據包。MTU的單位是字節大部分網絡設備的MTU都是1500
    • 若是本機的MTU比網關的MTU大,大的數據包就會被拆開來傳送,這樣會產生不少數據包碎片,增長丟包率,下降網絡速度。
  2. 面向流的通訊特色和Nagle算法
    • TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。
    • 收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無消息保護邊界的。
    • 對於空消息tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),也能夠被髮送,udp協議會幫你封裝上消息頭髮送過去。
    • 可靠黏包的tcp協議:tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包

3. Tcp黏包現象

  • 自定義協議:首先發送報頭(4bytes) ,針對發送數據大小進行提早聲明,內容是即將發送報文字節長度
    • struct:把全部數字都固定的轉換爲4bytes
    • 再發送數據信息
# 自定義協議,解決黏包問題
# server端
import struct
import socket
sk = socket.socket()
sk.bind(('ip', port))
sk.listen()
con, cli_addr = sk.accept()
size = con.recv(4)
size = struct.unpack(size)[0]           # unpack,爲一tuple類型
content = con.recv(size).decode('utf-8')# 接收文件內容
con.close()
sk.close()

# client端
import struct
import socket
sk = socket.socket()
sk.connect(('ip', port))
content  = '我是henry'.encode('utf-8')      # 字節流
size = struct.pack('i', len(content))          # 發送內容長度進行struct
sk.send(size)
sk.send(content)
sk.close()

4. UDP信息傳輸

# server
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('127.0.0.1', 9000))

msg, client_addr = sk.recvfrom(1024)
print(msg)
sk.sendto(b'received', client_addr)
sk.close()

# client
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)

sk.sendto(b'hello', ('127.0.0.1', 9000))
ret = sk.recv(1024)
print(ret)
sk.close()

Note3(2)

  1. socket收發的必須是bytes類型,通過編碼的文件均是bytes類型
  2. 網絡傳輸數據通常使用json格式

1. UDP不會發生黏包(6)

  1. UDP(user datagram protocol,用戶數據報協議)是無鏈接的,面向消息的,提供高效率服務。
  2. 不會使用塊的合併優化算法,因爲UDP支持的是一對多的模式,因此接收端的skbuff(套接字緩衝區)採用了鏈式結構來記錄每個到達的UDP包,在每一個UDP包中就有了消息頭(消息來源地址端口等信息),這樣,對於接收端來講,就容易進行區分處理了。 即面向消息的通訊是有消息保護邊界的
  3. 對於空消息:tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),也能夠被髮送,udp協議會幫你封裝上消息頭髮送過去。
  4. 不可靠不黏包的udp協議:udp的recvfrom阻塞的,一個recvfrom(x)必須對惟一一個sendto(y),收完了x個字節的數據就算完成,如果y;x數據就丟失,這意味着udp根本不會粘包,可是會丟數據,不可靠。
  5. UDP協議發送時,用sendto函數最大能發送數據的長度爲:65535- IP頭(20) – UDP頭(8)=65507字節。用sendto函數發送數據時,若是發送數據長度大於該值,則函數會返回錯誤。(丟棄這個包,不進行發送)
  6. TCP協議發送,因爲TCP是數據流協議,所以不存在包大小的限制(暫不考慮緩衝區的大小),這是指在用send函數時,數據長度參數不受限制。而實際上,所指定的這段數據並不必定會一次性發送出去,若是這段數據比較長,會被分段發送,若是比較短,可能會等待和下一次數據一塊兒發送

5. socket其餘操做

import socket
# 服務端套接字函數
s.bind()                                              # 綁定(主機,端口號)到套接字
s.listen()                                            # 開始TCP監聽
s.accept()                                          # 被動接受TCP客戶的鏈接,(阻塞式)等待鏈接的到來

# 客戶端套接字函數
s.connect()                                         # 主動初始化TCP服務器鏈接
s.connect_ex()                                    # connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常

# 公共用途的套接字函數
s.recv()                                               # 接收TCP數據
s.send()                                              # 發送TCP數據
s.sendall()                                           # 發送TCP數據

s.recvfrom()                                         # 接收UDP數據
s.sendto()                                            # 發送UDP數據

s.getpeername()                                   # 鏈接到當前套接字的遠端的地址
s.getsockname()                                   # 當前套接字的地址
s.getsockopt()                                      # 返回指定套接字的參數
s.setsockopt()                                      # 設置指定套接字的參數
s.close()                                              # 關閉套接字

# 面向鎖的套接字方法
s.setblocking()                                      # 設置套接字的阻塞與非阻塞模式
s.settimeout()                                       # 設置阻塞套接字操做的超時時間
s.gettimeout()                                       # 獲得阻塞套接字操做的超時時間

# 面向文件的套接字的函數
s.fileno()                                               # 套接字的文件描述符
s.makefile()                                           # 建立一個與該套接字相關的文件
# 官方文檔對socket模塊下的socket.send()和socket.sendall()解釋以下:
socket.send(string[, flags])
Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Returns the number of bytes sent. Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data.
# send()的返回值是發送的字節數量,這個數量值可能小於要發送的string的字節數,也就是說可能沒法發送string中全部的數據。若是有錯誤則會拋出異常。

socket.sendall(string[, flags])
Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Unlike send(), this method continues to send data from string until either all data has been sent or an error occurs. None is returned on success. On error, an exception is raised, and there is no way to determine how much data, if any, was successfully sent.
# 嘗試發送string的全部數據,成功則返回None,失敗則拋出異常。

# 故,下面兩段代碼是等價的:
sock.sendall('Hello world\n')

buffer = 'Hello world\n'
while buffer:
    bytes = sock.send(buffer)
    buffer = buffer[bytes:]

8.4 非阻塞模型

  • 阻塞io模型,非阻塞io模型,事件驅動io,io多路複用,異步io模型五種

1. 非阻塞io模型模型

  • 利用tcp能夠實現併發
  • server端使用setblocking(False)方法進行設置,此時須要使用異常處理
  • 客戶端下線時,在非阻塞狀況下,msg爲空
# server端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.setblocking(False)                               # 設置爲非阻塞狀態
sk.listen()

user = []
del_user = []
while True:
    try:
        con, addr = sk.accept()
        user.append(con)
    except BlockingIOError:
        for i in user:
            try:
                content = i.recv(1024).decode('utf-8')
                if not content:
                    del_user.append(i)
                    continue
                i.send(content.upper(). encode('utf-8')) 
                # 發送的bytes類型能夠直接解釋出(ascii字符)
            except BlockingIOError:pass            # 注意異常,會報錯
        for i in del_user:
            user.remove(i)
        del_user.clear()
sk.close()
# clinet端
import time
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 9000))
while True:
    sk.send(b'hello')
    msg = sk.recv(1024)
    print(msg)
    time.sleep(0.2)
sk.close()

Note4(3)

  1. socket的非阻塞io模型 + io多路複用實現(框架實現方式)
  2. 非阻塞提升了cpu利用率,但cpu有效率很低
  3. TCP斷開鏈接後,只要有數據發送就會報錯

2. 驗證客戶端的合法性

  • 驗證客戶端的合法性(自動化開發)
# 客戶端使用對象是用戶,直接登錄驗證
    # 能夠看到源碼,在服務端進行驗證登錄
# 客戶端使用對象是機器
  1. 摘要算法:hmac算法
import hmac
secret_key = b'asdfgh'
random_seq = os.urandom(32)
hmac.new(secret_key, random_seq)
ret = hmac.digest()                           # 結果是bytes類型數據
  1. 使用hmac驗證客戶端的合法性
# 使用TCP協議發送數據爲空時,默認不會發送
# server端
import os
import hmac
import socket

def chat(con):
    while True:
        msg = con.recv(1024).decode('utf-8')
        print('------>', msg)
        con.send(msg.upper().encode('utf-8'))
        # con.send(''.encode('utf-8'))              # tcp不會發送
        
sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.listen()

com_key = b'henry'
while True:
    con, addr = sk.accept()
    sec_key = os.urandom(32)
    con.send(sec_key)                             # 第一次發送
    val = hmac.new(com_key, sec_key).digest()
    data = con.recv(32)                           # 第一次接收
    if data == val:
        print('客戶端合法')
        chat(con)
    else:
        print('客戶端不合法')
        con.close()
sk.close()
# client 端
import socket
import hmac

def chat(sk):
    while True:
        sk.send('hello'.encode('utf-8'))
        msg = sk.recv(1024).decode('utf-8')
        print('------>', [msg])

sk = socket.socket()
sk.connect(('127.0.0.1', 9000))
sec_key = sk.recv(32)                           # 第一次接收
com_key = b'henry'
val = hmac.new(com_key, sec_key).digest()
sk.send(val)                                         # 第一次發送
chat(sk)

sk.close()
# 進階示例
from socket import *
import hmac,os

secret_key=b'henry bang bang bang'
def conn_auth(conn):
    ''' 認證客戶端連接'''
    print('開始驗證新連接的合法性')
    msg=os.urandom(32)
    conn.sendall(msg)
    h=hmac.new(secret_key,msg)
    digest=h.digest()
    respone=conn.recv(len(digest))
    return hmac.compare_digest(respone,digest)

def data_handler(conn,bufsize=1024):
    if not conn_auth(conn):
        print('該連接不合法,關閉')
        conn.close()
        return
    print('連接合法,開始通訊')
    while True:
        data=conn.recv(bufsize)
        if not data:break
        conn.sendall(data.upper())

def server_handler(ip_port,bufsize,backlog=5):
    '''只處理連接'''
    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(backlog)
    while True:
        conn,addr=tcp_socket_server.accept()
        print('新鏈接[%s:%s]' %(addr[0],addr[1]))
        data_handler(conn,bufsize)

if __name__ == '__main__':
    ip_port=('127.0.0.1',9999)
    bufsize=1024
    server_handler(ip_port,bufsize)
# 客戶端
__author__ = 'Linhaifeng'
from socket import *
import hmac,os

secret_key=b'linhaifeng bang bang bang'
def conn_auth(conn):
    '''驗證客戶端到服務器的連接'''
    msg=conn.recv(32)
    h=hmac.new(secret_key,msg)
    digest=h.digest()
    conn.sendall(digest)

def client_handler(ip_port,bufsize=1024):
    tcp_socket_client=socket(AF_INET,SOCK_STREAM)
    tcp_socket_client.connect(ip_port)
    conn_auth(tcp_socket_client)
    while True:
        data=input('>>: ').strip()
        if not data:continue            # tcp協議不支持發送數據爲空
        if data.lower() == 'q':break

        tcp_socket_client.sendall(data.encode('utf-8'))
        respone=tcp_socket_client.recv(bufsize)
        print(respone.decode('utf-8'))
    tcp_socket_client.close()

if __name__ == '__main__':
    ip_port=('127.0.0.1',9999)
    bufsize=1024
    client_handler(ip_port,bufsize)

8.5 socketserver模塊

1. socketserver模塊

# server端
import socketserver                                 # socket是socketserver的底層模塊和time,datetime同樣
class Myserver(socketserver.BaseRequestHandler):
  def handle(self):                  # 自動觸發handle方法,self.request == con
    print(self.request)                                 # con
server = socketsever.ThreadingTCPServer(('127.0.0.1', 9000), Myserver)
server.server_forever()

# client 
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 9000))

2. socketserver模塊進階

# 進階示例
import socketserver
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        self.data = self.request.recv(1024).strip()
        # self.client_address
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "127.0.0.1", 9999
    # 設置allow_reuse_address容許服務器重用地址
    socketserver.TCPServer.allow_reuse_address = True
    # 建立一個server, 將服務地址綁定到127.0.0.1:9999
    server = socketserver.TCPServer((HOST, PORT),Myserver)
    # 讓server永遠運行下去,除非強制中止程序
    server.serve_forever()
   
# client端
import socket
HOST, PORT = "127.0.0.1", 9999
data = "hello"
# 建立一個socket連接,SOCK_STREAM表明使用TCP協議
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.connect((HOST, PORT))                 # 連接到客戶端
    sock.sendall(bytes(data + "\n", "utf-8"))  # 向服務端發送數據
    received = str(sock.recv(1024), "utf-8")   # 從服務端接收數據

print("Sent:     {}".format(data))
print("Received: {}".format(received))

第九章 併發編程

9.1 操做系統基礎

  • 操做系統發展史
  • 併發和並行
  • 阻塞和非阻塞
  • 同步和異步
  • 進程:三狀態圖,惟一標示,開始和結束
  • 線程

1. 操做系統發展史

1.1 人機矛盾(cpu利用率低)

—>磁帶存儲+批處理(下降數據的讀取時間,提升cpu的利用率)

—>多道操做系統:數據隔離、時空複用(可以遇到I/O操做的時候主動把cpu讓出來,給其餘任務使用,切換須要時間,由OS完成)

—> 短做業優先算法、先來先服務算法

—>分時OS:時間分片,CPU輪轉,每個程序分配一個時間片,下降了cpu利用率提升了用戶體驗

—>分時OS + 多道OS:多個程序一塊兒執行,遇到IO切換,時間片到了也要切換

  • 多道技術介紹
  1. 產生背景:針對單核,實現併發
  2. 如今的主機通常是多核,那麼每一個核都會利用多道技術有4個cpu,運行於cpu1的某個程序遇到io阻塞,會等到io結束再從新調度,會被調度到4個 cpu中的任意一個,具體由操做系統調度算法決定。
  3. 空間上的複用:如內存中同時有多道程序
  4. 時間上的複用:複用一個cpu的時間片

Note:遇到io切,佔用cpu時間過長也切,核心在於切以前將進程的狀態保存下來,這樣
才能保證下次切換回來時,能基於上次切走的位置繼續運行。

2.2 操做系統分類(4)

OS做用:將應用程序對硬件資源的競態請求變得有序化

  • 實時OS:實時控制,實時信息處理
  • 通用OS:多道、分時、實時處理或其兩種以上
  • 網絡OS:自帶網絡相關服務
  • 分佈式OS:python中可以使用:celery模塊

2. 進程

2.1 進程:運行中的程序

  • 程序只是一個文件,進程是程序文件被cpu運行
  • 進程是計算機中最小的資源分配單位
  • 在OS中有惟一標示符PID

2.2 OS調度算法(4)

  • 短做業優先
  • 先來先服務
  • 時間片輪轉
  • 多級反饋算法:分時+先來先服務+短做業優先

2.3 並行與併發

  1. 併發(concurrency):把任務在不一樣的時間點交給處理器進行處理。看起來程序同時執行,實際在同一時間點,任務並不會同時運行。
  2. 並行(parallelism):把每個任務分配給每個處理器獨立完成。在同一時間點,任務必定是同時運行。
  3. 併發的本質:切換+保存狀態

3. 同步異步阻塞非阻塞

  1. 同步:調用一個函數/方法,須要等待這個函數/方法結束
    • 一個功能調用時,沒有獲得結果以前,就不會返回,能夠說是一種操做方式。
  2. 異步:程序同時運行,沒有依賴等待關係,調用一個方法,不等待這個方法結束,不關心這個方法作了什麼
  3. 阻塞:cpu不工做
    • 阻塞調用是指調用結果返回以前,當前線程會被掛起。函數只有在獲得結果以後纔會返回。
  4. 非阻塞:cpu工做
    • 指調用在不能馬上獲得結果以前,該調用不會阻塞當前線程
  5. 同步阻塞
    • con.recv(),socket阻塞的tcp協議
  6. 同步非阻塞
    • 沒有io操做的func()
    • socket非阻塞tcp協議; 調用自定義函數(不存在io操做)
  7. 異步非阻塞(重點)
    • 沒有io操做,把func扔到其餘任務裏各自執行,cpu一直工做
  8. 異步阻塞
    • 程序中出現io操做

Note1(2)

  1. 同步和異步關注的是消息通訊機制 (synchronous communication/ asynchronous communication)
  2. 阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態

4. 進程的三狀態圖

1. 進程狀態

進程狀態:運行(runing) 就緒(ready) 阻塞(blocking)

2. 進程的建立與結束

  • 進程建立
  1. 系統初始化(ps)
  2. 一個進程開啓了一個子進程(os.fork,subprocess.Popen)
  3. 用戶交互式請求(用戶雙擊app)
  4. 批處理做業的初始化(只在大型機的批處理系統中應用)
  • 進程結束
  1. 正常退出
  2. 出錯退出
  3. 嚴重錯誤
  4. 被其餘進程殺死(kill -9 pid)

9.2 進程

1. 進程與線程

1.1 分別作多件事

  • 若是是兩個程序分別作兩件事
    • 起兩個進程
  • 若是是一個程序,要分別作兩件事
    • 起線程,視頻軟件:下載電影1,電影2,電影3

1.2 進程解析

  1. 進程Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度基本單位,是OS結構的基礎。

  2. 在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。

  3. 顧名思義,進程即正在執行的一個過程。進程是對正在運行程序的一個抽象。

  4. 進程的概念起源於操做系統,是操做系統最核心的概念,也是操做系統提供的最古老也是最重要的抽象概念之一。操做系統的其餘全部內容都是圍繞進程的概念展開的。

    PS:即便能夠利用的cpu只有一個(早期的計算機確實如此),也能保證支持(僞)併發的能力。將一個單獨的cpu變成多個虛擬的cpu(多道技術:時間多路複用和空間多路複用+硬件上支持隔離),沒有進程的抽象,現代計算機將不復存在。

  5. 進程概念

    • 進程是一個程序實體。每一個進程都有它本身的地址空間,通常狀況下,包括文本區域(text region)、數據區域(data region)和堆棧(stack region)。文本區域存儲處理器執行的代碼;數據區域存儲變量和進程執行期間使用的動態分配的內存;堆棧區域存儲着活動過程調用的指令和本地變量
    • 進程是一個「執行中的程序」。程序是一個沒有生命的實體,只有處理器賦予程序生命時(操做系統執行之),它才能成爲一個活動的實體,咱們稱其爲進程。
    • 進程是操做系統中最基本、重要的概念。是多道程序系統出現後,爲了刻畫系統內部出現的動態狀況,描述系統內部各道程序的活動規律引進的一個概念,全部多道程序設計操做系統都創建在進程的基礎上。
  6. 特色

    • 是計算機中最小的資源分配單位數據隔離
    • 建立、銷燬、切換進程時間開銷大
    • 能夠利用多核

1.3 線程(5)

  1. 線程是進程中的一部分,不能脫離進程存在
  2. 任何進程中至少有一個線程,負責執行代碼,不負責存儲共享的數據,也不負責資源分配
  3. 建立、銷燬和切換的開銷遠遠小於進程
  4. 線程是計算機中能被cpu調度的最小單位
    • 爬蟲使用須要配合前端
  5. 一個進程中的多個線程能夠共享這個進程的數據—— 數據共享

1.4 開銷

  1. 線程的建立和銷燬
    • 須要一些開銷(一個存儲局部變量的數據結構,記錄狀態)
    • 建立、銷燬、切換開銷遠小於進程
  2. python中的線程比較特殊,因此進程也有可能被用到
  3. 進程:數據隔離、開銷大、數據不安全、能夠利用多核、os切換
  4. 線程:數據共享、開銷小 、數據不安全、不能夠利用多核、os切換

2. 進程的建立

  • 仔細說來,multiprocess不是一個模塊而是python中一個操做、管理進程的包。 之因此叫multi是取自multiple的多功能的意思,在這個包中幾乎包含了和進程有關的全部子模塊。因爲提供的子模塊很是多,爲了方便你們歸類記憶,我將這部分大體分爲四個部分:建立進程部分,進程同步部分,進程池部分,進程之間數據共享

2.1 multiprocessing

  • 基於process模塊
# 獲取進程的pid, 父進程的id及ppid
import os
import time
print('start')
time.sleep(20)
print(os.getpid(),os.getppid(),'end')

2.2 子進程和父進程

  1. pycharm中啓動的全部py程序都是pycharm的子進程
# 把func函數交給子進程執行
import os
import time
from multiprocessing import Process

def func():
  print('start', os.getpid())
  time.sleep(1)
  print('end', os.getpid())

if __name__ == '__main__':    
  p = Process(target=func)              # 建立一個即將要執行的進程對象
  p.start()                                             # 開啓一個進程,異步非阻塞
  p.join()                              # 同步阻塞,直到子進程執行完畢
  print('main', os.getpid())                    # 異步的程序,調用開啓進程的方法,並不等待這個進程的開啓
  1. 建立子進程注意

ps:__name__ 只有兩種狀況,文件名雙下劃線main字符串

# windows
經過(模塊導入)執行父進程文件中的代碼獲取父進程中的變量
只要是不但願被子進程執行的代碼,就寫在if __name__ == '__mian__'下
進入導入時,父進程文件中的 __name__ != '__mian__'
# linux/macos
建立新的子進程是copy父進程內存空間,完成數據導入工做(fork),正常寫就能夠

公司開發環境都是linux,無需考慮win中的缺陷
# windows中至關於把主進程中的文件又從頭執行了一遍

# linux,macos不執行代碼,直接執行調用的函數在Windows操做系統中因爲沒有fork(linux操做系統中建立進程的機制),在建立子進程的時候會自動 import 啓動它的這個文件,而在 import 的時候又執行了整個文件。所以若是將process() 直接寫在文件中就會無限遞歸建立子進程報錯。因此必須把建立子進程的部分使用if __name__ ==‘__main__’ 判斷保護起來,import 的時候  ,就不會遞歸運行了。
  • 父進程(主進程)存活週期

Note2(5)

  1. 進程之間不能共享內存
  2. 主進程須要等待子進程結束,主進程負責建立回收子進程
  3. 子進程執行結束,若父進程沒有回收資源,即殭屍進程。
  4. 主進程結束邏輯:主進程代碼結束、全部子進程結束、回收子進程資源、主進程結束
  5. 進程裏面沒法進行input
    • 全部print都是向文件裏數據
    • 只有主進程支持input操做,子進程不支持,會報錯EOFError: EOF when reading a line

2.3 join方法

  • 把一個進程的結束事件封裝成一個join方法
  • 執行join方法效果,阻塞,直到子進程結束,就結束
  • 全部的進程執行的前後是由OS控制的
# 在多個子進程中使用join方法
from multiprocessing import Process
def send_mail(i):
    print('郵件已發送', i)
if __name__ == '__main__':
    li = []
    for i in range(10):
        p = Process(target=send_mail, args=(i,))  # args必須是元組,給子進程中的函數傳參數
        p.start()
    li.append(p)
    for p in li: p.join()               # 阻塞,知道全部子進程執行完畢
    print('100封郵件已發送')
# 主線程等待p終止(強調:是主線程處於等的狀態,而p是處於運行的狀態)。timeout是可選的超時時間,須要強調的是,p.join只能join住start開啓的進程,而不能join住run開啓的進程

9.3 鎖&進程間通訊

1. Process類

1.1 守護進程

  • 生產者消費者模型
  • 和守護線程作對比

p.daemon:默認值爲False,若是設爲True,表明p爲後臺運行的守護進程,當p的父進程終止時,p也隨之終止,而且設定爲True後,p不能建立本身的新進程,必須在p.start()以前設置

import time
from multiprocessing import Process

def son1():
    while True:
        print('is alive')
        time.sleep(0.5)
    
def son2():
    for i in range(5):
      print('in son2')
      time.sleep(1)
              
if __name__ == '__main__':
    p = Process(target=son1)
    p.daemon = True                               # 把p子進程設置成了守護進程    
    p.start()
    p2 = Process(target=son2)
    p2.start()
    time.sleep(2)
# 守護進程是隨着主進程‘代碼’結束而結束
# 全部子進程都必須在主進程結束以前結束
# 守護進程內沒法再開啓子進程,不然拋出異常:AssertionError: daemonic processes are not allowed to have children

1.2 Process的方法

  • p.terminate(), p.is_alive(),current_process(current_process.ident/ name/ pid)
  • 異步非阻塞
    • 使用terminate方法後,再查看進程是否還存活時,會發現進程還存活,並無等待OS關閉進程,說明terminate方法請求後,程序會繼續執行
import time
from multiprocessing import Process

def son1():
    while True:
        print('is alive')
        time.sleep(0.5)
              
if __name__ == '__main__':
    p = Process(target=son1)
    p.start()                                           # 開啓了一個進程
    print(p.is_alive)                             # 判斷子進程時候存活, True和False
    time.sleep(1)
    p.terminate()                                     # 「異步非阻塞」,強制結束一個子進程
    print(p.is_alive)                                  # True,os還沒來得及關閉進程
    time.sleep(0.01)
    print(p.is_alive)                                  # False,OS已經響應了關閉進程的需求,再去檢測的時候,結果是進程已經結束

1.3 面向對象開啓進程

  • 當建立子進程須要傳參時,須要使用**super().__init__()**
import os
import time
from multiprocessing import Process

class MyProcess(Process):
    def __init__(self, x, y):                      # 子進程若是不須要參數,能夠省略
        self.x = x
        self.y = y
        super().__init__()        
        
    def run(self):
        while True:
            print(self.x, self.y, os.getpid())
            print('in myprocess')

if __name__ == '__main__':
    mp = MyProcess(1, 2)
    mp.daemon = True
    mp.start()                                        # 開啓一個子進程,會調用run()方法
    time.sleep(1)
    mp.terminate()               # 結束進程,異步非阻塞                       
    print(mp.is_alive())                 # True
    time.sleep(0.01)                
    print(mp.is_alive())                 # False
  • p.join() : 同步阻塞
  • p.terminate() 和 p.start():異步非阻
  • p.is_alive():獲取當前進程狀態
  • daemon = True:設置爲守護進程,守護進程在主進程代碼執行結束而結束

2. 鎖

  1. 在併發的場景下,設計某部分的內容
    • 須要修改一些全部進程共享數據資源
    • 須要加鎖來維護數據的安全
  2. 在數據安全的基礎上,才考慮效率問題
    • with lock:內部有異常處理
    • 在主進程中進行實例化
    • 把鎖傳遞給子進程
  3. 在子進程中對須要加鎖的代碼進行with lock
    • with lock:至關於lock.acquire()和lock.release()
  4. 須要加鎖的場景
    • 操做共享的數據資源
    • 對資源進行修改操做
    • 加鎖以後可以保證數據的安全性,但會下降程序執行效率
# 數據操做時,不能同時進行修改
import json
from multiprocessing import Process, Lock  # 導入Lock

def search_ticket(user):
    with open('tickets.txt') as f:
        dic = json.load(f)
        print('%s查詢結果:%s張餘票' %(user, dic['count']))

def buy_ticket(user, lock):
    # with lock:
    lock.acquire()
    # time.sleep(0.01)
    with open('tickets.txt') as f:
        dic = json.load(f)
    if dic["count"] > 0:
        print('%s已買到票' % user)
        dic["count"] -= 1
    else:
        print('%s沒買到票' % user)
    with open('tickets.txt', 'w') as f:
        json.dump(dic, f)
    lock.release()


if __name__ == '__main__':
    lock = Lock()                                    # 實例化一個對象
    for i in range(10):
        search_ticket('user%s '%(i+1),)
        p = Process(target=buy_ticket, args=('user%s '%(i+1), lock))
        p.start()

3. 進程間的通訊

3.1 進程間數據隔離

from multiprocessing import Process
n = 100
def func():
    global n
  n -= 1
 
li = []
for i in range(10):
  p = Process(target=func)
  p.start()
  li.append(p)
  
 for p in li:p.join()
 print(n)

3.2 進程間通訊

  • 文件或網絡只有這兩種
  • 進程間通訊(IPC, inter-process communication):Queue和Pipe
  • Queue(3):先進先出,文件家族的socket,寫文件基於pickle,基於Lock
    • 數據安全,Pipe管道:天生數據不安全(socket通訊)
    • Queue = Pipe(socket + picket)+Lock
  • 第三方提供(5):redis,memcache,kafka,rabbitmq(消息中間件(消息轉發))
    • 併發需求
    • 高可用
    • 實現集羣的概念
    • 斷電保存數據
    • 解耦
from multiprocessing import Process,Queue

def func(exp,q):
    res = eval(exp)
    q.put(res)

if __name__ == '__main__':
    q = Queue()
    p = Process(target=func, args=('1+2+3',q))
    p.start()
    print(q.get())
from multiprocessing import Pipe
pip = Pipe()
pip.send()
pip.recv()
# Process中的隊列
import queue
from multiprocessing import Queue
q = Queue(3)                    # 可設置隊列長度
q.put(1)
q.put(2)                        # 對列爲滿時,繼續放數據會發生阻塞
q.put(3)
print('----------')
try:
    q.put_nowait(4)                           # 對列爲滿時,繼續放數據會報錯和丟失
except queue.Full:pass
print('----------')

q.get()
q.get()
q.get()                                                # 對列爲空時,會發生阻塞
try:
    q.get_nowait()               # 對列爲空時,會報錯,阻塞會取消
except queue.Empty:pass
q.empty()                                               # 有缺陷
q.qsize()
q.full()

9.4 cp模型&線程

1. 生產者消費者模型

1.1 程序的解耦

  • 把寫在一塊兒的功能分開成多個小的功能處理
    • 修改和複用,增長可讀性
    • 計算速度有差別,執行效率最大化,節省進程
  • 生產者:生產數據
  • 消費者:處理數據

1.2 生產者和消費者

  1. 一個進程就是一個生產者/消費者
  2. 生產者和消費者之間的容器就是隊列(隊列有大小,控制內存消耗)
# 生產者消費者模型示例
import time
import random
from multiprocessing import Process, Queue

def producer(q, name, food):
    for i in range(10):
        time.sleep(random.random())
        fd = '%s%s' % (food, i)
        q.put(fd)
        print('%s生產了一個%s' % (name, food))

def consumer(q, name):
    while True:
        food = q.get()
        if not food:
            q.put(None)
            break
        time.sleep(random.randint(1, 3))
        print('%s吃了%s' % (name, food))

def cp(num1, num2):
    q = Queue(10)
    p_l = []
    for i in range(num1):
        p = Process(target=producer, args=(q, 'henry', 'food'))
        p.start()
        p_l.append(p)
    for i in range(num2):
        c = Process(target=consumer, args=(q, 'echo%s' % (i+1,)))
        c.start()
    for i in p_l:
        i.join()
    q.put(None)

if __name__ == '__main__':
    cp(1, 4)
# 生產者消費者模型示例之爬蟲
import re
import requests
from multiprocessing import Process, Queue

def producer(q, url):
    response = requests.get(url)
    q.put(response.text)

def consumer(q):
    while True:
        s = q.get()
        if not s:
            q.put(None)
            break
        com = re.compile(
            '<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
            '.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)評價</span>', re.S)
        ret = com.finditer(s)
        for i in ret:
            print({
                "id": i.group("id"),
                "title": i.group("title"),
                "rating_num": i.group("rating_num"),
                "comment_num": i.group("comment_num"),
            })

if __name__ == '__main__':
    count = 0
    q = Queue()
    p_l = []
    for i in range(10):
        count += 25
        p = Process(target=producer, args=(q, 'https://movie.douban.com/top250?start=%s&filter=' % count))
        p.start()
        p_l.append(p)
    for i in range(5):
        c = Process(target=consumer, args=(q,))
        c.start()
    for i in p_l:
        i.join()
    q.put(None)

1.3 joinablequeue

  1. q.join():阻塞,直到隊列中全部內容被取走且q.task_done
    • 生產者將使用此方法進行阻塞,直到隊列中全部項目均被處理。阻塞將持續到爲隊列中的每一個項目均調用
  2. 先設置消費者爲守護進程
    • c.daemon = True
  3. 阻塞生產者
    • 其中的隊列阻塞結束後,纔會結束
  4. 在生產者中使用阻塞隊列
    • 阻塞一結束,全部數據都已經消費完
  5. 隊列阻塞結束表明消費者,把全部生產數據消費完(jq.taks_done()操做
    • 使用者使用此方法發出信號,表示q.get()返回的項目已經被處理。若是調用此方法的次數大於從隊列中刪除的項目數量,將引起ValueError異常。

# joinable實現生產者、消費者模型
import time
import random
from multiprocessing import Process,JoinableQueue

def producer(q, name, food):
  for i in range(10):
    time.sleep(random.random())
    fd = '%s%s'%(food,i)
    print('%s生產了一個%s'%(name, food))
  q.join()

def consumer(q, name, food):
  while True:
    food = q.get()
    if not food:
      q.put(None)
      break
    time.sleep(random.randint(1, 3))
    print('%s吃了%s'%(name, food))
    q.task_done()
 
if __name__ = '__main__':
  jq = JoinableQueue()
  p = Processor(target=producer, args=(jq, 'henry', 'food'))
  p.start()

2. 進程間數據共享

  1. 與數據共享相關:Manager模塊(Manager().list(), Manager().Queue)
  2. multiprocessing 中有一個manager類
  3. 封裝了全部和進程相關的數據共享數據傳遞
  4. 可是對於dict、list這類進行數據操做時,會產生數據不安全
  5. m = Manager()也可使用with Manager() as m:
# 進程間數據是獨立的,能夠藉助於隊列或管道實現通訊,兩者都是基於消息傳遞的
# 雖然進程間數據獨立,但能夠經過Manager實現數據共享,事實上Manager的功能遠不止於此
A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.

A manager returned by Manager() will support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array.
from mutliprocessing import Manager,Lock
def func(dic, lock):
  with lock:
        dic['count'] -= 1

if __name__ = '__main__':
  # m = Manager()
  lock = Lock()
  with Manager() as m:
    l = Lock()
    dic = m.dict({'count': 100})             # 共享的dict,list li = m.list([1,2,3])
    p_l = []
    for i in range(100):
      p = Process(target=func, args=(dic,lock))
      p.start()
      p_l.append(p)
    for p in p_l:p.join()
    print(dic)

3. 線程

  1. 調度和切換:線程上下文切換比進程上下文切換要快得多。
  2. 四核八線程
    • 每一個核心被虛擬成兩個核心,能夠同時執行8個線程。
    • 若是是計算複雜數據,會轉換到四核
    • https:加證書,須要購買
  3. 在多線程的操做系統中,一般是在一個進程中包括多個線程,每一個線程都是做爲利用CPU的基本單位,是花費最小開銷的實體。
  4. 線程具備如下屬性(4)
    1. 線程中的實體基本上不擁有系統資源,只是有一點必不可少的、能保證獨立運行的資源
      • 獨立調度和分派的基本單位。
      • 在多線程OS中,線程是能獨立運行的基本單位,於是也是獨立調度和分派的基本單位。因爲線程很「輕」,故線程的切換很是迅速且開銷小(在同一進程中的)。
    2. 線程的實體包括程序數據TCB線程是動態概念,它的動態特性由線程控制塊TCB(Thread Control Block)描述。
      • 線程狀態。
      • 當線程不運行時,被保存的現場資源。
      • 一組執行堆棧。
      • 存放每一個線程的局部變量主存區
      • 訪問同一個進程中的主存和其它資源。
      • 用於指示被執行指令序列的程序計數器保留局部變量少數狀態參數返回地址等的一組寄存器和堆棧。
    3. 共享進程資源
      • 線程在同一進程中的各個線程,均可以共享該進程所擁有的資源,
      • 這首先表如今:全部線程都具備相同的進程id,這意味着,線程能夠訪問該進程的每個內存資源;
      • 此外,還能夠訪問進程所擁有的已打開文件定時器信號量機構等。因爲同一個進程內的線程共享內存和文件,因此線程之間互相通訊沒必要調用內核。
    4. 可併發執行
      • 在一個進程中的多個線程之間,能夠併發執行,甚至容許在一個進程中全部線程都能併發執行;
      • 一樣,不一樣進程中的線程也能併發執行,充分利用和發揮了處理機與外圍設備並行工做的能力。

3.1垃圾回收機制

  1. cpython解釋器不能實現多線程利用多核
  2. 垃圾回收機制(gc):引用計數 + 分代回收
    • 專門有一條線程完成垃圾回收機制,對每個在程序中的變量統計引用計數

3.2 GIL鎖

GIL(global interpreter lock):全局解釋器鎖

  1. 保證了整個python程序中,只能有一個線程被CPU執行
    • 致使了py程序不能並行
    • 使用多線程並不影響高IO型操做,只會對高計算型程序有效率的影響
    • 遇到高計算:多進程+多線程,分佈式
  2. 緣由:cpython解釋器中特殊的垃圾回收機制
  3. cpython、pypy,jpython(先翻譯爲java字節碼,在java上執行)、iron python

3.3 遇到IO操做的時候

  1. 5-6億條cpu指令
  2. 5-6cpu指令 == 一句python代碼
  3. 幾千萬條python代碼

3.4 web框架幾乎都是多線程

  • 利用IO操做,相似多道系統

4. python操做線程(重點)

4.1 開啓線程

  • 使用Threading
# multiprocessing 是徹底仿照Threading類的
import os
import time
from threading imoprt Thread
def func():
  print('start son thread')
  time.sleep(1)
  print('end son thread', os.getpid())
  
Thread(target=func).start()                    # 開啓一個線程的速度很是快
print('start')
time.sleep(0.5)
print('end', os.getpid())
# 開啓多個線程
import os
import time
from threading imoprt Thread
def func():
  print('start son thread')
  time.sleep(1)
  print('end son thread', os.getpid())
 
for i in range(10):
  Thread(target=func).start()                # 開啓一個線程的速度很是快
                               # 線程的調度由OS決定

4.2 join方法

  1. join阻塞知道子線程執行結束
  • 主線程若是結束了,主進程也就結束了
  • 主線程須要等待全部子線程結束才能結束
import os
import time
from threading imoprt Thread
def func():
    print('start son thread')
    time.sleep(1)
    print('end son thread', os.getpid())

t_l = []
for i in range(10):
    t = Thread(target=func)
    t.start()
    t_l.append(t)
for i in t_l:i.join()
print('子線程執行結束')

4.3 面向對象啓動線程

  • self.ident / current_thread:查看線程id
  • enumerate / active_count:查看線程存活狀況
from threading import Thread

class MyThread(Thread):
    def __init__(self, i):
        self.i = i
        super().__init__()                          # 注意變量名避免與父類init中的變量重名  

    def run(self):
        print(self.i, self.ident)                    # 經過self.ident,查看子線程的id

t_l = []
for i in range(100):
    t = MyThread(i)
    t_l.append(t)
    t.start()                                            # start 方法封裝了開啓線程的方法和run方法

for t in t_l: t.join()
print('主進程結束')

4.4 線程中的其餘方法(3大類)

from threading import current_thread, enumerate, active_count

def func():
    print('start son thread', i , current_thread())
    time.sleep(1)
    print('end son thread', os.getpid())

t = Thread(target=func)
t.start()
print(t.ident)
print(current_thread().ident)                  # current_ident()在哪一個線程,就獲得這個線程id
print(enumerate())              # 統計當前進程中多少線程活着,包含主線程
print(active_count())               # 統計當前進程中多少線程活着,個數,包含主線程
                                                        # 線程中不能從主線程結束一個子線程
  
  
current_thread().name               # 當前線程名稱
current_thread().ident              # 當前線程id
current_thread().isalive()          # 當前線程是否存活
current_thread().isdaemon()         # 當前線程是不是守護線程

4.5 效率差

import time
from threading import Thread
from multiprocessing import Process
def func(a, b):
    c = a + b
 
if __name__ == '__main__':
    p_l = []
    start = time.time()
    for i in range(100):
        p = Process(target=func, args=(i, i*2))
        p.start()
        p_l.append(p)
     for i in P_l:i.join()
     print(time.time() - start)
    
     t_l = []
     start = time.time()
     for i in range(100):
         t = Thread(target=func, args=(i, i*2))
         t.start()
         t_l.append(t)
     for i in t_l:i.join()
     print(time.time() - start)

4.6 數據共享

# 不要在子線程裏隨便修改全局變量
from threading import Thread
n = 100
def son():
  global n
  n -= 1

t_l = []
for i in range(100):
        t = Thread(target=son)
    t_l.append(t)
    t.start()
    
for t in t_l:t.join()
print(n)

4.7 守護線程

  • 守護線程會一直等到全部非守護線程結束以後才結束
  • 除了守護主線程的代碼以外,也會守護子線程
  • 只要有非守護線程存在,主進程就不會結束
import time
from threading imoprt Thread
def son():
    while True:
            time.sleep(0.5)

def son2():
    for i in range(5):
      
t = Thread(target=son)
t.daemon = True
t.start()
time.sleep(3)

Note3(2)

  1. 主進程來講,運行完畢指的是主進程代碼運行完畢
    • 主進程在其代碼結束後就已經運行完畢了(守護進程在此時就被回收),而後主進程會一直等非守護進程都運行完畢後回收子進程資源(不然會產生殭屍進程),纔會結束。
  2. 主線程來講,運行完畢指的是主線程所在的進程內全部非守護線程執行完畢
    • 主線程在其餘非守護線程運行完畢後纔算運行完畢(守護線程在此時就被回收),由於主線程的結束意味着進程的結束,進程總體的資源都將被回收,而進程必須保證非守護線程都運行完畢後才能結束。

9.5 鎖&池

1. 互斥鎖

1.1 互斥鎖

# 線程數據一樣不安全
import dis
a = 0
def func():
    global a
    a += 1
dis.dis(func)                                       # 返回cpu指令
  • 即使是線程,有GIL鎖, 也會出現數據不安全的問題
  • STORE_GLOBAL:一旦有這種方法,就會有數據安全問題
  • 操做是全局變量
  • 操做如下方法
    • += , -= , *=, /=(在操做線程全局變量時,注意)
    • li[0] += 1, dic['key'] -= 1
    • 對同一文件進行寫操做
# 使用互斥鎖解決線程全局變量數據不安全問題
from threading import Thread, Lock
a = 0
def add_f(lock):
    global a
    with lock:
        for i in range(2000000):
            a += 1
def sub_f(lock):
    global a
    with lock:
        for i in range(2000000):
            a -= 1
lock = Lock()
t1 = Thread(target=add_f, args=(lock,))
t1.start()
t2 = Thread(target=sub_f, args=(lock,))
t2.start()
t1.join()
t2.join()
print(a)

互斥鎖:是鎖中的一種,在同一線程中,不能連續lock.acquire()屢次

from threading import Lock
lock = Lock()
lock.acquire()
print('-------------')
lock.acquite()
print('-------------')

1.2 單例模式

import time
import random
from threading import Thread

class Singleton:
    from threading import Lock                 # 複用性考慮
    __instance = None
    lock = Lock()
    
    def __new__(cls, *args, **kwargs):
        with cls.lock:
            if not cls.__instance:
                time.sleep(random.random())   # 切換GIL鎖
                cls.__instance = super().__new__(cls)
        return cls.__instance

def fun():
    print(Singleton())

li = []
for i in range(10):
    t = Thread(target=fun)
    li.append(t)
    t.start()
for t in li: t.join()

2. 死鎖

2.1 緣由(2)

  • 多把鎖(一把以上)
  • 多把鎖交替使用
from threading import Thread,Lock
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name,noddle_lock, fork_lock):
    noddle_lock.acquire()
    print('%s搶到面了'%name)
    fork_lock.acquire()
    print('%s搶到叉子了'%name)
    print('%s吃了一口面'%name)
    noddle_lock.release()
    print('%s放下面了'%name)
    fork_lock.release()
    print('%s放下叉子了'%name)
 
def eat2(name,noddle_lock, fork_lock):
    fork_lock.acquire()
    print('%s搶到叉子了'%name)
    noddle_lock.acquire()
    print('%s搶到面了'%name)
    print('%s吃了一口面'%name)
    noddle_lock.release()
    print('%s放下面了'%name)
    fork_lock.release()
    print('%s放下叉子了'%name)

2.2 解決方案

  1. 遞歸鎖
    • 遞歸鎖在同一線程中,能夠連續acquire屢次不會阻塞
    • 本質:一把鎖
    • acquire和release次數要一致
    • 優勢:在同一線程中屢次acquire也不會發生阻塞
    • 缺點佔用了更多的資源
  2. 多把遞歸鎖也會產生死鎖現象
    • 使用遞歸鎖,永遠不要使用多把
    • 互斥鎖效率更高,遞歸鎖效率較低
import time
from threading import RLock, Thread
noodle_lock = fork_lock = RLock()          # 將多把互斥鎖變成了一把遞歸鎖

def eat1(name, noodle_lock, fork_lock):
    noodle_lock.acquire()
    print('%s搶到面了' % name)
    fork_lock.acquire()
    print('%s搶到叉子了' % name)
    print('%s吃了一口面' % name)
    time.sleep(0.1)
    fork_lock.release()
    print('%s放下叉子了' % name)
    noodle_lock.release()
    print('%s放下面了' % name)

def eat2(name, noodle_lock, fork_lock):
    fork_lock.acquire()
    print('%s搶到叉子了' % name)
    noodle_lock.acquire()
    print('%s搶到面了' % name)
    print('%s吃了一口面' % name)
    time.sleep(0.1)
    noodle_lock.release()
    print('%s放下面了' % name)
    fork_lock.release()
    print('%s放下叉子了' % name)

lst = ['henry', 'echo', 'dean', 'daniel']
Thread(target=eat1, args=(lst[0], noodle_lock, fork_lock)).start()
Thread(target=eat2, args=(lst[1], noodle_lock, fork_lock)).start()
Thread(target=eat1, args=(lst[2], noodle_lock, fork_lock)).start()
Thread(target=eat2, args=(lst[3], noodle_lock, fork_lock)).start()
  1. 代碼優化
# 使用互斥鎖解決問題
import time
from threading import Lock, Thread

lock = Lock()
def eat1(name, lock):
    lock.acquire()
    print('%s搶到面了' % name)
    print('%s搶到叉子了' % name)
    print('%s吃了一口面' % name)
    time.sleep(0.1)
    print('%s放下叉子了' % name)
    print('%s放下面了' % name)
    lock.release()

def eat2(name, lock):
    lock.acquire()
    print('%s搶到叉子了' % name)
    print('%s搶到面了' % name)
    print('%s吃了一口面' % name)
    time.sleep(0.1)
    print('%s放下面了' % name)
    print('%s放下叉子了' % name)
    lock.release()

lst = ['henry', 'echo', 'dean', 'daniel]
Thread(target=eat1, args=(lst[0], lock)).start()
Thread(target=eat2, args=(lst[1], lock)).start()
Thread(target=eat1, args=(lst[2], lock)).start()
Thread(target=eat2, args=(lst[3], lock)).start()

3. 隊列

  • 線程之間的通訊,線程安全
import queue
# 先進先出隊列:服務
from queue import Queue           
q = Queue(5)
q.put(1)
q.get()
# 後進先出隊列:算法
from queue import LifoQueue
lfq = LifiQueue(4)
lfq.put(1)
lfq.put(2)
lfq.get()
lfq.get()
# 優先級隊列:自動排序、vip用戶、告警級別
from queue import PriorityQueue
pq = PriorityQueue()
pq.put((10, 'henry'))
pq.put((6, 'echo'))
pq.put((10, 'dean'))
pq.get()
pq.get()
pq.get()
  • FIFO:全部的請求放在對列裏,先來先服務思想
  • LIFO:通常用於算法

4. 池

  • 進程,線程開啓關閉切換須要時間
  • 進程池通常和cpu核心說有關,個數通常爲cpu核心數或加一
  • 節省了進程建立和銷燬的時間

4.1 池

  • 預先開啓固定個數的進程數,當任務來臨時,直接提交給開好的進程,讓這個進行執行,從而減輕了os調度的負擔

4.2 concurrent

  • concurrent.futures模塊(3)
  • 3.4版本以後出現
  • 進程池
# 進程池。 p.submit, p.shutdown
import os,time, random
from concurrent.futrures import ProcessPoolExecutor

def func(i):
    print('start', os.getpid())
    time.sleep(random.randint(1,3))
    print('end', os.getpid())
    return '%s * %s' %(i, os.getpid())

if __name__ == '__main__':
    p = ProcessPoolExecutor(5)              # cpu核心數或多一
    ret_l = []
    for i in range(10):
        ret = p.submit(func, i)         # 提交進程,參數直接放在其後
        ret_l.append(ret)               # ret爲future對象,ret.result()取值
    # 關閉池,不能提交任務,阻塞,直到已經提交的任務執行完畢
    p.shutdown()
    for ret in ret_l:                                # 會阻塞,至關於q.get()
        print('------>',ret.result())          # result,同步阻塞
    print('main', os.getpid())
  • 一個進程池中的任務個數,限制了咱們程序的併發個數
  • 線程池
# 線程池。p.submit(), p.shutdown(), ret.result()
from concurrent.futures import ThreadPoolExecutor

def func(i):
    print('start', os.getpid())
    time.sleep(random.randint(1,3))
    print('end', os.getpid())
    return '%s * %s' %(i, os.getpid())

tp = ThreadPoolExecutor(20)               # 線程個數通常爲cpu核心數4-5倍
ret_l = []
for i in range(100):
        ret = tp.submit(func, 1)
    ret_l.append(ret)
for ret in ret_l:
    print('------->', ret.result())
p.shutdown()
print('main')
  • tp.map(func, 可迭代對象):參數只能傳輸一個
# 線程池。p.submit(), p.shutdown(), ret.result()
from concurrent.futures import ThreadPoolExecutor

def func(i):
    print('start', os.getpid())
    time.sleep(random.randint(1,3))
    print('end', os.getpid())
    return '%s * %s' %(i, os.getpid())

tp = ThreadPoolExecutor(20)                  # 線程個數通常爲cpu核心數4-5倍
ret = tp.map(func, range(20))         # tp.map()方法
for i in ret:print(i)                     # ret 爲生成器對象

4.3 回調函數

  • ret.add_done_callback:回調函數
  • 先來先響應,會提升總體的處理速度
  • 會監聽obj值:直到obj.result()有值爲止
from concurrent.futures import ThreadPoolExecutor
def get_page(url):
    content = requests.get(url)
    return {'url':url, 'content':content.text}
def parserpage(dic):
    print(dic.result()['url'])

for url in url_l:
    ret = get_page(url)
    ret.add_done_callback(parserpage)     # 先執行完,先調用parserpage函數
                                  # ret=add_done_callback(函數名)
from concurrent.futures import ProcessPoolExecutor
import requests
import os

def get_page(url):
    print('<進程%s> get %s' % (os.getpid(), url))
    respone = requests.get(url)
    if respone.status_code == 200:
        return {'url': url, 'text': respone.text}

def parse_page(res):
    res = res.result()
    print('<進程%s> parse %s' % (os.getpid(), res['url']))
    parse_res = 'url:<%s> size:[%s]\n' % (res['url'], len(res['text']))
    with open('db.txt', 'a') as f:
        f.write(parse_res)

if __name__ == '__main__':
    urls = ['https://www.baidu.com', 'https://www.openstack.org', 'http://www.sina.com.cn/', 'https://www.tencent.com']
    p = ProcessPoolExecutor(3)
    for url in urls:
        p.submit(get_page, url).add_done_callback(parse_page)  # parse_page拿到的是一個future對象obj,須要用obj.result()拿到結果

9.6 協程

1. 協程概念

  1. 協程:是單線程下的併發,又稱微線程纖程。英文名Coroutine。一句話說明什麼是協程:協程是一種用戶態的輕量級線程,即協程是由用戶程序本身控制調度的,多個任務在一條線程上來回切換。

  2. 協程:用戶級別,本身寫的py代碼;控制切換是OS不可見的

    1. 一個線程中的阻塞都被其餘的各類任務佔滿了
    2. 讓OS認爲這個線程很忙,儘可能減小這個線程進入阻塞狀態
    3. 提升了單線程對cpu的利用率
      • 多個任務在同一個線程中執行,也達到了一個併發的效果
    4. 規避了每一個任務的io操做,減小了線程的個數,減輕了OS負擔
  3. Cpython解釋器:協程和線程都不能利用多核

    1. 因爲多線程自己不能利用多核
    2. 即使開啓了多線程也只能輪流在一個cpu上執行
    3. 協程若是把全部io操做都規避掉,只剩下須要使用cpu的操做
  4. 線程和協程對比

    1. 線程
      • 切換須要OS,開銷大,os不可控,給os的壓力大
      • os對io操做的感知更加敏感
    2. 協程
    • 切換須要py代碼,開銷小,用戶操做可控,徹底不會增長os壓力
      • 用戶級別對io操做感知較低
        • 協程切換開銷幾乎和函數調用一致
  5. 協程特色

    1. 必須在只有一個單線程裏實現併發
    2. 修改共享數據不需加鎖
    3. 用戶程序裏本身保存多個控制流的上下文棧
    4. 附加:一個協程遇到IO操做自動切換到其它協程(如何實現檢測IO,yield、greenlet都沒法實現,就用到了gevent模塊(select機制))
  6. 對比OS控制線程的切換,用戶在單線程內控制協程的切換

    優勢以下

    1. 協程的切換開銷更小,屬於程序級別的切換,操做系統徹底感知不到,於是更加輕量級
    2. 單線程內就能夠實現併發的效果,最大限度地利用cpu

    缺點以下

    1. 協程的本質是單線程下,沒法利用多核,能夠是一個程序開啓多個進程,每一個進程內開啓多個線程,每一個線程內開啓協程
    2. 協程指的是單個線程,於是一旦協程出現阻塞,將會阻塞整個線程

2. greenlet&gevent

2.1 原生python完成

  • asynicio:使用yield
  • 可以在一個線程下的多個任務之間來回切換,那麼每個任務都是協程
# 切換,協程任務
def func1():
    1 + 2
    2 + 3
    yield 1 
    sleep(1)
def func2():
    g = func1()
    next(g)
    next(g)

2.2 C語言完成的py模塊

  • greenlet
  • gevent
  1. greenlet模塊
# 完成切換
import time
from greenlet import greenlet

def func1():
    print(123)
    time.sleep(0.5)
    g2.switch()
    print(123)
  
def func2():
    print(456)
    time.sleep(0.5)
    print(456)
    g1.switch()
   
g1 = greenlet(func1)
g2 = greenlet(func2)
g1.switch()
  1. 協程切換原理
    • 事件循環:第三者一直在循環全部任務調度全部任務
def sleep(num):
    t = num + time.time()
    yield t
    print('sleep 結束')
    
g1 = sleep(1)
g2 = sleep(2)
t1 = next(g1) 
t2 = next(g2)
lst = [t1, t2]
min_t = min(lst)
time.sleep(min_t - time.time())
g1.next()

min_t = min(lst)
time.sleep(min_t - time.time())
g2.next()
  1. gevent模塊
    • 基於greenlet
    • gevent.sleep/ gevent.spawn(func) / gevent.joinall(func_li) / g.join()
# gevent不認識time.sleep
import time
import gevent
from gevent import monkey
monkey.patch_all()                                 # 可能的阻塞方法

def func1():
    print(123)
    gevent.sleep(1)
    print(123)
  
def func2():
    print(456)
    gevnet.sleep(1)
    print(456)
  
g1 = gevent.spawn(fun1)
g2 = gevent.spawn(fun2)
# gevent.sleep(2)                # 遇到阻塞纔會切換
# g1.join()                                           # 阻塞直到g1任務完成爲止
# g2.join() 
gevent.joinall([g1, g2])

g_l = []
for i in range(10):
    g = gevent.spawn(func1)
    g_l.append(g)
gevent.joinall(g_l)
# 示例大變身
import gevent
from gevent import monkey,time
monkey.patch_all()                              # 對io操做進行重現實現

def func1():
    print(123)
    time.sleep(3)
    print(456)

def func2():
    print('---------')
    time.sleep(1)
    print('=========')

g1 = gevent.spawn(func1)
g2 = gevent.spawn(func2)
gevent.joinall([g1, g2])                # 阻塞列表中的全部協程
print('main')
  • 獲取返回值
print(g1.value )                                   # value是屬性,若是沒有執行則爲None
  • 協程實現socket
import socket
import gevent
from gevent import monkey
monkey.patch_all()

sk = socket.socket()
sk.bind(('127.0.0.1', 9000))
sk.listen()

def chat(con):
    while True:
        msg = con.recv(1024).decode('utf-8')
        con.send(msg.upper().encode('utf-8'))

while True:
    con, _ = sk.accept()
    gevent.spawn(chat, con)

3. asynico

  • python原生底層的協程模塊
    • 爬蟲、webserver框架
    • 提升網編的效率和併發效果

3.1 啓動一個任務

import asyncio                                        # 只識別自身的方法

# 起一個任務
async def demo():                                   # 必需要async修飾,協程方法
    print('start')
        await asyncio.sleep(1)              # 阻塞,await關鍵字 + 協程函數
    print('end')
    
loop = asyncio.get_event_loop()                # 建立一個事件循環
loop.run_until_complete(demo())               # 阻塞,直到協程執行完畢
                                    # 把demo任務丟到事件循環中執行

3.2 啓動多個任務

  1. 沒有返回值
    • 建立一個事件循環
    • 等待
    • 阻塞
import asyncio                                      # 只識別自身的方法

async def demo():                                 # 必需要async修飾,協程方法
    print('start')
        await asyncio.sleep(1)            # 阻塞,await關鍵字 + 協程函數
    print('end')

loop = asyncio.get_event_loop()               # 建立一個事件循環
wait_obj = asyncio.wait([demo(), demo(), demo()])
loop.run_until_complete(wait_obj)            # 沒有返回值
  1. 有返回值
    • 建立一個事件循環
    • loop.creat_task(func(arg1, arg2 …))
    • asyncio.wait([task1, taks2 …]):獲得一個任務列表對象
    • loop.run_until)_complete(wait_obj)
    • task列表中即爲返回值(task對象)
# 同步取返回值
import asyncio
async def demo():
    print('start')
        await asyncio.sleep(1)
    print('end')
    return 123
    
loop = asyncio.get_event_loop()                # 建立一個事件循環
t1 = loop.create_task(demo())
t2 = loop.create_task(demo())
tasks = [t1, t2]                                        # 任務列表
wait_obj = asyncio.wait([t1, t2])
loop.run_until_complete(wait_obj)
for task in tasks:
        print(t.result())                         # 獲取返回值
  1. 異步取返回值
    • task = asyncio.ensure_future(func(arg1, arg2...))
    • asyncio.as_compeleted(task_l):可迭代對象
    • i await i
import asyncio
async def demo(i):
    print('start')
        await asyncio.sleep(10-i)
    print('end')
    return i, 123

async def main():
    task_l = []
    for i in range(10):
        task = asyncio.ensure_future(demo(i))
        task_l.append(task)
    for ret in asyncio.as_compeleted(task_l):
        res = await ret
        print(res)
        
loop = asyncio.get_event_loop() 
loop.run_until_compeleted(main())

Note3(2)

  1. await 阻塞事件,協程函數從這裏切換出去,還能保證切回來
    • 必須寫在async裏面,async函數是個協程函數(調用時並不執行)
  2. loop是事件循環,全部的協程執行、調度、都離不開loop

4. 協程的上下文切換

  1. 在一個基於協程的應用程序中,可能會產生數以千計的協程,全部這些協程,會有一個的調度器來統一調度
  2. 另外咱們知道,高性能的程序首要注意的就是避免程序阻塞
  3. 那麼,在以協程爲最小運行單位的程序中,一樣也須要確保這一點,即每個協程都不能發生阻塞。
  4. 由於只要某一個協程發生了阻塞,那麼整個調度器就阻塞住了,後續等待的協程得不到運行,整個程序此時也將死翹翹了。
  5. CPU只認識線程,不會像線程同樣把上下文保存在CPU寄存器,協程是用戶控制的。
  6. 協程的優缺點
    1. 優勢
      • 無需線程上下文切換的開銷,用yield的時候,只是在函數之間來回切換
      • 無需原子操做鎖定及同步的開銷,沒有異步鎖之類的東西,由於協程就是單線程
      • 方便切換控制流,簡化編程模型
      • 高併發-高擴展-低成本,一個CPU支持上萬個協程都不成問題
    2. 缺點
      • 因爲是單線程的沒法利用多核資源,協程本質上是單線程
      • 協程須要和進程配合才能運行在多CPU上
      • 協程阻塞時會阻塞整個程序
相關文章
相關標籤/搜索