重點回顧:
- GIL鎖是CPython解釋器的問題
- copy模塊的deepcopy和copy方法對於tuple拷貝的區別
- 私有屬性的繼承問題和重整
- 多模塊引入問題
- Python對象的__mro__ ,以及導致的 super調用順序,還有 類屬性解析順序
- property創建和使用
- 各種魔法屬性
- 上下文管理器的使用,返回對象是
__enter__
返回值 - 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鎖問題
- 更換python解釋器(只有CPython有GIL鎖問題)
- 使用別的語言寫任務,然後用Python引入:
// libdead_loop.c
void DeadLoop(){
while(1){
;
}
}
gcc libdead_loop.c -shared -o libdead_loop.so
1.4 何時使用不同的多任務模式:
因爲多進程可以利用多個核心,而 多線程和協程,只是實現了阻塞時期的任務切換:
- 計算/CPU密集型程序:使用多進程
- IO密集型程序: 使用多線程、協程
2. 深淺拷貝
import copy
copy.copy
copy.deepcopy
(唯一的深拷貝方法)- 對象.copy
- 切片
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個模塊,
- 無論是
import common
還是from common import MYLIST
, 他們指向的都是同一份MYLIST
- 如果是
import common
,那麼common.FLAG
指向的是同一個對象 - 如果是
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'>
- 類屬性在內存中只保存一份
- 實例屬性在每個對象中都要保存一份
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
調用父類方法
super().__init__()
super(類名, self).__init__()
每個類都有一個__mro__
,是Python解釋器使用C3算法計算的,顯示的就是找方法的順序。
- 類中調用
super()
,實際上是在__mro__
顯示的元祖列表中去匹配,如果匹配到了類型,則調用的列表中的下一個類型的方法 - 類引用類屬性和類方法時,也是根據
__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)
- 還能使用
super(類名, self).方法
, 這個結果是在__mro__
中根據這個傳入的類名去查找,然後調用匹配順序的下一個類的方法。 所以super也能夠靈活的調用 繼承鏈中的方法
總結:
- super().__init__相對於類名.init,在單繼承上用法基本無差
- 但在多繼承上有區別,super方法能保證每個父類的方法只會執行一次,而使用類名的方3. 會導致方法被執行多次,具體看前面的輸出結果
- 多繼承時,使用super方法,對父類的傳參數,應該是由於python中super的算法導致的原因,必須把參數全部傳遞,否則會報錯
- 單繼承時,使用super方法,則不能全部傳遞,只能傳父類方法所需的參數,否則會報錯
- 多繼承時,相對於使用類名.__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屬性
注意:
- 定義 時只有一個
self
參數,不多不少 - 調用時不用加括號
@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種:
@property
@屬性名.setter
@屬性名.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 方法
- 經典類中的屬性只有一種訪問方式,其對應被 @property 修飾的方法
- 新式類中的屬性有三種訪問方式,並分別對應了三個被@property、@方法名.setter、@方法名.deleter修飾的方法
6.2 使用類方法創建
新式類和經典類使用這種方法是一樣的
步驟:
- 先定義好每個方法: getter, setter, deleter
- 最後在類的內部用
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__
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 總結通用寫法
- 使用
*args, **kwargs
傳遞參數 - 把返回值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")
# 結果爲
=====運行裝飾功能=====
程序運行中
調用步驟:
- 運行
outer
,將參數傳遞作爲outer
的參數,然後把這個函數的返回值作爲裝飾器 - 拿到
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