Python視頻學習(七、★Python高級)

重點回顧:

  1. GIL鎖是CPython解釋器的問題
  2. copy模塊的deepcopy和copy方法對於tuple拷貝的區別
  3. 私有屬性的繼承問題和重整
  4. 多模塊引入問題
  5. Python對象的__mro__ ,以及導致的 super調用順序,還有 類屬性解析順序
  6. property創建和使用
  7. 各種魔法屬性
  8. 上下文管理器的使用,返回對象是__enter__返回值
  9. hasattr, setattr,getattr方法

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, value):
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整型數字")

a = Money()
a.money = 100
print(a.money)

7. 魔法屬性

7.1 __doc__

class Foo:
    """ 描述類信息,這是用於看片的神奇 """
    def func(self):
        pass

print(Foo.__doc__)
#輸出:類的描述信息

7.2 __module____class__

  • __module__ 返回的是對象屬於哪個模塊,是一個字符串
  • __class__返回的是對象屬於哪一個類, 是一個對象
# test.py
# -*- coding:utf-8 -*-

class Person(object):
    def __init__(self):
        self.name = 'laowang'
# main.py
from test import Person

obj = Person()
print(obj.__module__)  # 輸出 test 即:輸出模塊
print(obj.__class__)  # 輸出 test.Person 即:輸出類

7.3 __init__

初始化方法,而不是構造方法

7.4 __del__

  • 當對象在內存中被釋放時,自動觸發執行
class Foo:
    def __del__(self):
        pass
f = Foo()
del f   # __del__被調用 

7.5 __call__

能夠讓對象本身被調用

class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        print('__call__')


obj = Foo()  # 執行 __init__
obj()  # 執行 __call__

7.6 __dict__

  • 類或對象中的所有屬性

類的方法是屬於類自己的屬性

class Province(object):
    country = 'China'

    def __init__(self, name, count):
        self.name = name
        self.count = count

    def func(self, *args, **kwargs):
        print('func')

# 獲取類的屬性,即:類屬性、方法、
print(Province.__dict__)
# 輸出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}

obj1 = Province('山東', 10000)
print(obj1.__dict__)
# 獲取 對象obj1 的屬性
# 輸出:{'count': 10000, 'name': '山東'}

obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 獲取 對象obj1 的屬性
# 輸出:{'count': 20000, 'name': '山西'}

7.7 __str__

打印對象的時候返回的內容

class Foo:
    def __str__(self):
        return 'laowang'


obj = Foo()
print(obj)
# 輸出:laowang

7.8 __getitem__、__setitem__、__delitem__

  • 用於索引操作,如字典。以上分別表示獲取、設置、刪除數據
class Foo(object):

    def __getitem__(self, key):
        print('__getitem__', key)

    def __setitem__(self, key, value):
        print('__setitem__', key, value)

    def __delitem__(self, key):
        print('__delitem__', key)


obj = Foo()

result = obj['k1']      # 自動觸發執行 __getitem__
obj['k2'] = 'laowang'   # 自動觸發執行 __setitem__
del obj['k1']           # 自動觸發執行 __delitem__

7.9 __getslice__、__setslice__、__delslice__

  • 用於切片
class Foo(object):

    def __getslice__(self, i, j):
        print('__getslice__', i, j)

    def __setslice__(self, i, j, sequence):
        print('__setslice__', i, j)

    def __delslice__(self, i, j):
        print('__delslice__', i, j)

obj = Foo()

obj[-1:1]                   # 自動觸發執行 __getslice__
obj[0:1] = [11,22,33,44]    # 自動觸發執行 __setslice__
del obj[0:2]                # 自動觸發執行 __delslice__

微信開發包:http://wechat-python-sdk.com/

8. with上下文管理

系統資源如文件、數據庫連接、socket 而言,應用程序打開這些資源並執行完業務邏輯之後,必須做的一件事就是要關閉(斷開)該資源。

8.1 自定義上下文管理器

context manager 就是一個實現了 __enter____exit__的類,可以配合with關鍵字

class File():

    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        print("entering")
        self.f = open(self.filename, self.mode)
        return self.f

    def __exit__(self, *args):
        print("will exit")
        self.f.close()

