1. 程式人生 > >Python視訊學習(七、Python高階)

Python視訊學習(七、Python高階)

重點回顧:

  1. GIL鎖是CPython直譯器的問題
  2. copy模組的deepcopy和copy方法對於tuple拷貝的區別
  3. 私有屬性的繼承問題和重整
  4. Python物件的__mro__ ,以及導致的 super呼叫順序,還有 類屬性解析順序
  5. property建立和使用
  6. 各種魔法屬性
  7. 上下文管理器的使用,返回物件是__enter__返回值

1. GIL鎖

1.1 多工不同情形下的CPU佔用率

使用的是htop程式檢視的CPU佔用情況。

單執行緒死迴圈:

while True:
	pass

在這裡插入圖片描述

雙執行緒死迴圈

import threading

def doforever:
	while True
: pass t = threading.Thread(target = doforever) t.start() while True: pass

在這裡插入圖片描述

雙程序死迴圈

import multiprocessing

def doforever:
	while True:
		pass

t = multiprocessing.Process(target = doforever)
t.start()

while True:
	pass

在這裡插入圖片描述

1.2 GIL鎖的概念特點

GIL(global interpreter lock)是全域性直譯器鎖,它不是Python語言的特性,而是由於歷史原因,使得CPython直譯器在實現的時候加上的。以前guido嘗試過移除GIL鎖,但是移除並不是那麼簡單的,還要加上很多別的控制程式碼,所以最終的效能反而不如之前好

Guido的宣告:http://www.artima.com/forums/flat.jsp?forum=106&thread=214235 The language doesn’t require the GIL – it’s only the CPython virtual machine that has historically been unable to shed it.

GIL鎖的特點:

  • 每個執行緒在執行的過程都需要先獲取GIL,保證同一時刻只有一個執行緒可以執行程式碼。所以就算有多個CPU,一個程式內同一時刻真正執行的執行緒數還是隻有一個。
  • 執行緒釋放GIL鎖的情況: 在IO操作等可能會引起阻塞的system call之前,可以暫時釋放GIL,但在執行完畢後,必須重新獲取GIL Python 3.x使用計時器(執行時間達到閾值後,當前執行緒釋放GIL)或Python 2.x,tickets計數達到100
  • 在Python中如果要真正使用全部的核心資源,應該使用多程序
  • 多執行緒爬取比單執行緒效能有提升,因為遇到IO阻塞會自動釋放GIL鎖

GIL面試題如下 描述Python GIL的概念, 以及它對python多執行緒的影響?編寫一個多執行緒抓取網頁的程式,並闡明多執行緒抓取程式是否可比單執行緒效能有提升,並解釋原因。

1.3 如何避免GIL鎖問題

  1. 更換python直譯器(只有CPython有GIL鎖問題)
  2. 使用別的語言寫任務,然後用Python引入:
// libdead_loop.c
void DeadLoop(){
	while(1){
		;
	}
}
gcc libdead_loop.c -shared -o libdead_loop.so

Python呼叫C語言程式碼來啟動多執行緒

1.4 何時使用不同的多工模式:

因為多程序可以利用多個核心,而 多執行緒和協程,只是實現了阻塞時期的任務切換:

  1. 計算/CPU密集型程式:使用多程序
  2. IO密集型程式: 使用多執行緒、協程

2. 深淺拷貝

import copy
  1. copy.copy
  2. copy.deepcopy (唯一的深拷貝方法)
  3. 物件.copy
  4. 切片
import copy

a = [1,2,3]
b = copy.copy(a)

id(a)
2732216717384
id(b)
2732218239560
# 成功拷貝
import copy

a = [1,2,3]
b = [4,5,6]
c = [a,b]
d = copy.copy(c)

id(c)
2732216577864
id(d)
2732218221320

a.append(9)
c
[[1, 2, 3, 9], [4, 5, 6]]
d
[[1, 2, 3, 9], [4, 5, 6]]
# 一起受影響,所以這是 淺拷貝
import copy

a = [1,2,3]
b = [4,5,6]
c = [a,b]
d = copy.deepcopy(c)

id(c)
2732216577864
id(d)
2732217733576

a.append(8)
c
[[1, 2, 3, 9, 8], [4, 5, 6]]
d
[[1, 2, 3, 9], [4, 5, 6]]
# d不受影響,所以這是 深拷貝

拷貝元祖:

