Python 學習之路(下)

不能say的祕密發表於2020-12-27

一、多型【瞭解】

一種事物的多種體現形式,函式的重寫其實就是多型的一種體現

在Python中,多型指的是父類的引用指向子類的物件

程式碼演示:

#父類
class Animal(object):
    pass

#子類
class Dog(Animal):
    pass

class Cat(Animal):
    pass

#定義變數
a = []   #a是list型別
b = Animal()  #b是Animal型別
c = Cat()  #c是Cat型別

#isinstance():判斷一個物件是否屬於某種型別【系統還是自定義的型別】
print(isinstance(a,list))
print(isinstance(b,Animal))
print(isinstance(c,Cat))

print(isinstance(c,Animal))  #True
print(isinstance(b,Dog))   #False

#結論:子類物件可以是父類型別,但是,父類的物件不能是子類型別

總結:

​ 簡化程式碼,提高程式碼的可讀性,可維護性

二、獲取物件資訊

type() isintance() dir()

程式碼演示:

#1.type() :判斷一個物件所屬的型別
num = 10
print(type(num))
print(type("hello"))

class Check(object):
    pass
c = Check()
print(type(c))

#使用==判斷type返回的結果
print(type(12) == type(57))  #True
print(type(12) == type("57"))  #False

#使用type返回的結果和資料型別直接判斷
print(type(12) == int)

#2.isintance(): 判斷一個物件是否屬於某種指定的資料型別
#自定義的類中
class Dog(object):
    pass

d = Dog()
print(isinstance(d,Dog))
print(isinstance([1,2,4],list))

#特殊用法:可以判斷一個物件是否屬於多種資料型別中的某一種
print(isinstance([1,2,4],(tuple,list)))

#3.dir()  :列出指定物件中所包含的所有的內容【成員變數,成員方法】
dict = {}
print(dir(dict))

print(dir("abc"))

print(dir(d))

三、類中特殊的屬性和方法

1.例項屬性和類屬性

1.1例項屬性和類屬性的區別【面試題】

a.定義的位置不同,類屬性時直接定義在類中,例項屬性定義在建構函式中

b.訪問的方式不同,類屬性使用類名直接訪問,例項屬性使用物件訪問

c.在記憶體中出現的時機不同,類屬性隨著類的出現而出現,例項屬性隨著物件的出現而出現

d.優先順序不同,例項屬性的優先順序高於類屬性

程式碼演示:

class Person(object):
    #1.定義位置
    #類屬性:直接定義在類中
    name = "abc"
    age = 0

    def __init__(self,name):
        #例項屬性:定義在建構函式中
        self.name = name


#2.訪問方式
print(Person.name)  #類屬性:類名.屬性 或者 物件.屬性

p = Person("hello")
print(p.name)   #例項屬性:物件.屬性

#3.優先順序不同:例項屬性的優先順序高於類屬性
print(p.name)   #hello

#4.不同物件的類屬性在記憶體中是不是同一塊空間?----->不是
p1 = Person("小白")
p2 = Person("小紅")
print(p1.age)
print(p2.age)
p1.age = 33
print(p1.age)
print(p2.age)
print(id(p1.age))
print(id(p2.age))
"""
0
0
33
0
1420404832
1420403776
"""

#注意:儘量避免類屬性和例項屬性的重名

#刪除屬性【類屬性,例項屬性】
del p1.age
1.2動態新增屬性和方法

程式碼演示:

from  types import MethodType


class Person(object):
    #__slots__ = ("name","age")
    pass


#1.動態新增屬性
per = Person()
str = "fjsgh"
per.name = str

#2.動態新增方法
def say(self):
    print("fhsj")
"""
per.test = say
per.test(per)
"""

#弊端:違背了普通函式定義
#解決方案:MethodType類,存在於types模組下

#類似於偏函式
#引數:函式名,物件
#作用:在現有函式的基礎上生成了一個物件【新的函式】,賦值給成員變數,則認為給物件新增了一個成員方法
per.test = MethodType(say,per)
per.test()

2.類方法和靜態方法

類方法:使用@classmethod裝飾器修飾的方法,被稱為類方法,可以通過類名呼叫,也可以通過物件呼叫,但是一般情況下使用類名呼叫

靜態方法:使用@staticmethod裝飾器修飾的方法,被稱為靜態方法,可以通過類名呼叫,也可以通過物件呼叫,但是一般情況下使用類名呼叫

程式碼演示:

class Test(object):
    #1.類屬性
    age = 100

    def __init__(self,name):
        #2.例項屬性
        self.name = name

    #3.成員方法,通過物件呼叫
    #必須有一個引數,這個引數一般情況下為self,self代表是當前物件
    def func(self):
        print("func")

    #4.類方法
    """
    a.必須有一個引數,這個引數一般情況下為cls,cls代表的是當前類
    b.類方法是屬於整個類的,並不是屬於某個具體的物件,在類方法中禁止出現self
    c.在類方法的內部,可以直接通過cls呼叫當前類中的屬性和方法
    d.在類方法的內部,可以通過cls建立物件
    """
    @classmethod
    def test(cls):
        print("類方法")
        print(cls)   #<class 'methodDemo01.Test'>
        print(cls.age)

        #6
        #注意:cls完全當做當前類使用
        c = cls("hello")
        c.func()

    #7.靜態方法
    @staticmethod
    def show():
        print("靜態方法")

t = Test("hjfsh")
t.func()

#5,.呼叫類方法
Test.test()   #類名.類方法的名稱()
t.test()       #物件.類方法的名稱()

#7。呼叫靜態方法
Test.show()
t.show()

總結:例項方法【成員方法】、類方法以及靜態方法之間的區別

a.語法上

​ 例項方法:第一個引數一般為self,在呼叫的時候不需要傳參,代表的是當前物件【例項】

​ 靜態方法:沒有特殊要求

​ 類方法:第一個引數必須為cls,代表的是當前類

b.在呼叫上

​ 例項方法:只能物件

​ 靜態方法:物件 或者 類

​ 類方法:物件 或者 類

c.在繼承上【相同點】

​ 例項方法、靜態方法、類方法:當子類中出現和父類中重名的函式的時候,子類物件呼叫的是子類中的方法【重寫】

程式碼演示:

class SuperClass(object):
    @staticmethod
    def show():
        print("父類中的靜態方法")

    @classmethod
    def check(cls):
        print("父類中的類方法")

class SubClass(SuperClass):
    pass

s = SubClass()
s.show()
s.check()

注意:注意區分三種函式的書寫形式,在使用,沒有絕對的區分

3.類常用屬性

__name__
	通過類名訪問,獲取類名字串
	不能通過物件訪問,否則報錯
	
__dict__
	通過類名訪問,獲取指定類的資訊【類方法,靜態方法,成員方法】,返回的是一個字典
	通過物件訪問,獲取的該物件的資訊【所有的屬性和值】,,返回的是一個字典
	
__bases__
	通過類名訪問,檢視指定類的所有的父類【基類】

程式碼演示:

class Animal(object):
    def __init__(self,arg):
        super(Animal, self).__init__()
        self.arg = arg


class Tiger(Animal):
    age = 100
    height = 200

    def __init__(self,name):
        #super(Tiger, self).__init__(name)
        self.name = name

    def haha(self):
        print("haha")

    @classmethod
    def test(cls):
        print("cls")

    @staticmethod
    def show():
        print("show")


if __name__ == "__main__":

    #1.__name__
    print(Tiger.__name__)  #Tiger

    t = Tiger("")
    #print(t.__name__)  #AttributeError: 'Tiger' object has no attribute '__name__'

    #2.__dict__
    print(Tiger.__dict__)  #類屬性,所有的方法
    print(t.__dict__)   #例項屬性

    #3.__bases__,獲取指定類的所有的父類,返回的是一個元組
    print(Tiger.__bases__)

四、運算子過載【瞭解】

運算子過載其實就是函式重寫

程式碼演示:

print(1 + 1)
print("1" + "1")
#print("1" + 1)
#不同的資料型別進行加法運算得到的是不同的解釋

#思考問題:兩個物件相加?
class Person(object):
    def __init__(self,num):
        self.num = num

    def __str__(self):
        return "num=" + str(self.num)

    def __add__(self, other):
        #兩個物件相加得到的結果仍然為一個物件
        return Person(self.num + other.num)   #Peson(30)


p1 = Person(10)
p2 = Person(20)

print(p1)  #10
print(p2)  #20

print(p1 + p2)  #30

#p1 + p2----->p1.__add__(p2),

五、單例設計模式【擴充套件】