with File('out.txt', 'w') as f:
    print("writing")
    f.write('hello, python')

注意:實際返回的對象並不是__init__返回的對象,而是先調用__init__之後,如果碰到了with,再去調用__enter__方法,將這個方法返回的結果返回。

8.2 contextmanager裝飾器實現上下文管理器

from contextlib import contextmanager

@contextmanager
def my_open(path, mode):
    f = open(path, mode)
    yield f
    f.close()


with my_open('out.txt', 'w') as f:
    f.write("hello , the simplest context manager")
  • yield前面的語句會在 __enter__的時候調用
  • yield後面的語句會在__exit__的時候調用
  • yield返回值就是 方法調用的返回值

9. 閉包

閉包就是函數內嵌套函數,返回值爲內部函數,並且通常內部函數引用了外部函數的參數——閉包是一塊維護了函數+變量的空間

def linecalc(k, b):
    def func(x):
        return k * x + b
    return func


myfunc1 = linecalc(5, 1)   # y = 5x+1 的線
print(myfunc1(4))   # 21
myfunc2 = linecalc(6, 1)  # y = 6x+1 的線
print(myfunc2(4))   # 25
print(myfunc1(4))   # 21
  • 對象:就是內部維護了 函數+變量
  • 閉包:內部的函數維護的空間中,還包含了函數+變量,就相當於一個輕量級的對象
  • 普通的函數:只有函數代碼,並沒有維護變量

閉包修改外部變量內容——nonlocal


def outer():
    x = 20
    def inner():
        nonlocal x
        print(x)   # 20
        x = 10
        print(x)   # 10
    inner()
outer()

函數內部好像有 變量預解析:

  • 下面的代碼中,x會自動往上層找,找到外面的20來打印
def outer():
    x = 20
    def inner():
        print(x)   # 20

    inner()
  • 下面的情況中,內部定義了x,所以第一句知道此時x沒有定義,就報錯了
def outer():
    x = 20
    def inner():
        print(x)  # 報錯
        x = 10
    inner()
outer()

10 ★★裝飾器

不懂裝飾器,都不好意思說會python

10.1 python中的裝飾器

1 別的語言的裝飾器模式

def origin():
	print("哈哈哈1")
	print("哈哈哈2")
	print("哈哈哈3")

