重点回顾:
- 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.
- 公有变量,可以继承,子类和对象可以访问 2.
-
私有变量, 可以继承,子类和对象可以访问;
但是不推荐外部直接使用, 而且无法被from abc import xxxx
导入
3.
- 私有变量,不会继承,子类只能通过父类的公有方法来访问,而且有 名字重整 4.
-
魔法属性和方法,可以继承。
比如父类自定义的
__init__
方法子类也会有,而且会默认调用父类的__init__
5.
- 单下划线后置,为的是避免和Python关键字的冲突,不推荐使用
xxxx
_xxxx
:
__xxx
:
__xxx__
:
xx_
:
私有属性和名字重整
带有__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