1.概念

什麼是設計模式

​ 經過已經總結好的解決問題的方案

​ 23種設計模式,比較常用的是單例設計模式,工廠設計模式,代理模式,裝飾模式

什麼是單例設計模式

​ 單個例項【物件】

​ 在程式執行的過程中,確保某一個類只能有一個例項【物件】,不管在哪個模組中獲取物件,獲取到的都是同一個物件

​ 單例設計模式的核心:一個類有且僅有一個例項,並且這個例項需要應用在整個工程中

2.應用場景

實際應用:資料庫連線池操作-----》應用程式中多處需要連線到資料庫------》只需要建立一個連線池即可,避免資源的浪費

3.實現

3.1模組

Python的模組就是天然的單例設計模式

模組的工作原理:

​ import xxx,模組被第一次匯入的時候,會生成一個.pyc檔案,當第二次匯入的時候,會直接載入.pyc檔案,將不會再去執行模組原始碼

3.2使用new
__new__():例項從無到有的過程【物件的建立過程】

程式碼演示:

class Singleton(object):
    #類屬性
    instance = None

    #類方法
    @classmethod
    def __new__(cls, *args, **kwargs):
        #如果instance的值不為None,說明已經被例項化了,則直接返回;如果為NOne,則需要被例項化
        if not cls.instance:
            cls.instance = super().__new__(cls)

        return cls.instance

class MyClass(Singleton):
    pass

#當建立物件的時候自動被呼叫
one = MyClass()
two = MyClass()

print(id(one))
print(id(two))

print(one is two)
3.3裝飾器

程式碼演示:

#單例類:將裝飾器作用於一個類上
def singleton(cls):
    #類屬性
    instance = {}

    #成員方法
    def getSingleton(*args, **kwargs):
        #思路:如果cls在字典中,則直接返回;如果不存在,則cls作為key,物件作為value,新增到字典中
        if cls not in instance:
            instance[cls] = cls(*args, **kwargs)
        return  instance[cls]

    return getSingleton

@singleton
class Test(object):
    pass

t1 = Test()
t2 = Test()

print(id(t1) == id(t2))
print(t1 is t2)
3.4使用在類中

程式碼演示:

#單例類
class Foo(object):
    #1.宣告一個變數【類屬性】
    instance = None

    #2.向外界提供一個公開的方法,用於返回當前類唯一的物件
    #方法命名格式:defaultInstance,currentInstance ,getInstance
    @classmethod
    def getInstance(cls):
        if cls.instance:
            return cls.instance
        else:
            #例項化
            cls.instance = cls()
            return  cls.instance

obj1 = Foo.getInstance()
obj2 = Foo.getInstance()

print(id(obj1) == id(obj2))
print(obj1 is obj2)

五、錯誤和異常

1.概念

兩種容易辨認的錯誤

​ 語法錯誤:一些關於語法的錯誤【縮排】

​ 異常:程式碼完全正確,但是,程式執行之後,會報出 的錯誤

exception/error

程式碼演示:

list1 = [23,54,6,6]
print(list1[2])
print(list1[3])
print(list1[4])  

print("over")

"""
6
6
Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/SZ-Python/Day15Code/textDemo01.py", line 4, in <module>
    print(list1[4])
IndexError: list index out of range
"""

異常特點:當程式在執行的過程中遇到異常,程式將會終止在出現異常的程式碼處,程式碼不會繼續向下執行

解決問題:越過異常,保證後面的程式碼繼續執行【實質:將異常暫時遮蔽起來,目的是為了讓後面的程式碼的執行不受影響】

2.常見的異常

NameError:變數未被定義

TypeError:型別錯誤

IndexError:索引異常

keyError:

ValueError:

AttributeError:屬性異常

ImportError:匯入模組的時候路徑異常

SyntaxError:程式碼不能編譯

UnboundLocalError:試圖訪問一個還未被設定的區域性變數

3.異常處理方式【掌握】

捕獲異常

丟擲異常

3.1捕獲異常
try-except-else

語法:

​ try:

​ 可能存在異常的程式碼

​ except 錯誤表示碼 as 變數:

​ 語句1

​ except 錯誤表示碼 as 變數:

​ 語句2

​ 。。。

​ else:

​ 語句n

說明:

​ a.try-except-else的用法類似於if-elif-else

​ b.else可有可無,根據具體的需求決定

​ c.try後面的程式碼塊被稱為監測區域【檢測其中的程式碼是否存在異常】

​ d.工作原理:首先執行try中的語句,如果try中的語句沒有異常,則直接跳過所有的except語句,執行else;如果try中的語句有異常,則去except分支中進行匹配錯誤碼,如果匹配到了,則執行except後面的語句;如果沒有except匹配,則異常仍然沒有被攔截【遮蔽】

程式碼演示:

#一、try-except-else的使用

#1.except帶有異常型別
try:
    print(10 / 0)
except ZeroDivisionError as e:
    print("被除數不能為0",e)

print("~~~~")
"""
總結:
a.try-except遮蔽了異常,保證後面的程式碼可以正常執行
b.except ZeroDivisionError as e相當於宣告瞭一個ZeroDivisionError型別的變數【物件】,變數e中攜帶了錯誤的資訊
"""

#2.try後面的except語句可以有多個
class Person(object):
    __slots__ = ("name")
try:
    p = Person()
    p.age = 19

    print(10 / 0)
except AttributeError as e:
    print("屬性異常",e)
except ZeroDivisionError as e:
    print("被除數不能為0",e)

print("over")

"""
總結:
a.一個try語句後面可以有多個except分支
b.不管try中的程式碼有多少個異常,except語句都只會被執行其中的一個,哪個異常處於try語句的前面,則先先執行對應的except語句
c.後面的異常不會報錯【未被執行到】
"""

#3.except語句的後面可以不跟異常型別
try:
    print(10 / 0)
except:
    print("被除數不能為0")


#4.一個except語句的後面可以跟多種異常的型別
#注意:不同的異常型別使用元組表示
try:
    print(10 / 0)
except (ZeroDivisionError,AttributeError):
    print("出現了異常")


#5.else分支
try:
    print(10 / 4)
except ZeroDivisionError as e:
    print("出現了異常",e)
else:
    print("hello")

"""
總結:
a.如果try中的程式碼出現了 異常,則直接去匹配except,else分支不會被執行
b.如果try中的程式碼沒有出現異常,則try中的程式碼正常執行,except不會被執行,else分支才會被執行
"""

#6.try中不僅可以直接處理異常,還可以處理一個函式中的異常
def show():
    x = 1 / 0

try:
    show()
except:
    print("出現了異常")

#7.直接使用BaseException代替所有的異常
try:
    y = 10 / 0
except BaseException as e:
    print(e)

"""
總結:在Python中,所有的異常其實都是類,他們都有一個共同的父類BaseException,可以使用BaseException將所有異常“一網打盡”
"""
try-except-finally

語法:

​ try:

​ 可能存在異常的程式碼

​ except 錯誤表示碼 as 變數:

​ 語句1

​ except 錯誤表示碼 as 變數:

​ 語句2

​ 。。。

​ finally:

​ 語句n

說明:不管try中的語句是否存在異常,不管異常是否匹配到了except語句,finally語句都會被執行

作用:表示定義清理行為,表示無論什麼情況下都需要進行的操作

程式碼演示:

#二、try-except-finally的使用

#1.
try:
    print(10 / 5)
except ZeroDivisionError as e:
    print(e)

finally:
    print("finally被執行")


#2.特殊情況
#注意:當在try或者except中出現return語句時,finally語句仍然會被執行
def show():
    try:
        print(10 / 0)
        return
    except ZeroDivisionError as e:
        print(e)

    finally:
        print("finally被執行~~~~")

show()
3.2丟擲異常

raise丟擲一個指定的異常物件

語法:raise 異常物件 或者 raise

說明:異常物件通過錯誤表示碼建立,一般來說錯誤表示碼越準確越好

程式碼演示:

#raise的使用主要體現在自定義異常中

#1.raise表示直接丟擲一個異常物件【異常是肯定存在的】
#建立物件的時候,參數列示對異常資訊的描述
try:
    raise NameError("hjafhfja")
except NameError as e:
    print(e)

print("over")

"""
總結:
通過raise丟擲的異常,最終還是需要通過try-except處理
"""

#2.如果通過raise丟擲的異常在try中不想被處理,則可以通過raise直接向上丟擲
try:
    raise NameError("hjafhfja")
except NameError as e:
    print(e)
    raise

4.assert斷言

對某個問題做一個預測,如果預測成功,則獲取結果;如果預測失敗,則列印預測的資訊

