装饰器


1.闭包函数

闭包函数一般用于==解决某一个函数只能传入一个参数==的情况。

def funOut(num1):
    def funIn(num2):
        #内部函数修改外部函数的变量,需要声明 nonlocal(变量非内部的,也就是外部的意思)
        nonlocal num1
        num1 += 100
        print(f'此时num1的值为:{num1},需要相加的num2的值为:{num2}')
        return num2+num1
    return funIn

f = funOut(100)
#调用以后外部变量num1,相当于静态变量,会一直保留他的值
print("两个数的和为:",f(200))#第一次调用的时候,外部变量num1为200,200+200=400
print("两个数的和为:",f(300))#第二次调用的时候,外部变量num1为300,300+300=600

#执行结果:
'''
此时num1的值为:200,需要相加的num2的值为:200
两个数的和为: 400
此时num1的值为:300,需要相加的num2的值为:300
两个数的和为: 600
'''

2.基本的装饰器

'''这个结构是装饰器的基本结构,基本函数具有参数的传入,具有返回值的输出'''

#装饰器该函数
def decor(func):
    #定义一个内部函数
    def inner(*args,**kwargs):
        #增加功能1
        print('6')
        #基本函数
        ret = func(*args,**kwargs)
        #增加功能2
        print('举高高')
        return ret
    #返回内部函数
    return inner

#基本函数
@decor # 这里的@decor完全等同于 love=decor(love)
def love(*args,**kwargs):
    print(f"{args}亲亲{kwargs}")
    return '基本函数返回值'

print(love('爸爸','妈妈',son='儿子',girl='女儿'))

#执行结果:
'''
6
('爸爸', '妈妈')亲亲{'son': '儿子', 'girl': '女儿'}
举高高
基本函数返回值
'''

3.带有参数的装饰器

==实际上就是在装饰器外面又加了一个闭包函数==,因为装饰器的使用格式:“@装饰函数”是不能传入参数的。

'''实际上装饰器还是不带参数的,带参数的只是在装饰器的基本结构外加了一层。

实际就是根据传入的参数,返回一个装饰器'''

#调用outer返回装饰器
def outer(arg):
    #装饰器该函数
    def decor(func):
        #定义一个内部函数
        def inner(*args,**kwargs):
            if arg == '抱举1次':
                #增加功能1
                print('抱抱')
                #基本函数
                ret = func(*args,**kwargs)
                #增加功能2
                print('举高高')
                return ret
            elif arg == '抱举3次':
                #增加功能1
                for i in range(3):
                    print('抱抱')
                #基本函数
                ret = func(*args,**kwargs)
                #增加功能2
                for i in range(3):
                    print('举高高')
                return ret
        #返回内部函数
        return inner
    #返回装饰器
    return decor

#基本函数
@outer('抱举1次') # 这里的@outer('抱举1次') 就等于一个decor
def love(*args,**kwargs):
    print(f"{args}亲亲{kwargs}")
    return '基本函数返回值'

#基本函数
@outer('抱举3次') # 这里的@outer('抱举1次') 就等于一个decor
def love2(*args,**kwargs):
    print(f"{args}亲亲{kwargs}")
    return '基本函数返回值'


'''这样实现的效果就是,同一个装饰器,

因为传入参数不用,可以实现装饰不同的函数的效果'''

print(love('爸爸','妈妈',son='儿子',girl='女儿'))

print(love2('爸爸','妈妈',son='儿子',girl='女儿'))

#执行结果:
'''
抱抱
('爸爸', '妈妈')亲亲{'son': '儿子', 'girl': '女儿'}
举高高
基本函数返回值
抱抱
抱抱
抱抱
('爸爸', '妈妈')亲亲{'son': '儿子', 'girl': '女儿'}
举高高
举高高
举高高
基本函数返回值
'''

4.使用类做装饰器

'''
#调用outer返回装饰器
def outer(arg):
    #装饰器该函数
    def decor(func):
        #定义一个内部函数
        def inner(*args,**kwargs):
            if arg == '':
                #增加功能1
                #基本函数
                ret = func(*args,**kwargs)
                #增加功能2
                return ret
            elif arg == '':
                #增加功能1
                #基本函数
                ret = func(*args,**kwargs)
                #增加功能2
                return ret
        #返回内部函数
        return inner
    #返回装饰器
    return decor

首先看下这个结构,用类实现需要实现3个主要部分
1.可以带有区分或者使用的参数arg 这个用类的__init__实现
2.具有接受基本函数传入的参数func 这个用类的__call__实现
3.定义未来使用的函数,也就是内部函数inner
'''