import copy
a = (1,2)
b = copy.copy(a)
id(a)
2732216884680
id(b)
2732216884680
# 對於不可變型別,因為沒有拷貝的必要,所以  copy是指向同一個元素
c = copy.deepcopy(c)
id(c)
2732216884680
# 深拷貝在拷貝只有 不可變物件的元祖時,也是使用的 直接指向
import copy
a = [1,2,3]
b = [4,5,6]
c = (a,b)
d = copy.copy(c)
e = copy.deepcopy(c)
id(c)
2732217828552
id(d)
2732217828552
id(e)
2732218154952
# 對於內部含有可變物件的元祖, deepcopy就會拷貝,而copy不拷貝

a.append(9)
c 
([1, 2, 3, 9], [4, 5, 6])
d
([1, 2, 3, 9], [4, 5, 6])
e
([1, 2, 3], [4, 5, 6])

3. 私有化

幾種屬性/方法的定義名稱:

1.xxxx
公有變數,可以繼承,子類和物件可以訪問
2._xxxx:
私有變數, 可以繼承,子類和物件可以訪問; 但是不推薦外部直接使用,而且無法被from abc import xxxx 匯入
3.__xxx:
私有變數,不會繼承,子類只能通過父類的公有方法來訪問,而且有名字重整
4.__xxx__:
魔法屬性和方法,可以繼承。 比如父類自定義的__init__方法子類也會有,而且會預設呼叫父類的__init__
5.xx_:
單下劃線後置,為的是避免和Python關鍵字的衝突,不推薦使用

私有屬性和名字重整

帶有__xxx形式的屬性/方法,只有在類中自己新增的時候,才會名字重整。之後自己新增的不是私有屬性.

class A(object):
    def __init__(self,name):
        self.__name = name

    def __hidden(self):
        return self.__name


a = A("Miller")
print(a.__dict__)
# 名字重整的結果
# {'_A__name': 'Miller'}   
print(A.__dict__)
# {'__module__': '__main__', '__init__': <function A.__init__ at 0x000001D5CA59FAE8>, '_A__hidden': <function A.__hidden at 0x000001D5CA59FA60>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
# 也有名字重整的  __hidden方法

# print(a.__name)  報錯

a.__age= 10
print(a.__age)
# 10
# 這個就不是隱藏屬性了

自動呼叫父類構造方法:

class A(object):
    def __init__(self):
        print("哈哈")

class B(A):
    pass

b = B()
# 哈哈
# 自動呼叫了父親的構造方法

繼承父類構造方法

class A(object):
    def __init__(self,name,age):
        print("哈哈")

class B(A):
    pass

b = B()
# TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
# 報錯,所以說子類繼承了父類的構造方法

重寫父類方法

class A(object):
    def __init__(self,name,age):
        print("哈哈")

class B(A):
    def __init__(self):
        print("你好")

b = B()
# 你好
# 子類重寫了父類方法

呼叫多個父類中的哪個方法

class A(object):
    def __init__(self):
        # self._name = name
        print("哈哈")


class B(object):
    def __init__(self):
        print("嘿嘿")
class C(B,A):
    pass

c = C()
# 嘿嘿
# 繼承的時候,誰寫在前面,就呼叫哪個父類的方法

4. 匯入模組的問題

4.1 重新匯入模組

from imp import reload

reload(模組名)   # 注意,這個模組一定要之前匯入過,才能重新匯入

應用:可以在執行時修改程式碼,然後讓程式重新帶入新程式碼內容

4.2 多模組開發的問題

開發時經常多模組,公用的內容放在一個模組中:

# common
FLAG = True
MYLIST = []

然後另外2個模組,

  1. 無論是 import common還是 from common import MYLIST, 他們指向的都是同一份 MYLIST
  2. 如果是import common,那麼common.FLAG指向的是同一個物件
  3. 如果是from common import FLAG,那麼每個模組中維護的FLAG物件不是同一份

5. 再談python物件

5.1 __class__

每個物件都有一個__class__能夠獲取他的類物件,從可以呼叫類的屬性:

class A(object):
	count = 10
    def __init__(self,name):
        self.name = name
a = A("哈哈")

print(a.__class__)
print(a.__class__.count)
print(A.__class__)
# <class '__main__.A'>
# 10
# <class 'type'>

在這裡插入圖片描述

  1. 類屬性在記憶體中只儲存一份
  2. 例項屬性在每個物件中都要儲存一份