程式碼演示:

def func(num,divNum):

    #語法:assert表示式,當出現異常時的資訊描述
    #assert關鍵字的作用:預測表示式是否成立,如果成立,則執行後面的程式碼;如果不成立,則將異常的描述資訊列印出來
    assert (divNum != 0),"被除數不能為0"

    return  num / divNum

print(func(10,20))
print(func(10,0))

5.自定義異常

實現思路:

a.定義一個類,繼承自Exception類

b.書寫建構函式,屬性儲存異常資訊【呼叫父類的建構函式】

c.重寫__str__函式,列印異常的資訊

d.定義一個成員函式,用來處理自己的異常

程式碼演示:

class MyException(Exception):
    def __init__(self,msg):
        super(MyException,self).__init__()
        self.msg = msg

    def __str__(self):
        return self.msg

    def handle(self):
        print("出現了異常")

try:
     raise MyException("自己異常的型別")
except MyException as e:
     print(e)
     e.handle()

六、檔案讀寫

1.概念

在Python中,通過開啟檔案生成一個檔案物件【檔案描述符】操作磁碟上的檔案,操作主要有檔案讀寫

2.普通檔案的讀寫

普通檔案包含:txt檔案,圖片,視訊,音訊等

2.1讀檔案

操作步驟:

​ a.開啟檔案:open()

​ b.讀取檔案內容:read()

​ c.關閉檔案:close()

說明:最後一定不要忘了檔案關閉,避免系統資源的浪費【因為一個檔案物件會佔用系統資源】

程式碼演示:

#一、開啟檔案
"""
open(path,flag[,encoding,errors])
path:指定檔案的路徑【絕對路徑和相對路徑】
flag:開啟檔案的方式
    r:只讀、
    rb:read binary,以二進位制的方式開啟,只讀【圖片,視訊,音訊等】
    r+:讀寫

    w:只能寫入
    wb:以二進位制的方式開啟,只能寫入【圖片,視訊,音訊等】
    w+:讀寫

    a:append,如果一個檔案不為空,當寫入的時候不會覆蓋掉原來的內容
encoding:編碼格式:gbk,utf-8
errors:錯誤處理
"""
path = r"C:\Users\Administrator\Desktop\SZ-Python\Day15\筆記\致橡樹.txt"
#呼叫open函式,得到了檔案物件
f = open(path,"r",encoding="gbk")

"""
注意:
a.以r的方式開啟檔案時,encoding是不是必須出現
    如果檔案格式為gbk,可以不加encoding="gbk"
    如果檔案格式為utf-8,必須新增encoding="utf-8"
b。如果開啟的檔案是圖片,音訊或者視訊等,開啟方式採用rb,但是,此時,不能新增encoding="xxx"
"""

#二、讀取檔案內容
#1.讀取全部內容   ***********
#str = f.read()
#print(str)

#2.讀取指定的字元數
#注意:如果每一行的結尾有個"\n",也被識別成字元
"""
str1 = f.read(2)
print(str1)
str1 = f.read(2)
print(str1)
str1 = f.read(2)
print(str1)


#3.讀取整行,不管該行有多少個字元    *********
str2 = f.readline()
print(str2)
str2 = f.readline()
print(str2)
"""

#4.讀取一行中的指定的字元
#str3 = f.readline(3)
#print(str3)

#5.讀取全部的內容,返回的結果為一個列表,每一行資料為一個元素
#注意:如果指明引數,則表示讀取指定個數的字元
str4 = f.readlines()
print(str4)

#三、關閉檔案
f.close()

其他寫法:

#1.讀取檔案的簡寫形式
#with open()  as 變數

#好處:可以自動關閉檔案,避免忘記關閉檔案導致的資源浪費
path = "致橡樹.txt"
with open(path,"r",encoding="gbk") as f:
    result = f.read()
    print(result)

#2.
try:
    f1 = open(path,"r",encoding="gbk")
    print(f1.read())
except FileNotFoundError as e:
    print("檔案路徑錯誤",e)
except LookupError as e:
    print("未知的編碼格式",e)
except UnicodeDecodeError as e:
    print("讀取檔案解碼錯誤",e)
finally:
    if f1:
        f1.close()

讀取圖片等二進位制檔案:

#1.
f = open("dog.jpg","rb")

result = f.read()
print(result)

f.close()

#2
with open("dog.jpg","rb") as f1:
    f1.read()

#注意:讀取的是二進位制檔案,讀取到的內容為\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xdb\x0
2.2寫檔案

操作步驟:

​ a.開啟檔案:open()

​ b.寫入資料:write()

​ c.重新整理管道【內部緩衝區】:flush()

​ d.關閉檔案:close()

程式碼演示:

path = "file1.txt"

#1.開啟檔案
#注意:寫入檔案的時候,檔案可以不存在,當open的時候會自動建立檔案
#讀取檔案的時候,檔案必須先存在,才能open
f = open(path,"w",encoding="utf-8")

#2.寫入資料
#注意:將資料寫入檔案的時候,預設是沒有換行的,如果向換行,則可以手動新增\n
f.write("Python高薪就業,走上人生巔峰")

#3.重新整理資料緩衝區
#作用:加速資料的流動,保證緩衝區的流暢
f.flush()

#4.關閉檔案
f.close()

#簡寫形式
with open(path,"w",encoding="utf-8") as f1:
    f1.write("hello")
    f.flush()

3.編碼和解碼

編碼:encode,字串型別轉換為位元組型別

解碼:decode,位元組型別轉換為字串型別

注意:編碼和解碼的格式必須保持一致

程式碼演示:

path = "file2.txt"

#編碼:字串----》位元組
with open(path,"wb") as f1:
    str = "today is a good day 今天是個好天氣"
    f1.write(str.encode("utf-8"))

#解碼:位元組----->字串
with open(path,"rb") as f2:
    data = f2.read()
    print(data)
    print(type(data))

    newData = data.decode("utf-8")
    print(newData)
    print(type(newData))

4.csv檔案的讀寫

csv:逗號分隔值【Comma Separated Values】

一種檔案格式,.csv,本質是一個純文字檔案,可以作為不同程式之間資料互動的格式

開啟方式:記事本,excel

4.1讀檔案

程式碼演示:

#C:\Users\Administrator\Desktop\SZ-Python\Day15\筆記\text.csv
import  csv


#方式一:三部曲
def readCsv1(path):
    #1.開啟檔案
    csvFile = open(path,"r")

    #2.將檔案物件封裝成可迭代物件
    reader= csv.reader(csvFile)

    #3.讀取檔案內容
    #遍歷出來的結果為列表
    for item in reader:
        print(item)

    #4.關閉檔案
    csvFile.close()

readCsv1(r"C:\Users\Administrator\Desktop\SZ-Python\Day15\筆記\text.csv")

#方式二:簡寫
def readCsv2(path):
    with open(path,"r") as f:
        reader = csv.reader(f)
        for item in reader:
            print(item)

readCsv2(r"C:\Users\Administrator\Desktop\SZ-Python\Day15\筆記\text.csv")
4.2寫檔案

程式碼演示:

import  csv

#1.從列表寫入資料
def writeCsv1(path):
    infoList = [['username', 'password', 'age', 'address'],['zhangsan', 'abc123', '17', 'china'],['lisi', 'aaabbb', '10', 'england']]

    #1.開啟檔案
    #注意:如果不設定newline,每一行會自動有一個空行
    csvFile = open(path,"w",newline="")

    #2.將檔案物件封裝成一個可迭代物件
    writer = csv.writer(csvFile)

    #3.寫入資料
    for i in range(len(infoList)):
        writer.writerow(infoList[i])

    #4.關閉檔案
    csvFile.close()

writeCsv1("file3.csv")

#2.從字典寫入檔案
def writeCsv2(path):
    dic = {"張三":123,"李四":456,"王麻子":789}
    csvFile = open(path, "w", newline="")
    writer = csv.writer(csvFile)

    for key in dic:
        writer.writerow([key,dic[key]])

    csvFile.close()

#3.簡寫形式
def writeCsv3(path):
    infoList = [['username', 'password', 'age', 'address'], ['zhangsan', 'abc123', '17', 'china'],
                ['lisi', 'aaabbb', '10', 'england']]
    with open(path, "w", newline="") as f:
        writer = csv.writer(f)

        for rowData in infoList:
            writer.writerow(rowData)

正規表示式 Regular Expression

七、正規表示式

1.引入案例

程式碼演示:

import  re     #regular  Expession   
#需求:判斷一個qq號是否是合法的
"""
分析:
1.全數字
2.第一位數字不能為0
3.位數:5~11
"""
def checkQQ(str):
    #不管str是否合法,假設合法
    result = True

    #尋找條件推翻假設
    try:
        #判斷是否是全數字
        num = int(str)

        #判斷位數
        if len(str) >= 5 and len(str) <= 11:

            #判斷開頭是否為0
            if str[0] == "0":
                result = False

        else:
            result = False
    except ValueError as e:
        result = False

    return  result


print(checkQQ("6725675785678657"))

#使用正規表示式實現上面的需求
result = re.match(r"[1-9]\d{4,10}","6725675786574657")
print(result)

2.概述

正規表示式【Regular Expression】,簡寫為regex,RE,使用單個字串來描述一系列具有特殊格式的字串

功能:

​ a.搜尋

​ b.替換

​ c.匹配

使用情景:

​ 爬蟲

​ 驗證手機號,驗證郵箱,密碼【使用者名稱】

3.使用規則

3.1匹配單個數字或者字元

程式碼演示:

import  re
"""
----------匹配單個字元與數字---------
.                匹配除換行符以外的任意字元
[0123456789]     []是字符集合,表示匹配方括號中所包含的任意一個字元
[good]           匹配good中任意一個字元
[a-z]            匹配任意小寫字母
[A-Z]            匹配任意大寫字母
[0-9]            匹配任意數字,類似[0123456789]
[0-9a-zA-Z]      匹配任意的數字和字母
[0-9a-zA-Z_]     匹配任意的數字、字母和下劃線
[^good]          匹配除了good這幾個字母以外的所有字元,中括號裡的^稱為脫字元,表示不匹配集合中的字元
[^0-9]           匹配所有的非數字字元
\d               匹配數字,效果同[0-9]
\D               匹配非數字字元,效果同[^0-9]
\w               匹配數字,字母和下劃線,效果同[0-9a-zA-Z_]
\W               匹配非數字,字母和下劃線,效果同[^0-9a-zA-Z_]
\s               匹配任意的空白符(空格,回車,換行,製表,換頁),效果同[ \r\n\t\f]
\S               匹配任意的非空白符,效果同[^ \f\n\r\t]
"""
#[]  :只匹配其中的一位
# - :表示一個區間
#1.
#1.1編譯正規表示式返回物件
pattern = re.compile(r"[abcd]")   #正規表示式  [a-d]
#1.2使用正規表示式匹配字串,成功返回物件,並攜帶匹配之後額結果,如果匹配失敗則返回None
ret = pattern.match("dhello")    #需要被匹配的字串
print(ret)
print(ret.group())

#2.
pattern = re.compile(r"[s-z]")
ret = pattern.match("xhello")
print(ret)
print(ret.group())

#3
pattern = re.compile(r"[0-9]")
ret = pattern.match("5hello")
print(ret)
print(ret.group())

#4
pattern = re.compile(r"[0-9a-zA-Z]")
ret = pattern.match("8hello")
print(ret)
print(ret.group())

#5  ^:脫字元【】否定的含義
pattern = re.compile(r"[^0-9]")
ret = pattern.match("chello")
print(ret)
print(ret.group())

#6 \d:只匹配數字,等同於[0-9]
pattern = re.compile(r"\d")
ret = pattern.match("4")
print(ret)
print(ret.group())

#7.   \w
pattern = re.compile(r"\w")
ret = pattern.match("7")
print(ret)
print(ret.group())

#8   \s
pattern = re.compile(r"\s")
ret = pattern.match("\t")
print(ret)
print(ret.group())

#9.   .:匹配不到換行符【\n】
pattern = re.compile(r".")
ret = pattern.match("\r")
print(ret)
print(ret.group())
3.2匹配邊界字元

程式碼演示:

import  re
"""
--------------錨字元(邊界字元)-------------

^     行首匹配,和在[]裡的^不是一個意思   startswith
$     行尾匹配                          endswith

\A    匹配字串開始,它和^的區別是,\A只匹配整個字串的開頭,即使在re.M模式下也不會匹配它行的行首
\Z    匹配字串結束,它和$的區別是,\Z只匹配整個字串的結束,即使在re.M模式下也不會匹配它行的行尾

\b    匹配一個單詞的邊界,也就是值單詞和空格間的位置   bounds
\B    匹配非單詞邊界

"""
#search()
print(re.search(r"^to","today is a good day"))
print(re.search(r"day$","today is a good day"))

#findall()
#re.M
print(re.findall(r"\Ato","today is a good day\ntoday is a good day",re.M))
print(re.findall(r"^to","today is a good day\ntoday is a good day",re.M))
#總結:\A只匹配第一行的行首,^匹配每一行的行首

#\b匹配邊界【開始和結尾】,\B匹配的是非邊界【中間】
print(re.search(r"er\b","never"))   #er
print(re.search(r"er\b","nerve"))   #None
print(re.search(r"er\B","never"))    #None
print(re.search(r"er\B","nerve"))   #er
3.3匹配多個字元

程式碼演示:

import  re
"""
-------------------匹配多個字元------------------------

說明:下方的x、y、z均為假設的普通字元,n、m(非負整數),不是正規表示式的元字元
(xyz)    匹配小括號內的xyz(作為一個整體去匹配)
x?       匹配0個或者1個x
x*       匹配0個或者任意多個x(.* 表示匹配0個或者任意多個字元(換行符除外))
x+       匹配至少一個x
x{n}     匹配確定的n個x(n是一個非負整數)
x{n,}    匹配至少n個x
x{n,m}   匹配至少n個最多m個x。注意:n <= m
x|y      |表示或,匹配的是x或y
"""

#()當做一個整體去匹配,返回的結果為一個列表
print(re.findall(r"(ab)","aaacccaabbbbb"))

print(re.findall(r"a?","aaacccaabbbbb"))
print(re.findall(r"a*","aaacccaabbbbb"))
print(re.findall(r"a+","aaacccaabbbbb"))
"""
['a', 'a', 'a', '', '', '', 'a', 'a', '', '', '', '', '', '']
['aaa', '', '', '', 'aa', '', '', '', '', '', '']
['aaa', 'aa']
"""
#恰好出現n次
print(re.findall(r"a{2}","aaacccaabbbbb"))
#至少出現n次
print(re.findall(r"a{2,}","aaacccaabbbbb"))
#{m,n}:至少出現m次,至多出現n次
print(re.findall(r"a{2,5}","aaacccaabbbbb"))
#表示或
print(re.findall(r"a|b","aaacccaabbbbb"))   #[ab]
3.4匹配分組

程式碼演示:

import  re

#匹配分組
#|   :或
#()   :整體
#search:會在字串中從左向左進行查詢,如果找到第一個符合條件的,則停止查詢
#正則1|正則2:只要正則1或者正則2中的一個滿足,則直接按照這個條件查詢
pattern = re.compile("\d+|[a-z]+")
ret = pattern.search("123-d223344aa$$aa")   #abc123-d223344aa$$aa
print(ret.group())

pattern = re.compile("([a-z]\d)+\w+")
ret = pattern.search("abc123-d223344aa$$aa")   #abc123-d223344aa$$aa
print(ret.group())
3.5子模式

程式碼演示:

import  re

#子模式
#()
#如果在正規表示式中出現\1  \2等識別符號的時候,那麼\1代表從左向右的第一個()中的內容。。。被稱為子模式【簡化正規表示式】
pattern = re.compile(r"<([a-z]+)><(\w+)>\w+</\2></\1>")
ret = pattern.search("<div><span>hello</span></div>")
print(ret.group())
#子模式訪問
print(ret.group(1))
print(ret.group(2))
3.6貪婪和非貪婪

程式碼演示:

import re

#貪婪和非貪婪【匹配一位還是匹配多位的區別】
#+   *  :多次匹配【貪婪匹配】
#在+或者*後面新增?則改為非貪婪匹配
result1 = re.findall(r"a(\d+)","a23333333b")
print(result1)   #['23333333']

result1 = re.findall(r"a(\d+?)","a23333333b")
print(result1)   #['2']

#特殊情況;如果一個正規表示式的前後都有限定條件的時候,那麼則不存在貪婪模式,?將不起作用
result1 = re.findall(r"a(\d+)b","a23333333b")
print(result1)
result1 = re.findall(r"a(\d+?)b","a23333333b")
print(result1)
3.7模式修正

程式碼演示:

import re

