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。