5.2 __dict__

__dict__ 屬性內部是物件的 屬性鍵值對

print(a.__dict__)
# {'name': '哈哈'}

5.3 ★多繼承的MRO

在多繼承中,如果直接使用Parent.__init__(args)的方法,會導致交叉的祖先類被多次呼叫構造方法:

class Parent(object):
    def __init__(self, name):
        print('parent的init開始被呼叫')
        self.name = name
        print('parent的init結束被呼叫')

class Son1(Parent):
    def __init__(self, name, age):
        print('Son1的init開始被呼叫')
        self.age = age
        Parent.__init__(self, name)
        print('Son1的init結束被呼叫')

class Son2(Parent):
    def __init__(self, name, gender):
        print('Son2的init開始被呼叫')
        self.gender = gender
        Parent.__init__(self, name)
        print('Son2的init結束被呼叫')

class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init開始被呼叫')
        Son1.__init__(self, name, age)  # 單獨呼叫父類的初始化方法
        Son2.__init__(self, name, gender)
        print('Grandson的init結束被呼叫')

gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年齡:', gs.age)
print('性別:', gs.gender)

# 執行結果:
Grandson的init開始被呼叫
Son1的init開始被呼叫
parent的init開始被呼叫
parent的init結束被呼叫
Son1的init結束被呼叫
Son2的init開始被呼叫
parent的init開始被呼叫
parent的init結束被呼叫
Son2的init結束被呼叫
Grandson的init結束被呼叫
姓名: grandson
年齡: 12
性別: 男

在這裡插入圖片描述

使用super呼叫父類方法

  1. super().__init__()
  2. super(類名, self).__init__()

每個類都有一個__mro__,是Python直譯器使用C3演算法計算的,顯示的就是找方法的順序。

  1. 類中呼叫super(),實際上是在__mro__顯示的元祖列表中去匹配,如果匹配到了型別,則呼叫的列表中的下一個型別的方法
  2. 類引用類屬性和類方法時,也是根據__mro__中的順序去查詢的:
class Parent(object):
    count = 10


class Child(Parent):
    pass


print(Child.count)
c = Child()
print(c.count)
# 10
# 10

#因為是根據 __mro__找到了父類Parent
class Parent(object):
    def __init__(self, name, *args, **kwargs):  # 為避免多繼承報錯,使用不定長引數,接受引數
        print('parent的init開始被呼叫')
        self.name = name
        print('parent的init結束被呼叫')

class Son1(Parent):
    def __init__(self, name, age, *args, **kwargs):  # 為避免多繼承報錯,使用不定長引數,接受引數
        print('Son1的init開始被呼叫')
        self.age = age
        super().__init__(name, *args, **kwargs)  # 為避免多繼承報錯,使用不定長引數,接受引數
        print('Son1的init結束被呼叫')

class Son2(Parent):
    def __init__(self, name, gender, *args, **kwargs):  # 為避免多繼承報錯,使用不定長引數,接受引數
        print('Son2的init開始被呼叫')
        self.gender = gender
        super().__init__(name, *args, **kwargs)  # 為避免多繼承報錯,使用不定長引數,接受引數
        print('Son2的init結束被呼叫')

class Grandson(Son1, Son2):
    def __init__(self, name, age, gender):
        print('Grandson的init開始被呼叫')
        # 多繼承時,相對於使用類名.__init__方法,要把每個父類全部寫一遍
        # 而super只用一句話,執行了全部父類的方法,這也是為何多繼承需要全部傳參的一個原因
        # super(Grandson, self).__init__(name, age, gender)
        super().__init__(name, age, gender)
        print('Grandson的init結束被呼叫')

print(Grandson.__mro__)

gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年齡:', gs.age)
print('性別:', gs.gender)
  1. 還能使用super(類名, self).方法, 這個結果是在__mro__中根據這個傳入的類名去查詢,然後呼叫匹配順序的下一個類的方法。 所以super也能夠靈活的呼叫 繼承鏈中的方法

總結:

  1. super().__init__相對於類名.init,在單繼承上用法基本無差
  2. 但在多繼承上有區別,super方法能保證每個父類的方法只會執行一次,而使用類名的方3. 會導致方法被執行多次,具體看前面的輸出結果
  3. 多繼承時,使用super方法,對父類的傳引數,應該是由於python中super的演算法導致的原因,必須把引數全部傳遞,否則會報錯
  4. 單繼承時,使用super方法,則不能全部傳遞,只能傳父類方法所需的引數,否則會報錯
  5. 多繼承時,相對於使用類名.__init__方法,要把每個父類全部寫一遍, 而使用super方法,只需寫一句話便執行了全部父類的方法,這也是為何多繼承需要全部傳參的一個原因