#模式修正
"""
re.I:忽略大小寫模式【ignorecase】
re.M:視為多行模式【more】
re.S:視為單行模式【single】
"""
pattern = re.compile(r"^love",re.M)
string = """
alove you
love her
love he
"""
result1 = pattern.search(string)
print(result1.group())

pattern = re.compile(r"[a-z]+",re.I)
result1 = pattern.search("LLLLLLlove")
print(result1.group())

4.re模組中常用功能函式

程式碼演示:

import re

#re模組中常用的函式

#1.compile()
str1 = "today is a good day"
str2 = r"\w*"
pattern1 = re.compile(str2)
print(pattern1.findall(str1))

#2.match()
result = re.match(r"[1-9]\d{4,10}","6725675786574657")
print(result)

#3.search()
#注意:只要匹配到一個符合條件的子字串,則直接返回,後面的內容不參與搜尋
print(re.search(r"\dcom","www.4comnghughs").group())

#4.findall()
#注意;返回的結果為列表
#finditer(): 返回的結果為可迭代器
iter = re.finditer(r"\d+","12 fjhaehgj 66 fhaj  ")
print(iter)

for i in iter:
    print(i)

    #獲取值
    print(i.group())
    #獲取下標
    print(i.span())


#5.split() :返回一個列表
s1 = "1one2two3445545three454four56977878five"
print(re.split(r"\d+",s1))
print(re.split(r"[0-9]{1,}",s1))
print(re.split(r"[^a-z]+",s1))

s2 = "zhangsan lilei      lisi    Han   Jack"
print(re.split(r" +",s2))

s3 = "zhangsan@@@@lilei##########lisi%%%Han&&&&&&&&&&&&&Jack"
print(re.split(r"[^a-zA-Z]+",s3))

#6.sub() 替換,返回的是替換之後的字串
string1 = "today is a good day today is a good da"
#用-將空格替換
#引數:舊的字元換【正規表示式】  新的字串   原字串
print(re.sub(r"\s","-",string1))

#subn()  替換,返回一個元組(新的字串,替換的次數)
print(re.subn(r"\s","-",string1))

八、網路程式設計

1.網路程式設計基礎

1.1概念

計算機網路:把分佈在不同區域的計算機【裝置】與專門的一些外部裝置通過通訊線路相關聯,形成一個網路系統,從而使得計算機之間可以共享資料

網路程式設計:同一個網路中不同的機器之間的通訊

1.2計算機之間需要通訊的必要條件

ip地址,埠,網路協議

1.>ip地址

​ 網際網路協議地址【Internet Protocol Address】,是聯網裝置和網際網路之間的唯一標識,在同一個網段中,ip地址是唯一的

​ ip地址是數字型,是一個32位的整數,

​ 舉例:10.36.131.32:32位的整數,分成4個8位的二進位制,將二進位制轉換為0~255之間的十進位制整數

​ 分類:

​ a.形式分類

​ ipv4:分為4段

​ ipv6:分為6段

​ b.功能分類

​ A類:保留給政府機構,1.0.0.1~126.255.255.255

​ B類:分配中型企業,128.。。。。~191.。。

​ C類:分配個人,192…~223

​ D類:組播,224~239

​ E類:實驗,240~255

​ 127.0.0.1:回送地址,一般指的是本機的ip,localhost,一般用來進行測試

​ 總結:ip地址可以唯一確定網路上的一個通訊實體,但是一個通訊實體上可能有很多的應用程式,可以同時提供網路服務,此時還需要藉助於埠進行區分

2>埠

​ 資料的傳送和接收都需要通過埠,埠號用於唯一標識通訊實體上進行網路通訊的程式

​ 注意:同一臺機器上的不同的應用程式不能佔用同一個埠,埠的範圍:0~65535

​ 作用:ip地址結合埠號,就可以唯一的確定一個網路中唯一一臺計算機上的一個應用程式

​ 分類:

​ a.公認埠:0~1023

​ b.註冊埠:1025~49151

​ c.動態埠或者私有埠:1024~65535

​ 常用埠:

​ mysql: 3306

​ oracle:1521

​ tomcat:8080

​ qq:4000

3>網路協議

​ http: 被動式協議

​ tcp

​ udp

​ tcp/ip:網際網路協議

2.TCP程式設計

Transmission Control Protocol,傳輸控制協議,是一個傳輸層的通訊協議

客戶端/服務端:套接字【socket】,程式通常通過套接字向網路發出請求或者應答網路請求,使得兩臺裝置之間進行通訊

理解;開啟了一個網路連線,必須知道ip地址,埠號,協議

​ 特點:

​ a.安全的【確保接收方完全正確的接收資料】

​ b.面向連線【資料傳輸之前必須要建立連線】

​ c.傳輸的效率較低【面向連線需要耗時】

​ d.一旦連線建立,雙方可以按照指定的格式傳送資料【大小沒有限制】

使用經典三次握手建立連線

​ a.客戶端向服務端發起請求

​ b.服務端收到請求之後,會給客戶端一個響應

​ c.客戶端收到服務端的響應之後,給服務端回覆一個確認資訊

總結:使用tcp實現資料的傳送和接收需要有傳送方和接收方,但是兩個通訊實體之間沒有明確的客戶端或者服務端之分,在兩個通訊實體在建立連線之前,必須有一個通訊實體先做出主動姿態,主動發起請求

2.1 socket通訊流程

程式碼演示:

server:

#服務端流程描述
import  socket

#1.建立服務端的socket物件
serverSocket = socket.socket()

#2.為socket繫結埠和ip地址
"""
bind(元組),將埠號和ip地址建立元組,然後傳參
(host,port)
"""
#檢視ip地址:在終端輸入ipconfig命令
ip_port = ("10.36.131.32",6666)
serverSocket.bind(ip_port)

#3.服務端監聽請求,隨時準備接受客戶端發來的連線
"""
listen(backlog)
backlog:在拒絕連線之前,可以掛起的最大連線數量
注意:不能無限大
"""
serverSocket.listen(5)

print("server waiting~~~~~")

#4.服務端接收到客戶端的請求,被動開啟進行連線
#accept();在連線的時候,會處於阻塞狀態
#返回值:conn,address,conn表示連線到的套接字物件,address表示連線到的客戶端的地址
conn,addr = serverSocket.accept()

#5.服務端接收訊息
"""
recv(size)
可以一次性接收到多大的資料
"""
client_data = conn.recv(1024)
print(str(client_data,"utf-8"))

#6.服務端關閉
serverSocket.close()

client:

import  socket

#1.建立socket物件
clientSocket = socket.socket()

#2.發起連線請求
#connect(元組)
#(host,port)ip地址和埠號需要和服務端中繫結的ip地址以及埠號保持一致
ip_port = ("10.36.131.32",6666)
clientSocket.connect(ip_port)

#3.傳送資料
#sendall(string) ,位元組型別的字串【編碼的過程】
clientSocket.sendall(bytes("hello你好啊",encoding="utf-8"))

#4.關閉客戶端
clientSocket.close()
2.2客戶端和服務端的資料互動

程式碼演示:

server:

import  socket

server = socket.socket()

server.bind(("10.36.131.32",6666))

server.listen(5)

conn,address = server.accept()

print("連線成功")

while True:
    clientData = conn.recv(1024)
    result = clientData.decode("utf-8")
    print("客戶端對服務端說:",result)

    if result == "bye" or result == "再見":
        break

    sendData = input("請輸入要給回覆客戶端的資料:")
    conn.send(sendData.encode("utf-8"))

server.close()

client:

import  socket

client = socket.socket()

client.connect(("10.36.131.32",6666))

while True:
    sendData = input("請輸入要傳送給服務端的資料:")
    client.send(sendData.encode("utf-8"))

    serverData = client.recv(1024)
    result = serverData.decode("utf-8")
    print("服務端回覆:",result)

    if result == "bye":
        break

client.close()

3.UDP程式設計

User Datagram Protocol,使用者資料包協議

特點:

​ a.不安全的

​ b.無連線

​ c.效率高,速度快

​ d.對資料的大小是有限制的,每個被傳輸的資料包的大小不超過64k

九、發郵件和發簡訊【擴充套件】

1.發簡訊

互億無線

# 傳送簡訊
# APIID:C80604386
# APIKEY:16874f2ed0a2d0bb225747370f9aedc4

程式碼演示:

import requests

# 使用者名稱 檢視使用者名稱請登入使用者中心->驗證碼、通知簡訊->帳戶及簽名設定->APIID
account  = "C80604386"
# 密碼 檢視密碼請登入使用者中心->驗證碼、通知簡訊->帳戶及簽名設定->APIKEY
password = "16874f2ed0a2d0bb225747370f9aedc4"
mobile = "18566218480"
text = "您的驗證碼是:121254。請不要把驗證碼洩露給其他人。"

