python-04-装饰器、描述器
python 装饰器、描述器、常用内置装饰器
本节知识点
知识点一:装饰器
知识点二:描述器
知识点三:常用内置装饰器
一、part one 装饰器
装饰器是一个函数,一个用来包装函数的函数,装饰器在函数申明(不需要调用)完成的时候被调用,调用之后返回一个修改之后的函数对象,将其重新赋值原来的标识符,并永久丧失对原始函数对象的访问。对某个方法应用了装饰方法后, 其实就改变了被装饰函数名称所引用的函数代码块入口点,使其重新指向了由装饰方法所返回的函数入口点。
作用:能够给现有的函数增加功能
如何给一个现有的函数增加执行计数的功能

以面向对象的方式封装
def add(x,y):
print('Calc:%s+%s=' %(x,y),end='')
return x+y
class MyCounter:
def __init__(self,f):
self.count = 0
self.func =f
def __call__(self,*args,**kwargs):
self.count += 1
return self.func(*args,**kwargs)
myadd =MyCounter(add)
print(myadd(1,2),'\tCount:',myadd.count) #Calc:1+2=3 Count: 1
print(myadd(2,3),'\tCount:',myadd.count) #Calc:2+3=5 Count: 2
print(myadd(3,4),'\tCount:',myadd.count) #Calc:3+4=7 Count: 3
以函数的方式封装
def add(x,y):
print('Calc:%s+%s=' %(x,y),end='')
return x+y
def my_counter(f):
def wrapper(*args,**kwargs):
wrapper.count += 1
return f(*args,**kwargs)
wrapper.count = 0
return wrapper
myadd =my_counter(add)
print(myadd(1,2),'\tCount:',myadd.count) #Calc:1+2=3 Count: 1
print(myadd(2,3),'\tCount:',myadd.count) #Calc:2+3=5 Count: 2
print(myadd(3,4),'\tCount:',myadd.count) #Calc:3+4=7 Count: 3
装饰器语法
eg:
def add(x,y):
print('Calc:%s+%s=' %(x,y),end='')
return x+y
def my_counter(f):
def wrapper(*args,**kwargs):
wrapper.count += 1
return f(*args,**kwargs)
wrapper.count = 0
return wrapper
@my_counter
def minus(x,y):
print('Calc:%s-%s=' %(x,y),end='')
return x-y
print(minus(1,2),'\tCount:',minus.count) #Calc:1-2=-1 Count: 1
print(minus(2,3),'\tCount:',minus.count) #Calc:2-3=-1 Count: 2
print(minus(3,4),'\tCount:',minus.count) #Calc:3-4=-1 Count: 3
问题分析:
首先用类来添加新功能
def fun(): #首先我们定义一个函数
print('func running')
#看到封装,我们首先想到的是函数
class MyFunc():
def __init__(self,f): #把函数和变量封装在一起
self.f=f
self.count=0
def run(self): #间接的调用了封装的函数
self.count+=1
return self.f()
mf=MyFunc(func)#实例化
mf.run()#输出func running
print(mf.count) #输出1
mf.run()#输出func running
print(mf.count)#输出2
mf.run()#输出func running
print(mf.count)#输出3
#在python中,有一个__call__函数可以让这个对象可以调用
class MyFunc():
def __init__(self,f): #把函数和变量封装在一起
self.f=f
self.count=0
def __call__(self): #让这个对象可以调用
self.count+=1
return self.f()
mf =MyFunc(func)
mf() #当你调用一个实例的时候,其实就是调用它的__call__方法,虽然mf()是一个实例,但也同时也是一个函数
func() ——>func.__call__()
mf() #func running
print(mf.count)#1
mf() #func running
print(mf.count)#2
mf() #func running
print(mf.count)#3
mf() #func running
print(mf.count)#4
def func():
print('func running')
class MyFunc():
def __init__(self,f): #把函数和变量封装在一起
self.f=f
self.count=0
def __call__(self,*arg,**kwarg): #为保证所有参数都能够传入,让这个对象可以调用
self.count+=1
return self.f(*arg,**kwarg)#调用封装进来的函数,并拿到他的返回值
func = MyFunc(func) #这便是装饰,右边的实例由于有call方法,他便是一个函数右边的实例封装了原函数和计数器;然后又将这个实例赋值个func函数,让func不指向原函数,指向一个新的函数;看上去功能和原来一样,其实已经增加了功能
#关于装饰,函数名还是原来的函数名,指向的却是新的函数,这个新的函数会调用原来的函数,也可以做些别的事情
也可以用函数来封装
#思路:装饰----一个新的函数,看上去功能和原来一样,其实已经增加了功能,增加了计数器
#首先先定义一个函数:
def my_func(f):
return f() #我在调用my_func的时候,其实已经调用了f()
#然后就是添加功能,添加计数器
def my_func(f):
def new_func():
new_func.count+=1 #计数器加1
return f() #调用并返回f的内容
new_func.count=0
return new_func
func=my_func(func)#装饰
#装饰,不过是指向了一个新的函数,只不过这个函数除了调用原来的函数以外,还会做点别的事
def my_func(func):
def new_func():
new_func.count+=1#把计数器加1
return func() #返回并调用原来的函数func
new_func.count=0
return new_func#只是返回,不调用
@my_func #在定义的同时便装饰了
def mimc(a,b):
return a-b
@MyFunc #在定义的同时便装饰了
def add(a,b):
return a+b
#总结:将新的功能和老的功能都绑定在一个新的函数里面
二、part two 描述器
描述器在监视特定属性的时候很有用,其只在新式类中起作用。所有的描述器协议如下:
descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None
如果一个对象同时定义了 __get__()
和 __set__()
,它叫做资料描述器(data descriptor)。仅定义了 __get__()
的描述器叫非资料描述器
描述器在属性访问时被自动调用。举例来说, obj.x
会在 obj
的字典中找x
,如果x
定义了 __get__
方法,那么 x.__get__(obj)
会依据下面的优先规则被调用
调用优先级:
资料描述器 -> 实例字典 -> 非资料描述器
常用的描述器就是property
了,一般都只实现了__get__
的接口
先给出一个classmethod
的实现和一个用于测试描述器优先级的类
管理一个类属性的访问,修改,删减
class MyAttribute:#将来他的实例会是一个类的属性(注意!是类的属性,不是实例的属性)
def __get__(self,instance,owner):#instance是实例,owner是类
print('get',instance,owner)
def __set__(self,instance,value):#value是赋的值
print('set',instance,value)
def __delete__(self,instance,value):
print('delete',instance)
def __str__(self):
print('1111111')
#重点来了
class MyClass:
attr=MyAttribute() #实例化
mc=MyClass()
print(mc.attr) #如果没有get set delete三种,就会输出字符串表示str的内容;如果够get,get就会被调用,就会替代字符串表示;set就是修改,delete就是删除
一个基于描述器的装饰器
#基于描述器的装饰器 Property
class Property:
def __init__(self,fget=None):
self.fget=fget
def __get__(self,obj,objtype=None):
if obj is None:return selg
if self.fget is None: raise AttributeError('无法访问这个属性')
returnself.fget(obj)
#这个东西不用理解
#他是一个装饰器,也是一个描述器
描述器应用场景示例
#例子:是否匿名
class Person:
def __init__(self,name):
self.name=name
self.is_anonymous=False #是否是匿名
@Property
def get_name(self):#方法,所以若要拿到name的,肯定需要调用,如果不想调用,在上面加个@Property这个装饰器即可
if not self.is_anonymous:
return self.name
else:
return 'anonymous'
p= Person('lili')
p.is_anonymous=False
print(p.get_name)#这样就可以直接取值,不需要print(p.get_name())
三、常用内置装饰器
property
class Person:
def __init__(self,name):
self.name=name
@property
def name(self):
print('通过property来获取name')
if hasattr(self,'name'):
return self.name
else:
raise AttributeError('没有name这个属性')
@name.setter #控制赋值
def name(self,value):
print('通过property来设置name')
self.name=value
@name.deleter
def name(self):
print('通过property来删除name')
del self.name
#property 就是把一个方法变成一个属性的样子
静态方法和类方法
静态方法装饰器: staticmethod
- 类调用与实例调用五差别, 也不需要self参数,类似于普通函数
类方法装饰器: classmethod
- self参数被换成cls参数,自动传入对应的类
class A:
@staticmethod #静态方法能够让类和实例,看他都是一个普通函数
def meth():
print('xxx')
a=A()
a.meth() #实例调用的时候——>A.meth(a),两者看起来都是函数
class A:
@classmethod #类方法:第一个参数,回传进去的都是类,不是实例
def meth(cls): #cls表示类
print('xxx')
a=A()
a.meth() #实例调用的时候——>A.meth(a)
#正常情况下我们都应该传实例,但是以后做项目的时候会用到类方法
#以后做数据库接入的时候,有一个ORM,在查询的时候,可以是由类方法,来简化你的代码
四、关于一些内容的补充
1、关于运行后进入交互环境的说明
#IDLE执行以后,会进入shell模式(交互模式),此时程序还没有执行完,等待你输入下一段代码
2、函数返回值的理解
#区分func和func()
#func是一个函数对象
#func()分为两步:1、执行这个函数里面的内容。2、用return出来的返回值,替代这个调用
def func():
print('xxx')
return 1
print(func()) #相当于print(1)
def other():#定义函数,是不会执行这个函数的
print('other')
return 10
def func():
print('xxx')
return other()
print(func())#输出 xxx other 10
#文件--第一个执行的是func()
#func--先打印出func
#func--调用other
#other--先打印出other
#other--返回值10
#func--在返回other()的值
#文件--print要输出func()的值
3、装饰后必须赋值变量
def myfunc(func):
def wrapper(*args,**kwargs):
wrapper.count+=1
return func(*args,**kwargs)
wrapper.count=0
return wrapper
def func():
print('xxx')
f1=myfunc(func)
f2=myfunc(func)
print(f1 is f2) #两次装饰,都是得到了加强版的函数,但是两次装饰的函数时两个,因此他们拥有两个计数器,所以装饰完必须要有一个变量接收它,装饰以后必须赋变量
4、装饰器
#装饰,首先是加功能;碧玺,原来的是得做,还得加东西
#原来的函数,功能一定要有;其次应该还有一个新的功能和她深度的绑定在一起
#思考:我们装饰完以后,得到的必须是一个加强版的函数(新的函数)
def func():
print('func')
return 1
#如果我们要模仿这个函数,我们得实现什么
#1、你需要打印出func 2、你也要返回出1
#假如,我要写一个装饰器,这个装饰器什么功能都不加,只是单纯模仿
def myfunc(f):
return f
f2=myfunc(func)#f2和func是一个函数
#希望,我返回的不是原来的那个函数,而是新函数,但是做的事和原来一样
def myfunc(f):
def wrapper():#这个是定义,不是调用。定义完成以后会得到一个函数对象wrapper
wrapper.count+=1#把我自己这个函数里面的count+1
return f() #就是运行f,并且把它的返回值给返回出来(因为我要返回f(),就是返回f里面的return)
wrapper.count=0 #由于是一个对象,所以可以加.count
return wrapper#我返回的是wrapper这个函数对象(不是wrapper的调用)
def myfunc(f)
def wrapper():#这个封装的新函数:1、增加计数器 2、调用原函数,并返回其返回值
wrapper.count+=1
return f()
wrapper.count=0
return wrapper
#入门版:
def myfunc(f):
count=0
def wrapper():
nonlocal count #作用域的问题,全局变量和局部变量
count+=1
return f()
return wrapper
一个property的python 实现
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError, "unreadable attribute"
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError, "can't set attribute"
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError, "can't delete attribute"
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)