6. ★property屬性

property能夠像屬性一樣的呼叫方法,比較像別的語言中的getter與setter。

class Foo:
    def func(self):
        pass

    # 定義property屬性
    @property
    def prop(self):    # 這個地方必須只寫一個self引數,不能多不能少,多了就報錯
        pass

# ############### 呼叫 ###############
foo_obj = Foo()
foo_obj.func()  # 呼叫例項方法
foo_obj.prop  # 呼叫property屬性

注意:

  1. 定義 時只有一個self引數,不多不少
  2. 呼叫時不用加括號

@property 這種的,都叫裝飾器

另一個例子

class Pager:
    def __init__(self, current_page):
        # 使用者當前請求的頁碼(第一頁、第二頁...)
        self.current_page = current_page
        # 每頁預設顯示10條資料
        self.per_items = 10 

    @property
    def start(self):
        val = (self.current_page - 1) * self.per_items
        return val

    @property
    def end(self):
        val = self.current_page * self.per_items
        return val

# ############### 呼叫 ###############
p = Pager(1)
p.start  # 就是起始值,即:m
p.end  # 就是結束值,即:n

6.1 用裝飾器建立

經典類中只有一種@property,而新式類有3種:

  1. @property
  2. @屬性名.setter
  3. @屬性名.deleter
class Goods:
    """python3中預設繼承object類
        以python2、3執行此程式的結果不同,因為只有在python3中才有@xxx.setter  @xxx.deleter
    """
    @property
    def price(self):
        print('@property')

    @price.setter
    def price(self, value):
        print('@price.setter')

    @price.deleter
    def price(self):
        print('@price.deleter')

# ############### 呼叫 ###############
obj = Goods()
obj.price          # 自動執行 @property 修飾的 price 方法,並獲取方法的返回值
obj.price = 123    # 自動執行 @price.setter 修飾的 price 方法,並將  123 賦值給方法的引數
del obj.price      # 自動執行 @price.deleter 修飾的 price 方法
  1. 經典類中的屬性只有一種訪問方式,其對應被 @property 修飾的方法
  2. 新式類中的屬性有三種訪問方式,並分別對應了三個被@property、@方法名.setter、@方法名.deleter修飾的方法

6.2 使用類方法建立

新式類和經典類使用這種方法是一樣的

步驟:

  1. 先定義好每個方法: getter, setter, deleter
  2. 最後在類的內部用property方法呼叫傳參,得到的返回值即為屬性
class Foo(object):
    def get_bar(self):
        print("getter...")
        return 'laowang'

    def set_bar(self, value): 
        """必須兩個引數"""
        print("setter...")
        return 'set value' + value

    def del_bar(self):
        print("deleter...")
        return 'laowang'

    BAR = property(get_bar, set_bar, del_bar, "description...")

obj = Foo()

obj.BAR  # 自動呼叫第一個引數中定義的方法:get_bar
obj.BAR = "alex"  # 自動呼叫第二個引數中定義的方法:set_bar方法,並將“alex”當作引數傳入
desc = Foo.BAR.__doc__  # 自動獲取第四個引數中設定的值:description...
print(desc)
del obj.BAR  # 自動呼叫第三個引數中定義的方法:del_bar方法

property方法的引數:

  • 第一個引數是方法名,呼叫 物件.屬性 時自動觸發執行方法
  • 第二個引數是方法名,呼叫 物件.屬性 = XXX 時自動觸發執行方法
  • 第三個引數是方法名,呼叫 del 物件.屬性 時自動觸發執行方法
  • 第四個引數是字串,呼叫 物件.屬性.doc ,此引數是該屬性的描述資訊

django中常用property

property的應用——當作getter和setter:

class Money(object):
    def __init__(self):
        self.__money = 0

    # 使用裝飾器對money進行裝飾,那麼會自動新增一個叫money的屬性,當呼叫獲取money的值時,呼叫裝飾的方法
    @property
    def money(self):
        return self.__money

    # 使用裝飾器對money進行裝飾,當對money設定值時,呼叫裝飾的方法
    @money.setter
    def money(self