url = 'http://106.ihuyi.com/webservice/sms.php?method=Submit'
data = {'account': account, 'password' : password, 'content': text, 'mobile':mobile,'format':'json'}

res = requests.post(url, data=data)
print(res, res.text)

2.發郵件

程式碼演示:

#傳送純文字
#發郵件的模組
import  smtplib
#郵件標題
from  email.header import Header
#郵件文字
from  email.mime.text import MIMEText

"""
user:使用者名稱
pwd:授權碼
sender:傳送方
receiver:接收方
content:郵件的正文
title:郵件的標題
"""
def sendMail(user,pwd,sender,receiver,content,title):
    mail_host = "smtp.163.com"   #163的SMTP伺服器

    #第一部分:準備工作
    #1.將郵件的資訊打包成一個物件
    message = MIMEText(content,"plain","utf-8")   #內容,格式,編碼
    #2.設定郵件的傳送者
    message["From"] = sender
    #3.設定郵件的接收方
    #message["To"] = receiver
    #join():通過字串呼叫,引數為一個列表
    message["To"] = ",".join(receiver)
    #4.設定郵件的標題
    message["Subject"] = title

    #第二部分:傳送郵件
    #1.啟用伺服器傳送郵件
    #引數:伺服器,埠號
    smtpObj = smtplib.SMTP_SSL(mail_host,465)
    #2.登入郵箱進行驗證
    #引數:使用者名稱,授權碼
    smtpObj.login(user,pwd)
    #3.傳送郵件
    #引數:傳送方,接收方,郵件資訊
    smtpObj.sendmail(sender,receiver,message.as_string())

    print("mail send successful!")

if __name__ == "__main__":
    mail_user = "18501970795@163.com"
    mail_pwd = "yang0122"

    mail_sender = "18501970795@163.com"
    mail_receiver = ["1490980468@qq.com"]

    email_content = "人生苦短,我用Python"
    email_title = "Python"

    sendMail(mail_user,mail_pwd,mail_sender,mail_receiver,email_content,email_title)

十、多執行緒

在介紹Python中的執行緒之前,先明確一個問題,Python中的多執行緒是假的多執行緒!
為什麼這麼說,我們先明確一個概念,全域性直譯器鎖(GIL)

什麼是GIL

Python程式碼的執行由Python虛擬機器(直譯器)來控制,同時只有一個執行緒在執行。對Python虛擬機器的訪問由全域性直譯器鎖(GIL)來控制,正是這個鎖能保證同時只有一個執行緒在執行。

為什麼要GIL

為了執行緒間資料的一致性和狀態同步的完整性

GIL的影響

只有一個執行緒在執行,無法使用多核(多個CPU)。

在多執行緒環境中,Python虛擬機器按照以下方式執行。
	1.設定GIL。
	2.切換到一個執行緒去執行。
	3.執行。
	4.把執行緒設定為睡眠狀態。
	5.解鎖GIL。
	6.再次重複以上步驟。

比方我有一個4核的CPU,那麼這樣一來,在單位時間內每個核只能跑一個執行緒,然後時間片輪轉切換。
但是Python不一樣,它不管你有幾個核,單位時間多個核只能跑一個執行緒,然後時間片輪轉。
執行一段時間後讓出,多執行緒在Python中只能交替執性,10核也只能用到1個核

# 使用執行緒
from threading import Thread
def loop():
    while True:
        print("親愛的,我錯了,我能吃飯了嗎?")

if __name__ == '__main__':
    for i in range(3):
        t = Thread(target=loop)
        t.start()
    while True:
        pass

# 而如果我們變成程式呢?cpu --100%
from multiprocessing import Process
def loop():
    while True:
        print("親愛的,我錯了,我能吃飯了嗎?")

if __name__ == '__main__':
    for i in range(3):
        t = Process(target=loop)
        t.start()
    while True:
        pass

多執行緒怎麼使用多核

1、重寫python編譯器(官方cpython)如使用:PyPy直譯器
2、呼叫C語言的連結庫
3. 不用執行緒,改用程式

cpu密集型(計算密集型)、I/O密集型

計算密集型任務由於主要消耗CPU資源,程式碼執行效率至關重要,C語言編寫

IO密集型,涉及到網路、磁碟IO的任務都是IO密集型任務,這類任務的特點是CPU消耗很少,任務的大部分時間都在等待IO操作完成,99%的時間花費在IO上,指令碼語言是首選,C語言最差。

計算密集型: 建議使用多程式(多程式可以用到多核)
IO密集型: Input output(一般是比較耗時的操作), 建議使用多執行緒

建立多執行緒

def doSth(arg):
    # 拿到當前執行緒的名稱和執行緒號id
    threadName = threading.current_thread().getName()
    tid = threading.current_thread().ident
    for i in range(5):
        print("%s *%d @%s,tid=%d" % (arg, i, threadName, tid))
        time.sleep(2)

1、使用_thread.start_new_thread開闢子執行緒

def simpleThread():
    # 建立子執行緒,執行doSth
    # 用這種方式建立的執行緒為【守護執行緒】(主執行緒死去“護衛”也隨“主公”而去)
    _thread.start_new_thread(doSth, ("拍森",))

    mainThreadName = threading.current_thread().getName()
    print(threading.current_thread())
    
    # 5秒的時間以內,能看到主執行緒和子執行緒在併發列印
    for i in range(5):
        print("勞資是主執行緒@%s" % (mainThreadName))
        time.sleep(1)

    # 阻塞主執行緒,以使【守護執行緒】能夠執行完畢
    while True:
        pass

2、 通過建立threading.Thread物件實現子執行緒

def threadingThread():
    # 預設不是【守護執行緒】
    t = threading.Thread(target=doSth, args=("大王派我來巡山",)) # args=(,) 必須是元組
    # t.setDaemon(True)  # 設定為守護執行緒
    t.start()  # 啟動執行緒,呼叫run()方法
    

3、通過繼承threading.Thread類,進而建立物件實現子執行緒

class MyThread(threading.Thread):
    def __init__(self, name, task, subtask):
        super().__init__()

        self.name = name  # 覆蓋了父類的name
        self.task = task  # MyThread自己的屬性
        self.subtask = subtask  # MyThread自己的屬性

    # 覆寫父類的run方法,
    # run方法以內為【要跑在子執行緒內的業務邏輯】(thread.start()會觸發的業務邏輯)
    def run(self):
        for i in range(5):
            print("【%s】並【%s】 *%d @%s" % (self.task, self.subtask, i, threading.current_thread().getName()))
            time.sleep(2)

def classThread():
    mt = MyThread("小分隊I", "巡山", "掃黃")
    mt.start()  #  啟動執行緒

4、幾個重要的API

def importantAPI():
    print(threading.currentThread())  # 返回當前的執行緒變數
    # 建立五條子執行緒
    t1 = threading.Thread(target=doSth, args=("巡山",))
    t2 = threading.Thread(target=doSth, args=("巡水",))
    t3 = threading.Thread(target=doSth, args=("巡鳥",))

    t1.start()  # 開啟執行緒
    t2.start()
    t3.start()

    print(t1.isAlive())  # 返回執行緒是否活動的
    print(t2.isDaemon())  # 是否是守護執行緒
    print(t3.getName())  # 返回執行緒名
    t3.setName("巡鳥")  # 設定執行緒名
    print(t3.getName())
    print(t3.ident)  # 返回執行緒號

    # 返回一個包含正在執行的執行緒的list
    tlist = threading.enumerate()
    print("當前活動執行緒:", tlist)

    # 返回正在執行的執行緒數量(在數值上等於len(tlist))
    count = threading.active_count()
    print("當前活動執行緒有%d條" % (count))

執行緒衝突

'''
【執行緒衝突】示例:
	多個執行緒併發訪問同一個變數而互相干擾
'''
import threading
import time
money = 0

# CPU分配的時間片不足以完成一百萬次加法運算,
# 因此結果還沒有被儲存到記憶體中就被其它執行緒所打斷
def addMoney():
    global money
    for i in range(1000000):
        money += 1
    print(money)


# 建立執行緒鎖
lock = threading.Lock()

def addMoneyWithLock():
    # print("addMoneyWithLock")
    time.sleep(1)
    global money
    # print(lock.acquire())
    # if lock.acquire():
    #     for i in range(1000000):
    #         money += 1
    # lock.release()
    # 獨佔執行緒鎖
    with lock:  # 阻塞直到拿到執行緒鎖

        # -----下面的程式碼只有拿到lock物件才能執行-----
        for i in range(1000000):
            money += 1
        # 釋放執行緒鎖,以使其它執行緒能夠拿到並執行邏輯
        # ----------------鎖已被釋放-----------------

    print(money)