def decoration():
	print("===驗證模塊===")
	origin()
	print("===資源清理==="

decoration()

但是對於python來說,這樣就需要把以前調用origin的代碼全部修改成decoration, 違反了開放封閉原則

2 python的裝飾器模式——閉包實現

def decoration(func):
	def new():
		print("===驗證模塊===")
		func()
		print("===資源清理==="
	return new

def origin():
	print("哈哈哈1")
	print("哈哈哈2")
	print("哈哈哈3")


origin()  # 這是原來的origin

origin = decoration(origin)  # 此時origin成了新的new

origin()  # 被裝飾之後的origin

3 裝飾器語法糖

def decoration(func):
	def new():
		print("===驗證模塊===")
		func()
		print("===資源清理==="
	return new

@decoration
def origin():
	print("哈哈哈1")
	print("哈哈哈2")
	print("哈哈哈3")

origin() # 已經是裝飾過的origin了

上面代碼中,加上@decoration, 就和10.2中,將 origin的引用指向新的new效果是一樣的。

裝飾器只能在原函數之前和之後增加功能,無法插入函數內部

10.2 帶參數無返回值的裝飾器

def mydec(func):
    def inner(paraname):
        print("裝飾前")
        func(paraname)
        print("裝飾後")
    return inner


@mydec
def demo(name):
    print(f"我的名字是{name}")

demo("高富帥")

不定長參數的裝飾器

def mydec(func):
    def inner(*args, **kwargs):
        print("裝飾前")
        func(*args, **kwargs)
        print("裝飾後")
    return inner


@mydec
def demo(name,*args, **kwargs):
    print(f"我的名字是{name}")
    print(f"======={args}=======")
    print(f"======={kwargs}=======")


demo("高富帥")

10.3 有返回值函數的裝飾器

def mydec(func):
    def inner(*args, **kwargs):
        print("裝飾前")
        result = func(*args, **kwargs)
        print("裝飾後")
        return result
    return inner


@mydec
def demo(name,*args, **kwargs):
    print(f"我的名字是{name}")
    print(f"======={args}=======")
    print(f"======={kwargs}=======")
    return len(args)


a = demo("高富帥",3,4,5)
print(a)

10.4 總結通用寫法

  1. 使用 *args, **kwargs 傳遞參數
  2. 把返回值return

10.5 一個裝飾器裝飾多個函數

  • 就是給每個函數都加上裝飾器, 這樣會爲每一個函數創建一個閉包
  • 裝飾是在設置了@就已經創建好了,而不是函數調用的時候
def mydec(func):
    print("裝飾器運行了")
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner


@mydec
def demo1(*args, **kwargs):
    return len(args)

@mydec
def demo2(name,age):
    print(f"我叫{name},年齡是{age}")

# 調用demo1
a = demo1("高富帥",3,4,5)
print(a)

# 調用demo2
demo2("張三",18)

結果:

裝飾器運行了
裝飾器運行了
4
我叫張三,年齡是18

10.6 多個裝飾器修飾一個函數

順序: 離函數定義近的先裝飾,離函數定義遠的後裝飾

def mydec1(func):
    print("裝飾器1運行了")

    def inner(*args, **kwargs):
        print("裝飾器1的功能")
        return func(*args, **kwargs)

    return inner


def mydec2(func):
    print("裝飾器2運行了")

    def inner(*args, **kwargs):
        print("裝飾器2的功能")
        return func(*args, **kwargs)

    return inner


@mydec1
@mydec2
def demo(*args, **kwargs):
    return len(args)

demo()

結果:

裝飾器2運行了
裝飾器1運行了
裝飾器1的功能
裝飾器2的功能

10.7 類裝飾器

  • @類名的含義是: demo = 類名(demo)
  • 所以裝飾的類,要有__call__
class Decoration(object):
    def __init__(self,func):
        print("====裝飾中====")
        self.func = func

    def __call__(self, *args, **kwargs):
        print("=====運行裝飾的功能====")
        return self.func(*args, **kwargs)


@Decoration
def demo(name, *args, **kwargs):
    print(f"運行打印==={name}")
    return name


print(demo("haha"))

使用類方法裝飾

class Decoration(object):

    @classmethod
    def mydec(cls, func):
        print("====裝飾中====")
        def inner(*args, **kwargs):
            print("====運行裝飾功能====")
            return func(*args, **kwargs)
        return inner


@Decoration.mydec
def demo(name, *args, **kwargs):
    print(f"運行打印==={name}")
    return name

使用類的靜態方法裝飾

class Decoration(object):

    @staticmethod
    def mydec(func):
        print("====裝飾中====")
        def inner(*args, **kwargs):
            print("====運行裝飾功能====")
            return func(*args, **kwargs)
        return inner


@Decoration.mydec
def demo(name, *args, **kwargs):
    print(f"運行打印==={name}")
    return name


print(demo("haha"))

10.8 裝飾器帶參數

嵌套三層:

  • 第一層收裝飾器的參數
  • 第二層接收被裝飾函數
  • 第三層接收被裝飾函數的參數
def outter(arg):
    print("=====運行參數傳遞=====")
    def inner(func):
        print("=====運行裝飾函數=====")
        def core(*args, **kwargs):
            print("=====運行裝飾功能=====")
            return func(*args, **kwargs)
        return core
    return inner

@outter("hello")
def demo(*args, **kwargs):
    print("程序運行中")

# 結果爲
=====運行參數傳遞=====
=====運行裝飾函數=====

demo("123")

# 結果爲
=====運行裝飾功能=====
程序運行中

調用步驟:

  1. 運行outer,將參數傳遞作爲 outer的參數,然後把這個函數的返回值作爲裝飾器
  2. 拿到outer的返回值,調用它,將被裝飾函數作爲參數傳入,返回值作爲被裝飾後的函數
def set_func(level):
	def decoration(func):
		def newfunc(*args, **kwargs):
			if level == 1:
				print("level1級別驗證")
			elif level == 2:
				print("level2級別驗證")
			return func(*args, **kwargs)
		return newfunc
	return decoration

@set_level(1)
def test1():
	pass

@set_level(2)
def test2():
	pass
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章