python 装饰器@:
1 函数修饰符@的作用首先介绍函数装饰器。通过装饰器函数,在不修改原函数的前提下,对函数的功能进行合理扩充。例如:有一个函数func(a, b),它的功能是求a,b的差值,我现在有一个需求,就是想对函数功能再装饰下,求完差值后再取绝对值,但是不能在func函数内部实现,这时候就需要装饰器函数了,比如func = decorate(func)函数,将func函数作为参数传递给decorate函数,由decorate来丰富func函数,丰富完成后再返回给func,此时func的功能就丰富
用@装饰器的格式来写的目的就是为了书写简单方便
2 函数修饰符@的工作原理
假设用 funA() 函数装饰器去装饰 funB() 函数,如下所示:
#funA 作为装饰器函数
def funA(fn):
#...
fn() # 执行传入的fn参数
#...
return '...'
@funA
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 "装饰器函数的返回值"
@funA
def funB():
print("学习 Python")
得到的是:
C语言中文网
学习 Python
http://c.biancheng.net
C语言中文网
在此基础上,如果在程序末尾添加如下语句:
print(funB)
其输出结果为:
装饰器函数的返回值
显然,被“@函数”修饰的函数不再是原来的函数,而是被替换成一个新的东西(取决于装饰器的返回值),即如果装饰器函数的返回值为普通变量,那么被修饰的函数名就变成了变量名;同样,如果装饰器返回的是一个函数的名称,那么被修饰的函数名依然表示一个函数。
实际上,所谓函数装饰器,就是通过装饰器函数,在不修改原函数的前提下,来对函数的功能进行合理的扩充。
带参数的函数装饰器
在分析 funA() 函数装饰器和 funB() 函数的关系时,细心的读者可能会发现一个问题,即当 funB() 函数无参数时,可以直接将 funB 作为 funA() 的参数传入。但是,如果被修饰的函数本身带有参数,那应该如何传值呢?
比较简单的解决方法就是在函数装饰器中嵌套一个函数,该函数带有的参数个数和被装饰器修饰的函数相同。例如:
def funA(fn):
# 定义一个嵌套函数
def say(arc):
print("Python教程:",arc)
return say
@funA
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
@funA
def funB(arc):
print("C语言中文网:",arc)
@funA
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.netPython教程: 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。