# 5條執行緒同時訪問money變數,導致結果不正確
def conflictDemo():
    for i in range(5):
        t = threading.Thread(target=addMoney)
        t.start()

# 通過執行緒同步(依次執行)解決執行緒衝突
def handleConflictBySync():
    for i in range(5):
        t = threading.Thread(target=addMoney)
        t.start()
        t.join()  # 一直阻塞到t執行完畢

# 通過依次獨佔執行緒鎖解決執行緒衝突
def handleConflictByLock():
    # 併發5條執行緒
    for i in range(5):
        t = threading.Thread(target=addMoneyWithLock)
        t.start()

if __name__ == '__main__':
    # conflictDemo()
    # handleConflictBySync()
    handleConflictByLock()

    pass

死鎖

死鎖:是指一個資源被多次呼叫,而多次呼叫方都未能釋放該資源就會造成一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖。

互相鎖住對方執行緒需要的資源,造成死鎖局面

執行緒安全

互斥鎖
互斥鎖
    狀態:鎖定/非鎖定
    # 建立鎖
        lock = threading.Lock()
    # 鎖定
        lock.acquire()
    # 釋放
        lock.release()
遞迴鎖
遞迴鎖,重用鎖,用於解決死鎖的問題,可重複鎖

# 遞迴鎖
rlock = threading.RLOCK()

訊號量Semaphore排程執行緒:控制最大併發量

'''
使用Semaphore排程執行緒:控制最大併發量
'''
import threading
import time

# 允許最大併發量3
sem = threading.Semaphore(3)

def doSth(arg):
    with sem:
        tname = threading.current_thread().getName()
        print("%s正在執行【%s】" % (tname, arg))
        time.sleep(1)
        print("-----%s執行完畢!-----\n" % (tname))
        time.sleep(0.1)

if __name__ == '__main__':

    # 開啟10條執行緒
    for i in range(10):
        threading.Thread(target=doSth, args=("巡山",), name="小分隊%d" % (i)).start()
    

協程

協程,又稱微執行緒,纖程。英文名Coroutine。
首先我們得知道協程是啥?協程其實可以認為是比執行緒更小的執行單元。為啥說他是一個執行單元,因為他自帶CPU上下文。這樣只要在合適的時機,我們可以把一個協程切換到另一個協程,只要這個過程中儲存或恢復CPU上下文那麼程式還是可以執行的。

通俗的理解:在一個執行緒中的某個函式,可以在任何地方儲存當前函式的一些臨時變數等資訊,然後切換到另外一個函式中執行,注意不是通過呼叫函式的方式做到的,並且切換的次數以及什麼時候再切換到原來的函式都由開發者自己確定。

協程和執行緒差異

協程的特點在於是一個執行緒執行, 那和多執行緒比,協程有何優勢?
	1.最大的優勢就是協程極高的執行效率。因為子程式切換不是執行緒切換,而是由程式自身控制,因此,沒有執行緒切換的開銷,和多執行緒比,執行緒數量越多,協程的效能優勢就越明顯。
	2.第二大優勢就是不需要多執行緒的鎖機制,因為只有一個執行緒,也不存在同時寫變數衝突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多執行緒高很多。

因為協程是一個執行緒執行,那怎麼利用多核CPU呢?
	最簡單的方法是多程式+協程,既充分利用多核,又充分發揮協程的高效率,可獲得極高的效能。
	
協程的缺點: 它不能同時將CPU的多個核用上,只能使用一個核

Python對協程的支援是通過generator實現的。
在generator中,我們不但可以通過for迴圈來迭代,還可以不斷呼叫next()函式獲取由yield語句返回的下一個值。

# 協程一個簡單實現
def C():
    while True:
        print("=====C=====")
        yield
        time.sleep(0.5)

def D(c):
    while True:
        print("=====D=====")
        next(c)
        time.sleep(0.5)

if __name__ == "__main__":
    c = C()
    D(c)

使用協程

1.使用greenlet + switch實現協程排程

'''
	使用greenlet + switch實現協程排程
'''
from greenlet import greenlet
import time


def func1():
    print("開門走進衛生間")
    time.sleep(3)
    gr2.switch()  # 把CPU執行權交給gr2

    print("飛流直下三千尺")
    time.sleep(3)
    gr2.switch()
    pass


def func2():
    print("一看拖把放旁邊")
    time.sleep(3)
    gr1.switch()

    print("疑是銀河落九天")
    pass


if __name__ == '__main__':
    gr1 = greenlet(func1)
    gr2 = greenlet(func2)
    gr1.switch()  # 把CPU執行權先給gr1
    pass

2.使用gevent +sleep自動將CPU執行權分配給當前未睡眠的協程

'''
	使用gevent + sleep自動將CPU執行權分配給當前未睡眠的協程
'''
import gevent

def func1():
    gevent.sleep(1)
    print("大夢誰先覺")

    gevent.sleep(13)
    print("1:over")
    pass

def func2():
    gevent.sleep(3)
    print("平生我自知")

    gevent.sleep(9)
    print("2:over")
    pass

def func3():
    gevent.sleep(5)
    print("草堂春睡足")

    gevent.sleep(5)
    print("3:over")
    pass

def func4():
    gevent.sleep(7)
    print("窗外日遲遲")

    gevent.sleep(1)
    print("4:over")
    pass

def simpleGevent():
    gr1 = gevent.spawn(func1)
    gr2 = gevent.spawn(func2)
    gr3 = gevent.spawn(func3)
    gr4 = gevent.spawn(func4)
    gevent.joinall([
        gr1, gr2, gr3, gr4
    ])

if __name__ == '__main__':
    simpleGevent()
    

3.通過monkey排程

'''
	使用gevent + monkey.patch_all()自動排程網路IO協程
'''
import gevent
from gevent import monkey
monkey.patch_all()  # 將【標準庫-阻塞IO實現】替換為【gevent-非阻塞IO實現】

import requests
import time

def getPageText(url, order=0):
    print("No%d:%s請求開始..." % (order, url))
    resp = requests.get(url)  # 發起網路請求,返回需要時間——阻塞IO

    html = resp.text
    print("No%d:%s成功返回:長度為%d" % (order, url, len(html)))
    pass

if __name__ == '__main__':
    start = time.time()
    time.clock()
    gevent.joinall([
        gevent.spawn(getPageText, "http://www.sina.com", order=1),
        gevent.spawn(getPageText, "http://www.qq.com", order=2),
        gevent.spawn(getPageText, "http://www.baidu.com", order=3),
        gevent.spawn(getPageText, "http://www.163.com", order=4),
        gevent.spawn(getPageText, "http://www.4399.com", order=5),
        gevent.spawn(getPageText, "http://www.sohu.com", order=6),
        gevent.spawn(getPageText, "http://www.youku.com", order=7),
        gevent.spawn(getPageText, "http://www.iqiyi.com", order=8),
    ])

    end = time.time()
    print("over,耗時%d秒" % (end - start))
    print(time.clock())
    

十一. 程式

程式的概念

python中的多執行緒其實並不是真正的多執行緒,如果想要充分地使用多核CPU的資源,在python中大部分情況需要使用多程式。

程式的概念:
	 程式是程式的一次執行過程, 正在進行的一個過程或者說一個任務,而負責執行任務的則是CPU.
	
程式的生命週期:
	當作業系統要完成某個任務時,它會建立一個程式。當程式完成任務之後,系統就會撤銷這個程式,收回它所佔用的資源。從建立到撤銷的時間段就是程式的生命期

程式之間存在併發性:
	在一個系統中,同時會存在多個程式。他們輪流佔用CPU和各種資源

並行與併發的區別:
	無論是並行還是併發,在使用者看來都是同時執行的,不管是程式還是執行緒,都只是一個任務而已, 
真正幹活的是CPU,CPU來做這些任務,而一個cpu(單核)同一時刻只能執行一個任務。 
並行:多個任務同時執行,只有具備多個cpu才能實現並行,含有幾個cpu,也就意味著在同一時刻可以執行幾個任務。 
併發:是偽並行,即看起來是同時執行的,實際上是單個CPU在多道程式之間來回的進行切換。