class Outer:
    def __init__(self,arg):
        self.arg = arg

    #一定要具有__call__魔术方法使得类的实例化对象,可以当作函数使用
    def __call__(self, func):#这是装饰器的本体 相当于decor函数
        self.func = func
        #返回内部函数
        return self.inner

    #定义一个内部函数
    def inner(self,*args,**kwargs):
        if self.arg == '抱举1次':
            # 增加功能1
            print('抱抱')
            # 基本函数
            ret = self.func(*args, **kwargs)
            # 增加功能2
            print('举高高')
            return ret
        elif self.arg == '抱举3次':
            # 增加功能1
            for i in range(3):
                print('抱抱')
            # 基本函数
            ret = self.func(*args, **kwargs)
            # 增加功能2
            for i in range(3):
                print('举高高')
            return ret

#基本函数
@Outer('抱举1次') # @Outer('抱举1次') 就等于一个实例对象,但是装饰器需要一个函数对象,所以用__call__,还是等于一个decor
def love(*args,**kwargs):
    print(f"{args}亲亲{kwargs}")
    return '基本函数返回值'

#基本函数
@Outer('抱举3次') # @Outer('抱举1次') 就等于一个实例对象,但是装饰器需要一个函数对象,所以用__call__,还是等于一个decor
def love2(*args,**kwargs):
    print(f"{args}亲亲{kwargs}")
    return '基本函数返回值'

'''这样实现的效果就是,同一个装饰器,

因为传入参数不用,可以实现装饰不同的函数的效果'''

print(love('爸爸','妈妈',son='儿子',girl='女儿'))
print('====================分割线====================')
print(love2('爸爸','妈妈',son='儿子',girl='女儿'))

#执行结果:
'''
抱抱
('爸爸', '妈妈')亲亲{'son': '儿子', 'girl': '女儿'}
举高高
基本函数返回值
====================分割线====================
抱抱
抱抱
抱抱
('爸爸', '妈妈')亲亲{'son': '儿子', 'girl': '女儿'}
举高高
举高高
举高高
基本函数返回值
'''

5.多个装饰器的执行顺序

'''这里是演示多个装饰器的使用'''
#闭包1
def funOut(func):
    print("闭包1返回")
    def funin(*args,**kwargs):#如果有参数就写这个可变参数
        print("闭包1执行")
        func(*args,**kwargs)
    return funin
#闭包2
def funOut2(func):
    print("闭包2返回")
    def funin2(*args,**kwargs):#如果有参数就写这个可变参数
        print("闭包2执行")
        func(*args,**kwargs)
    return funin2


'''这里的装饰器起的作用是从下到上的。相当与依次执行了 foo = funOut(foo) 与 foo = funOut2(foo)
所以输出的顺序为:闭包1返回,闭包2返回
'''
@funOut2#等同于:foo = funOut2(foo)
@funOut#等同于:foo = funOut(foo)
def foo():
    print("foo函数正在运行")

'''这里调用的时候,闭包函数执行又相反
所以输出顺序为:闭包2执行,闭包1执行

注意:foo函数只执行一次
'''
foo()

#执行结果:
'''
闭包1返回
闭包2返回
闭包2执行
闭包1执行
foo函数正在运行
'''

6.内置装饰器

property

描述符
  • 概念

如果一个类中包含了三个魔术方法(==__get__,__set__,__delete__==)之一或者全部的类就是描述符

  • 作用

描述符的作用就是对类/对象中某个成员进行详细的管理操作

  • 数据描述符

同时具备三个魔术方法的类就是数据描述符

  • 非数据描述符

没有同时具备三个魔术方法的类就是非数据描述符

==下面的这个例子可以用于类的属性==,而property装饰器用于类的私有属性

class Descriptor():

    def __init__(self,name):
        self.temp = name

    def __get__(self, instance, owner):
        '''instance 就是含有描述符的类的实例 就是下面的 h 对象
            owner 就是含有描述符的类 就是下面的 Human 类
        '''

        # 修改的时候以做一些限制,比如中间的文字用*号代替
        result = self.temp[0]+'*'+self.temp[-1]
        return result

    def __set__(self, instance, value):
        # 设置的可以做一些限制,比如修改名字只能在三个字以内
        if len(value) < 4:
            self.temp = value

    def __delete__(self, instance):
        # 删除的时候可以做一些判断
        if instance.is_allow_del:
            del self.temp

class Human:
    name = Descriptor('初始名字')
    is_allow_del = False#用这个来判断描述符是否可以删除

h = Human()
print(h.name) # 会调用 __get__

h.name = '范大哥' # 会调用 __set__
print(h.name) # 会调用 __get__

del h.name # 会调用 __delete__
print(h.name)

#执行结果:
'''
初*字
范*哥
范*哥
'''
property方法的描述符

