笔记6:python装饰器
1 函数修饰符@的作用 首先介绍函数装饰器。通过装饰器函数,在不修改原函数的前提下,对函数的功能进行合理扩充。
用@装饰器的格式来写的目的就是为了书写简单方便
2 函数修饰符@的工作原理
假设用 funA() 函数装饰器去装饰 funB() 函数,如下所示:
#funA 作为装饰器函数
def funA(fn):
#...
fn() # 执行传入的fn参数
#...
return '...'
def funB():
#...
其等价于:
def funA(fn):
#...
fn() # 执行传入的fn参数
#...
return '...'
def funB():
#...
funB = funA(funB)
通过比对以上 2 段程序不难发现,使用函数装饰器 A() 去装饰另一个函数 B(),其底层执行了如下 2 步操作:
-
将 B 作为参数传给 A() 函数;
-
将 A() 函数执行完成的返回值反馈回 B。
示例如下:
#funA 作为装饰器函数
def funA(fn):
print("C语言中文网")
fn() # 执行传入的fn参数
print("http://c.biancheng.net")
return "装饰器函数的返回值"
def funB():
print("学习 Python")
得到的是:
C语言中文网
学习 Python
http://c.biancheng.net
在此基础上,如果在程序末尾添加如下语句:
print(funB)
其输出结果为:
装饰器函数的返回值
显然,被“@函数”修饰的函数不再是原来的函数,而是被替换成一个新的东西(取决于装饰器的返回值),即如果装饰器函数的返回值为普通变量,那么被修饰的函数名就变成了变量名;同样,如果装饰器返回的是一个函数的名称,那么被修饰的函数名依然表示一个函数。
实际上,所谓函数装饰器,就是通过装饰器函数,在不修改原函数的前提下,来对函数的功能进行合理的扩充。
带参数的函数装饰器
在分析 funA() 函数装饰器和 funB() 函数的关系时,细心的读者可能会发现一个问题,即当 funB() 函数无参数时,可以直接将 funB 作为 funA() 的参数传入。但是,如果被修饰的函数本身带有参数,那应该如何传值呢?
比较简单的解决方法就是在函数装饰器中嵌套一个函数,该函数带有的参数个数和被装饰器修饰的函数相同。例如:
def funA(fn):
# 定义一个嵌套函数
def say(arc):
print("Python教程:",arc)
return say
def funB(arc):
print("funB():", a)
funB("http://c.biancheng.net/python")
程序执行结果为:
Python教程: http://c.biancheng.net/python
其实和下面的函数相同
def funA(fn):
# 定义一个嵌套函数
def say(arc):
print("Python教程:",arc)
return say
def funB(arc):
print("funB():", a)
funB = funA(funB)
funB("http://c.biancheng.net/python")
显然,通过 funB() 函数被装饰器 funA() 修饰,funB 就被赋值为 say。这意味着,虽然我们在程序显式调用的是 funB() 函数,但其实执行的是装饰器嵌套的 say() 函数。
但还有一个问题需要解决,即如果当前程序中,有多个(≥ 2)函数被同一个装饰器函数修饰,这些函数带有的参数个数并不相等,怎么办呢?
最简单的解决方式是用 args 和 **kwargs 作为装饰器内部嵌套函数的参数,args 和 **kwargs 表示接受任意数量和类型的参数。举个例子:
def funA(fn):
# 定义一个嵌套函数
def say(*args,**kwargs):
fn(*args,**kwargs)
return say
def funB(arc):
print("C语言中文网:",arc)
def other_funB(name,arc):
print(name,arc)
funB("http://c.biancheng.net")
other_funB("Python教程:","http://c.biancheng.net/python")
返回:
C语言中文网: http://c.biancheng.net Python教程: http://c.biancheng.net/python
函数装饰器可以嵌套
上面示例中,都是使用一个装饰器的情况,但实际上,Python 也支持多个装饰器,比如:
@funA @funB @funC def fun(): #...
上面程序的执行顺序是里到外,所以它等效于下面这行代码:
fun = funA( funB ( funC (fun) ) )
带参数:
def funA(c): # 多一层包装 c=2 def bbb(fn): def aaa(n): #n=1 n = n + 3 + c print("a:",n) fn(n) # print("Python教程:",n) return aaa return bbb @funA(2) def funB(a): b = a + 100 print("funB():", b) funB(1) 输出: a: 6 funB(): 106
python内置装饰符函数:
-
@staticmethod:
使用场景:当某个方法不需要用到对象中的任何资源时,将这个方法改为一个静态方法
也就是没有用到任何外面的资源,此时加上@staticmethod,方法变为静态方法,同事不需要常常要加的self。
比如:
@staticmethod def get_name(): return 'what?'
·
-
classmethod :
使用场景:修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。
# 初始类: class Data_test(object): day=0 month=0 year=0 def __init__(self,year=0,month=0,day=0): self.day=day self.month=month self.year=year def out_date(self): print "year :" print self.year print "month :" print self.month print "day :" print self.day # 新增功能: class Str2IntParam(Data_test): @classmethod def get_date(cls, string_date): #这里第一个参数是cls, 表示调用当前的类名 year,month,day=map(int,string_date.split('-')) date1=cls(year,month,day) #返回的是一个初始化后的类 return date1 # 使用: r = Str2IntParam.get_date("2016-8-1") r.out_date() # 输出: year : 2016 month : 8 day : 1
可以扩充类的方法。易于维护。
@property
-
1.只读属性
-
2.可以做方法来修改属性,也就是变成属性了,而不是方法,最直观没有括号
-
3.@方法名.setter,可以做到修改不用参数,或者参数的预处理。
-
4.因为property是类,所以可以用其内部的 age = property(get_age, set_age,del_age)来修改,顺序不能改变顺序。 (property函数内置方法:属性=property(get,set,del))
其功能1是可定义只读属性,也就是真正意义上的私有属性(属性前双下划线的私有属性也是可以访问的)。
class Person(object): def __init__(self, name, age=18): self.name = name self.__age = 18 @property def age(self): return self.__age xm = Person('xiaoming') #定义一个人名小明 print(xm.age) #结果为18 xm.age = -4 #报错无法给年龄赋值 print(xm.age)
@property真正强大的是可以限制属性的定义。往往我们定义类,希望其中的属性必须符合实际,但因为在__init__里定义的属性可以随意的修改,导致很难实现。如我想实现Person类,规定每个人(即创建的实例)的年龄必须大于18岁,正常实现的话,则必须将属性age设为只读属性,然后通过方法来赋值,代码如下:
class Person(object): def __init__(self, name, age): self.name = name self.__age = 18 @property def age(self): return self.__age def set_age(self, age): #定义函数来给self.__age赋值 if age < 18: print('年龄必须大于18岁') return self.__age = age return self.__age xm = Person('xiaoming', 20) print(xm.age) print('----------') xm.set_age(10) print(xm.age) print('----------') xm.set_age(20) print(xm.age) 返回: 18 ---------- 年龄必须大于18岁 18 ---------- 20
@property方法
class Person(object): def __init__(self, name, age): self.name = name self.__age = 18 @property def age(self): return self.__age @age.setter def age(self, age): if age < 18: print('年龄必须大于18岁') return self.__age = age return self.__age xm = Person('xiaoming', 20) print(xm.age) print('----------') xm.age = 10 print(xm.age) print('----------') xm.age = 20 print(xm.age)
结果和上图一致。两段代码变化的内容:将set_age修改为age,并且在上方加入装饰器@age.setter。这就是@property定义可访问属性的语法,即仍旧以属性名为方法名,并在方法名上增加@属性.setter就行了。
@property是个描述符(decorator),实际上他本身是类,
class Person(object): def __init__(self, name, age): self.name = name self.__age = 18 def get_age(self): #恢复用方法名来获取以及定义 return self.__age def set_age(self, age): if age < 18: print('年龄必须大于18岁') return self.__age = age return self.__age age = property(get_age, set_age) #增加property类
上述代码的运行结果和前面一致,将@property装饰的属性方法再次修改回定义方法名,然后再类的最下方,定义:属性=property(get,set,del),这个格式是固定的,是由property源码决定的。
class C(object): def __init__(self): self._x = None def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") #如果 c 是 C 的实例化, c.x 将触发 getter,c.x = value 将触发 setter , del c.x 触发 deleter。