同步與非同步的概念:
	同步就是指一個程式在執行某個請求的時候,若該請求需要一段時間才能返回資訊,那麼這個程式將會一直等待下去,直到收到返回資訊才繼續執行下去。 
	非同步是指程式不需要一直等下去,而是繼續執行下面的操作,不管其他程式的狀態。當有訊息返回時系統會通知進行處理,這樣可以提高執行的效率。 
	比如:打電話的過程就是同步通訊,發簡訊時就是非同步通訊。

多執行緒和多程式的關係:
	對於計算密集型應用,應該使用多程式;
	對於IO密集型應用,應該使用多執行緒。執行緒的建立比程式的建立開銷小的多。

建立程式

使用multiprocessing.Process
import multiprocessing
import time

def func(arg):
    pname = multiprocessing.current_process().name
    pid = multiprocessing.current_process().pid
    print("當前程式ID=%d,name=%s" % (pid, pname))

    for i in range(5):
        print(arg)
        time.sleep(1)

if __name__ == "__main__":
    p = multiprocessing.Process(target=func, args=("hello",))
    # p.daemon = True  # 設為【守護程式】(隨主程式的結束而結束)
    p.start()

    while True:
        print("子程式是否活著?", p.is_alive())
        time.sleep(1)
    print("main over")

通過繼承Process實現自定義程式
import multiprocessing
import os

# 通過繼承Process實現自定義程式
class MyProcess(multiprocessing.Process):
    def __init__(self, name, url):
        super().__init__()
        self.name = name
        self.url = url  # 自定義屬性

    # 重寫run
    def run(self):
        pid = os.getpid()
        ppid = os.getppid()
        pname = multiprocessing.current_process().name
        print("當前程式name:", pname)
        print("當前程式id:", pid)
        print("當前程式的父程式id:", ppid)

if __name__ == '__main__':

    # 建立3個程式
    MyProcess("小分隊1", "").start()
    MyProcess("小分隊2", "").start()
    MyProcess("小分隊3", "").start()
    print("主程式ID:", multiprocessing.current_process().pid)

    # CPU核數
    coreCount = multiprocessing.cpu_count()
    print("我的CPU是%d核的" % coreCount)

    # 獲取當前活動的程式列表
    print(multiprocessing.active_children())
同步非同步和程式鎖
import multiprocessing
import random
import time

def fn():
    name = multiprocessing.current_process().name
    print("開始執行程式:", name)
    time.sleep(random.randint(1, 4))
    print("執行結束:", name)

# 多程式
# 非同步執行程式
def processAsync():
    p1 = multiprocessing.Process(target=fn, name="小分隊1")
    p2 = multiprocessing.Process(target=fn, name="小分隊2")
    p1.start()
    p2.start()

# 同步執行
def processSync():
    p1 = multiprocessing.Process(target=fn, name="小分隊1")
    p2 = multiprocessing.Process(target=fn, name="小分隊2")
    p1.start()
    p1.join()
    p2.start()
    p2.join()

# 加鎖
def processLock():
    # 程式鎖
    lock = multiprocessing.Lock()
    p1 = multiprocessing.Process(target=fn2, name="小分隊1", args=(lock,))
    p2 = multiprocessing.Process(target=fn2, name="小分隊2", args=(lock,))
    p1.start()
    p2.start()

def fn2(lock):
    name = multiprocessing.current_process().name
    print("開始執行程式:", name)

    # 加鎖
    # 方式一
    # if lock.acquire():
    #     print("正在工作...")
    #     time.sleep(random.randint(1, 4))
    #     lock.release()

    # 方式二
    with lock:
        print("%s:正在工作..." % name)
        time.sleep(random.randint(1, 4))

    print("%s:執行結束:"% name)


if __name__ == '__main__':
    # processAsync() # 非同步執行
    # processSync()  # 同步執行
    processLock()  # 加程式鎖

使用Semaphore控制程式的最大併發
import multiprocessing
import time

def fn(sem):
    with sem:
        name = multiprocessing.current_process().name
        print("子執行緒開始:", name)
        time.sleep(3)
        print("子執行緒結束:", name)

if __name__ == '__main__':
    sem = multiprocessing.Semaphore(3)
    for i in range(8):
        multiprocessing.Process(target=fn, name="小分隊%d"%i, args=(sem, )).start()

十二. 高階函式

sorted(key=lambda x:x[‘age’])

reversed()

1.map()

程式碼演示:

"""
map(function,iterable)
function:函式
iterable:可迭代物件
作用:將傳入的函式依次作用於可迭代物件中的每一個元素,並把結果【Iterator】返回
"""
#需求1:給一個已知列表中的元素求平方
def square(x):
    return x ** 2
list1 = [1,2,3,4,5]
result1 = map(square,list1)
#注意:map是一個類,表示一種資料型別,集合或者序列,使用類似於list,tuple,set
print(result1)   #<map object at 0x000001EE25431DA0>
print(type(result1))   #<class 'map'>
print(list(result1))  #[1, 4, 9, 16, 25]

result2 = map(lambda x:x ** 2,list1)
print(list(result2))

#str = 10

#需求2:將整型元素的列表轉換為字串元素的列表
#舉例:[1,2,3,4]------>["1","2","3","4"]
#str(1) ---- >字串1
#注意:在使用系統函式之前,最好不要出現同名的變數
result3 = map(str,[1,2,3,4])
print(list(result3))


#需求3:已知兩個整型列表,將兩個列表中相同位置的元素相加,得到一個新的列表
def add(x,y):
    return  x  + y
l1 = [1,2,3,4,5]
l2 = [6,7,8,9,10]

result4 = map(add,l1,l2)
print(list(result4))

2.reduce()

程式碼演示:

from  functools  import  reduce

"""
reduce(function,Iterable)  :通過函式對引數列表中的元素進行累積
function:函式
Iterable:可迭代物件,一般使用列表
工作原理:用傳給reduce的function先作用於list中第一個和第二個元素,用得到的結果和list中第三個元素計算。。。
reduce(add,[a,b,c,d])
add(add(add(a,b),c),d)---->遞迴
"""

#需求1;求一個已知列表中元素的和
list1 = [1,2,3,4,5]
def add(x,y):
    return x + y
result1 = reduce(add,list1)
print(result1)

result2 = reduce(lambda x,y:x + y,list1)
print(result2)

#需求2:將列表[1,3,5,7,9]變換成整數13579
"""
分析:
13 = 1 * 10 + 3
135 = 13 * 10 + 5
1357 = 135 * 10 + 7
13579 = 1357 * 10 + 9
"""
list2 = [1,3,5,7,9]
def fn(x,y):
    return x * 10 + y

result3 = reduce(fn,list2)
print(result3)

#需求3:
#結合map函式,實現一個將str轉換為int的函式   int()

#思路:傳進來一個字串,返回一個對應的整數
def strToInt(s):
    digits = {"0":0,"1":1,"2":2,"3":3,"4":4}
    return digits[s]

#"23401"------>23401
r0 = map(strToInt,"23401")
print(list(r0))   #[2, 3, 4, 0, 1]

r1 = reduce(fn,map(strToInt,"23401"))
print(r1)   #23401
print(type(r1))   #<class 'int'>

3.filter()

程式碼演示:

"""
filter(function,序列)
作用:通過指定的條件過濾列表中的元素
工作原理:將傳入的函式依次作用於列表中的每一個元素,根據返回的是True還是False決定元素是否需要保留
"""

#需求1:將列表中的偶數篩選出來
list1 = [1,2,3,4,5,6,7,8,9]
#作用:定義篩選的規則
def func(num):
    if num % 2 == 0:
        return  True
    return  False

result1  = filter(func,list1)
print(result1)
print(list(result1))  #[2, 4, 6, 8]

4.sorted()

程式碼演示:

#1.普通排序
#預設為升序排序,得到了的一個新的列表
list1 = [4,5,23,3,5,7]
result1 = sorted(list1)
print(list1)
print(result1)  #r[3, 4, 5, 5, 7, 23]

#2.按照絕對值進行排序
#預設為升序排序,排序的依據是所有元素的絕對值的大小
list2 = [4,5,-23,3,-5,7]
result2 = sorted(list2,key=abs)
print(result2)  #[3, 4, 5, -5, 7, -23]

#3.降序升序
list3 = [4,5,23,3,5,7]
result3 = sorted(list3,reverse=True)
print(result3)

#4.字元也可以實現排序
list4 = ["f","a","k","z"]
result4 = sorted(list4)
print(result4)

#5.自定義排序規則
#預設為升序排序
def myFunc(str):
    return len(str)
list5 = ["gsg","a","34535","efgg","562875678257fhjawhgj"]
result5 = sorted(list5,key=myFunc)
print(result5)

相關文章