1.必须赋值给私有属性,因为下面使用property返回的名字和这个名字同名,不是私有属性,直接可以调用,就没有意义了,理解不了可以看下面装饰器的例子

2.使用property(get,set,delete)返回给该属性值

class Human():

    def __init__(self,name):
        #必须赋值给私有属性
        self.__name = name
        self.is_allow_del = False  # 用这个来判断描述符是否可以删除

    def my_get(self):
        # 修改的时候以做一些限制,比如中间的文字用*号代替
        result = self.__name[0]+'*'+self.__name[-1]
        return result

    def my_set(self, value):
        # 设置的可以做一些限制,比如修改名字只能在三个字以内
        if len(value) < 4:
            self.__name = value

    def my_delete(self):
        # 删除的时候可以做一些判断
        if self.is_allow_del:
            del self.__name

    #使用property返回给该属性值
    name = property(my_get,my_set,my_delete)


h = Human('张三')
print(h.name) # 会调用 __get__

h.name = '李四' # 会调用 __set__
print(h.name) # 会调用 __get__

del h.name # 会调用 __delete__
print(h.name)

#执行结果:
'''
张*三
李*四
李*四
'''
property装饰器的描述符

说明:

  • property装饰器装饰一个方法以后,就使得==像属性一样调用==这个方法,不需要加括号(实例对象.方法)
  • 实际的使用,我们一般用于对==私有属性==的获取,设置,删除操作,因为私有属性是==外部实例对象==无法获取的。
  • 所以我们把获取,设置,删除的==方法名==设置的与==私有属性名==一致,那么在外部实例对象调用的时候,就像是对私有属性的直接调用(获取,设置,删除)。在调用(获取,设置,删除)之前,还可以加入我们的判断
class Person(object):

    def __init__(self,age):
        # 必须赋值给私有属性
        self.__age = age
        self.is_allow_del = False  # 用这个来判断描述符是否可以删除

    @property
    def age(self):
        print('get_age_fun触发了')
        return self.__age

    @age.setter
    def age(self, value):
        print('set_age_fun触发了')
        if not isinstance(value, int):
            raise ValueError('年龄必须是数字!')
        if value < 0 or value > 100:
            raise ValueError('年龄必须是0-100')
        self.__age = value

    @age.deleter
    def age(self):
        print('del_age_fun触发了')
        if self.is_allow_del:
            del self.__age


p = Person(20)
print(p.age)

p.age = 17
print(p.age)

del p.age
print(p.age)

#执行结果:
'''
get_age_fun触发了
20
set_age_fun触发了
get_age_fun触发了
17
del_age_fun触发了
get_age_fun触发了
17
'''

classmethod

描述
  • classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等
作用
  1. 可以不需要实例化,直接类名.方法名()来调用。这有利于组织代码,把某些应该属于某个类的函数给放到那个类里去,同时有利于命名空间的整洁

```python class A():

   @classmethod
   def a(cls):
       print(f'这个是类方法:{cls}') # 传入的就是类对象本身

print(f'这个是对象A:{A}') A.a() # 可以直接通过类调用,如果是self方法会报错(必须传入实例对象)

A().a() # 当然也允许实例化的对象去调用

#执行结果: ''' 这个是对象A: 这个是类方法: 这个是类方法: ''' ```

  1. @classmethod的作用实际是可以在class内实例化class,一般使用在有==工厂模式==要求时。作用就是比如输入的数据需要清洗一遍再实例化,可以把清洗函数定义在class内部并加上@classmethod装饰器已达到减少代码的目的。总结起来就是:==@class method可以用来为一个类创建一些预处理的实例==。

```python 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

   @classmethod
   def get_date(cls, data_as_string):
       # 这里第一个参数是cls 表示调用当前的类名

       year, month, day = map(int, data_as_string.split('-'))
       date1 = cls(year, month, day)  # 返回的是一个初始化后的类
       return date1

   def out_date(self):
       print("year :", self.year)
       print("month :", self.month)
       print("day :", self.day)

r = Data_test.get_date("2020-1-1") r.out_date()

#执行结果: ''' year : 2020 month : 1 day : 1 ''' ```

staticmethod

描述
  • staticmethod叫做静态方法,在类里面加上@staticmethod装饰器的方法不需要传入self,同时该方法不能使用类变量和实例变量。在类内部可以调用加上装饰器@staticmethod的方法,同时也不需要实例化类调用该方法
实例
class A:

    @staticmethod
    def static_fun():
        '''静态方法一般封装一些和类无关的函数,比如一些算法等等'''
        print('static_fun')

A.static_fun() # 一般可以通过类调用

A().static_fun() # 也可以通过类实例对象调用