python 装饰器

前言

部分摘自:https://zhuanlan.zhihu.com/p/269012332

一、id()和其反函数

获取一个变量的id使用id,通过id获取某个变量使用ctypes模块中的ctypes.cast(id, ctypes.py_object).value

a=10
b=id(a)
print('a type:%s id:%s value:%s'%(type(a),id(a),a))
print('b type:%s id:%s value:%s'%(type(b),id(b),b))
import ctypes
c = ctypes.cast(b, ctypes.py_object).value
print('c type:%s id:%s value:%s'%(type(c),id(c),c))

打印:
a type:<class ‘int’> id:1672708496 value:10
b type:<class ‘int’> id:2837416433712 value:1672708496
c type:<class ‘int’> id:1672708496 value:10
发现a和c什么都一模一样

二、is和==

is比较变量id,==比较变量值

a=[12,3]
b=[12,3]
print("a %s,b %s,a==b %s"%(a,b,a==b))#(a == b)没有括号就是False
print("id(a) %s,id(b) %s,a is b %s"%(id(a),id(b),a is b))

打印:
a [12, 3],b [12, 3],a==b True
id(a) 1404902129352,id(b) 1404902071560,a is b False

二、不可变数据类型和可变数据类型

不可变数据类型: 当该数据类型的对应变量的值发生了改变,那么它对应的内存地址也会发生改变,对于这种数据类型,就称不可变数据类型。如int
可变数据类型 :当该数据类型的对应变量的值发生了改变,它对应的内存地址不发生改变,对于这种数据类型,就称可变数据类型。如list

a=123
print("a",a,id(a))
a=456
print("a",a,id(a))
a=123
b=123
print("a",a,id(a))
print("b",b,id(b))
a=1234567890
b=1234567890
print(id(a))
print(id(b))

打印:
a 123 1672712112
a 456 2404217706704
a 123 1672712112
b 123 1672712112
2404217706192
2404217706192
说明
其实是给123,456 分配了内存,a=123其实是让变量a保存指向123的内存地址
那么id(a)就是输出a中保存的内存地址,a就是a中保存内存地址指向的内存块值
按照这个逻辑推下去,首先浮点数是不能这么玩的因为太多了,事实证明浮点数确实不是不可变类型
那么系统给所有整形都分配了地址吗?显然整形也是无穷的,分配下去会耗尽内存,根据编译器的不同分配方式不同
比如:在shell中运行
a=1234567890
b=1234567890
id(a)不能与id(b)
但是写在这个文件中运行就没问题,显然这个文件是用到了这个大数后就把这个大数定在内存中了,所以这里需要小心因为你不知道编译器本身定了多少个整形在内存中

a=[1,2]
print("a",a,id(a))
a=[3,4]
print("a",a,id(a))
a=[1,2]
b=[1,2]
c=b#c对b进行了浅复制,只复制了b对应的变量地址,没有重新创建新的内存块
d=b[::]#列表切块是创建了新的内存块,内存地址也发生变化
print("a",a,id(a))
print("b",b,id(b))
print("c",c,id(c))
print("d",d,id(d))

打印:
a [1, 2] 1445000853512
a [3, 4] 1445000863432
a [1, 2] 1445000853512
b [1, 2] 1445000863432
c [1, 2] 1445000863432
d [1, 2] 1445000862216
说明
可变数据类型赋值时a=[1,2]是先创建一块内存保存[1,2]然后把这个内存给a
总结
整形、字符串、元祖是不可变类型
浮点、列表、集合、字典是可变类型

三、全局变量,局部变量和自由变量

a,b,c=([1,2],2,3)
def printa():
    a[0]+=1
    #b+=10#b 属于不可变数据类型在没有生命全局变量时可以访问不能修改
    d=b#可以访问
    d+=1
    global c
    c+=1
    print("a%s,b%s,c%s,d%d"%(a,b,c,d))
printa()

打印:
a[2, 2],b2,c4,d3

def outter():
    outa = 10
    def printouta():
        #outa+=10#可以直接访问但是不能修改
        #global outa定义为全局也是不行的
        nonlocal outa#定义为自由变量就可以了
        outa+=1
        print(outa)     
    return printouta
outter()()

说明:nonlocal同样只是针对不可变类型,可变类型不牵扯,变量重名,先局部再自由再全局
打印:
10

三、闭包

其实上面nonlocal已经用到了闭包,闭包的特别之处在于你以为应该析构或者说回收的东西其实还存在

def outter():
    outa = 10
    def printouta():
        #outa+=10#可以直接访问但是不能修改
        #global outa定义为全局也是不行的
        nonlocal outa#定义为自由变量就可以了
        outa+=1
        print(outa)     
    return printouta
x=outter()
x()

因为printouta属于outter(),所以在outter()调用完成之后,其outa 和printouta所占的内存都应被回收掉,但是事实是没有回收,这就是闭包,看似模块变量生存期长于模块
那么它的生命期有多长,没有变量引用就结束了

def outter():
    #n=10
    m=[1]
    def closurefunc():
        #n+=1
        m[0]+=1
        print('closure','m',m)
    return closurefunc

x=outter()
x()
b=id(x)
#x=1000#这行注释去掉的话,下面执行就会出现问题,因为x被赋值,虽然保存了之前存放函数的地址,但是没有变量引用,系统回收内存下面再用id找函数内存的话就不行了
import ctypes
get_valuex = ctypes.cast(b, ctypes.py_object).value  # 读取地址中的变量
get_valuex()

四、装饰器

在不修改函数内容的前提下对函数执行前后进行修改,妄自下个结论,任何一个装饰器都可以被一个新的函数取代
1、最简单的装饰器,函数没有参数,装饰器没有参数,在函数执行前后做一些事情

def deco(func):
    print('deco begin')
    def wrapper():
        print('wrapper begin')
        func()
        print('wrapper end')
    print('deco end')
    return wrapper
    
@deco
def pri():
    print('hello')
pri()

打印:主要看顺序
deco begin
deco end
wrapper begin
hello
wrapper end
2、函数有参数有返回值,有不同个数的参数,装饰器没有参数,在函数执行前后做一些事情

print('2、函数有参数且有返回值,装饰器没有参数,在函数执行前后做一些事情')
def deco1(func):
    print('deco1 begin')
    def wrapper1(a,b):#参数从这里传入
        print('wrapper1 begin')
        return func(a,b)
        print('wrapper1 end')
    print('deco1 end')
    return wrapper1

@deco1
def mysum(a,b):
    print('hello')
    return a+b
print(mysum(1,2))
'''
#打印:
deco1 begin
deco1 end
wrapper1 begin
hello
3
'''
print('2、函数有数量不同的参数且有返回值,装饰器没有参数,在函数执行前后做一些事情')
def deco2(func):
    print('deco2 begin')
    def wrapper2(*var,**keyv):#传参就两种形式一个是1,2,4一个是a=1,b=2,所以这里var是一个元祖,keyv是一个字典,加上*和**是解开的意思
        print('wrapper2 begin')
        return func(*var)
        print('wrapper2 end')#直接返回没有这个打印了
    print('deco2 end')
    return wrapper2

@deco2
def mysum(a,b):
    print('hello')
    return a+b

@deco2
def mysum1(a,b,c):
    print('hello')
    return a+b+c
print(mysum(1,2))
print(mysum1(1,2,3))

打印:
deco2 begin
deco2 end
deco2 begin
deco2 end
wrapper2 begin
hello
3
wrapper2 begin
hello
6

3、装饰器有参数
装饰器有参数,我要在打印的时候告诉打印的是三个相加还是两个相加。
装饰器有参数需要再叠一层函数,形成三层函数

print('3、装饰器有参数')
def deco3(parm):#这里传装饰器参数
    print(parm)
    print('deco3 begin')
    def out3(func):#这里传函数
        print('out3 begin')
        def innerfun(*var,**keyv):#这里传函数参数
            print('innerfun begin')
            return func(*var)
            print('innerfun end')
        print('out3 end')
        return innerfun
    print('deco3 end')
    return out3

@deco3('两数相加')
def mysum(a,b):
    print('hello')
    return a+b
print(mysum(1,2))

打印:
两数相加
deco3 begin
deco3 end
out3 begin
out3 end
innerfun begin
hello
3

4、多层装饰器

print('——————以下是多层装饰器迭代——————')

def mydecorate10(func):
    print("mydecorate begin10")
    def mywrapper(*data,**index):#参数先传在这里
        print("mywrapper begin10")
        return func(*data)#再传在这里
        print("mywrapper end10")
    print("mydecorate end10")
    return mywrapper

def mydecorate11(func):
    print("mydecorate begin11")
    def mywrapper(*data,**index):#参数先传在这里
        print("mywrapper begin11")
        return func(*data)#再传在这里
        print("mywrapper end11")
    print("mydecorate end11")
    return mywrapper

@mydecorate11
@mydecorate10
def mysum2(data,index):
    mysum=data+index
    print('mysum2',mysum)
    return mysum

print(mysum2(10,2)) 

打印:注意顺序
mydecorate begin10
mydecorate end10
mydecorate begin11
mydecorate end11
mywrapper begin11
mywrapper begin10
mysum2 12
12
5、类装饰器,前面都是基于函数的
基于类装饰器的实现,必须实现 call 和 __init__两个内置函数。 init :接收被装饰函数 call :实现装饰逻辑。

print("——————基于类的装饰器————")
class logger(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("the function {func}() is running..."\
            .format(func=self.func.__name__))
        return self.func(*args, **kwargs)
@logger
def say(something):
    print("say {}!".format(something))
say("hello")

打印:
the function say() is running…
say hello!
6、带参数的类装饰器
init :不再接收被装饰函数,而是接收传入参数。 call :接收被装饰函数,实现装饰逻辑。


print("——————带参数的类装饰器————")
class logger(object):
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func): # 接受函数
        def wrapper(*args, **kwargs):
            print("[{level}]: the function {func}() is running..."\
                .format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
        return wrapper  #返回函数

@logger(level='WARNING')
def say(something):
    print("say {}!".format(something))
say("hello")

说明:可以看出来这个和函数嵌套很想,__init__是第一层,__call__是第二层,里面可以嵌套是第三层,根据有无参数确定需要几层
打印:
[WARNING]: the function say() is running…
say hello!

7_1、插入偏函数
偏函数

print('————以下是偏函数————')
def mysum(a,b,c,d):
    return a+b+c+d
import functools
mysum_2_1=functools.partial(mysum,2,1)#如果前两个参数始终是2,1那就构造一个偏函数以后省略这两个
print(mysum_2_1(3,4))
mysum____3_4=functools.partial(mysum,c=3,d=4)
print(mysum____3_4(1,2))
mysum_5_6=functools.partial(mysum,a=5,b=6)
#print(mysum_5_6(1,2)),报错因为a有值是5这里默认还要把1给a
print(mysum_5_6(c=1,d=2))

打印:
10
10
14
偏函数的强大之处在于其内部的函数还可以是类,比如类的初始化需要三个参数,两个是固定的所以就创造出来一个新的类,但是还是之前的类只不过创建起来比较方便而已
7_2 使用偏函数与类实现装饰器

print('————偏函数类装饰器————')
import time
import functools

class DelayFunc:
    def __init__(self,  duration, func):
        self.duration = duration
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f'Wait for {self.duration} seconds...')
        time.sleep(self.duration)
        return self.func(*args, **kwargs)

def delay(duration):
    """
    装饰器:推迟某个函数的执行。
    同时提供 .eager_call 方法立即执行
    """
    # 此处为了避免定义额外函数,
    # 直接使用 functools.partial 帮助构造 DelayFunc 实例
    return functools.partial(DelayFunc, duration)

@delay(duration=2)
def add(a, b):
    return a+b

print(type(add))#这里的add是一个类实例,是类实例的()是调用__call__(),不是类名,如果是类名的话是<class 'type'>
print(type(add.func))
print(add(2,3))

打印:
————偏函数类装饰器————
<class ‘main.DelayFunc’>
<class ‘function’>
Wait for 2 seconds…
5
8、装饰累的装饰器

instances = {}

def singleton(cls):
    def get_instance(*args, **kw):
        cls_name = cls.__name__
        print('===== 1 ====')
        if not cls_name in instances:#这里确保对一个类只创建一个实例
            print('===== 2 ====')
            instance = cls(*args, **kw)
            instances[cls_name] = instance
        return instances[cls_name]
    return get_instance

@singleton
class User:
    _instance = None

    def __init__(self, name):
        print('===== 3 ====')
        self.name = name
User('ok')#这里把类User传给了装饰器,而非实例

打印:
————————装饰类的装饰器————
===== 1 ====
===== 2 ====
===== 3 ====

说明:万物皆对象,其实前面加上@装饰器,就是把下面对象传给装饰器,且在对象()中展示出装饰器的内容。
所有代码

print("——————id和反函数————")
a=10
b=id(a)
print('a type:%s id:%s value:%s'%(type(a),id(a),a))
print('b type:%s id:%s value:%s'%(type(b),id(b),b))
import ctypes
c = ctypes.cast(b, ctypes.py_object).value
print('c type:%s id:%s value:%s'%(type(c),id(c),c))

print("——————is和==————")
a=[12,3]
b=[12,3]
print("a %s,b %s,a==b %s"%(a,b,a==b))#(a == b)没有括号就是False
print("id(a) %s,id(b) %s,a is b %s"%(id(a),id(b),a is b))
print("——————不可变数据类型和可变数据类型————")
#不可变数据类型: 当该数据类型的对应变量的值发生了改变,那么它对应的内存地址也会发生改变,对于这种数据类型,就称不可变数据类型。
a=123
print("a",a,id(a))
a=456
print("a",a,id(a))
a=123
b=123
print("a",a,id(a))
print("b",b,id(b))
a=1234567890
b=1234567890
print(id(a))
print(id(b))
"""打印输出
a 123 1348833200
a 456 1893893396816
a 123 1348833200
b 123 1348833200
2487891318992
2487891318992
"""
'''说明
其实是给123,456 分配了内存,a=123其实是让变量a保存指向123的内存地址
那么id(a)就是输出a中保存的内存地址,a就是a中保存内存地址指向的内存块值
按照这个逻辑推下去,首先浮点数是不能这么玩的因为太多了,事实证明浮点数确实不是不可变类型
那么系统给所有整形都分配了地址吗?显然整形也是无穷的,分配下去会耗尽内存,根据编译器的不同分配方式不同
比如:在shell中运行
a=1234567890
b=1234567890
id(a)不能与id(b)
但是写在这个文件中运行就没问题,显然这个文件是用到了这个大数后就把这个大数定在内存中了,所以这里需要小心因为你不知道编译器本身定了多少个整形在内存中


'''
#可变数据类型 :当该数据类型的对应变量的值发生了改变,那么它对应的内存地址不发生改变,对于这种数据类型,就称可变数据类型。




a=[1,2]
print("a",a,id(a))
a=[3,4]
print("a",a,id(a))
a=[1,2]
b=[1,2]
c=b#c对b进行了浅复制,只复制了b对应的变量地址,没有重新创建新的内存块
d=b[::]#列表切块是创建了新的内存块,内存地址也发生变化
print("a",a,id(a))
print("b",b,id(b))
print("c",c,id(c))
print("d",d,id(d))
"""打印输出
a [1, 2] 1893893816008
a [3, 4] 1893893910600#这里和下面的bc相同了,因为再次对a赋值时该内存没有变量关联了,收回再利用
a [1, 2] 1893893816008
b [1, 2] 1893893910600
c [1, 2] 1893893910600
d [1, 2] 1893893910792
"""
'''说明
可变数据类型赋值时a=[1,2]是先创建一块内存保存[1,2]然后把这个内存给a

'''
'''总结
整形、字符串、元祖是不可变类型
浮点、列表、集合、字典是可变类型

'''
'''插叙根据id获取value

'''
a=123456
b=id(a)
print('a',a,type(a),id(a))
import ctypes
get_value = ctypes.cast(b, ctypes.py_object).value  # 读取地址中的变量
print('get_value',get_value,type(get_value),id(get_value))
'''打印输出
a 123456 <class 'int'> 2393712090512
get_value 123456 <class 'int'> 2393712090512
'''
print("——————以下是全局变量局部变量和自由变量————")

a,b,c=([1,2],2,3)
def printa():
    a[0]+=1
    #b+=10#b 属于不可变数据类型在没有生命全局变量时可以访问不能修改
    d=b#可以访问
    d+=1
    global c
    c+=1
    print("a%s,b%s,c%s,d%d"%(a,b,c,d))
printa()

def outter():
    outa = 10
    
    def printouta():
        #outa+=10#可以直接访问但是不能修改
        #global outa定义为全局也是不行的
        nonlocal outa#定义为自由变量就可以了
        outa+=1
        print(outa)     
    return printouta
outter()()

#检验变量顺序
a=[3,4]
def outter1():
    a = [1,2]  
    def printouta():
        print(a)     
    return printouta
outter1()()


print('——————以下是闭包——————————')

def outter():
    #n=10
    m=[1]
    def closurefunc():
        #n+=1
        m[0]+=1
        print('closure','m',m)
    return closurefunc

x=outter()#很好理解,调用outter()后返回closurefunc()执行的过程中打印了m
x()
b=id(x)
#x=1000
import ctypes
get_valuex = ctypes.cast(b, ctypes.py_object).value  # 读取地址中的变量
get_valuex()
print('ok')
def outter1():
    #n=10
    m=[1]
    def closurefunc():
        #nonlocal n
        #n+=1
        m[0]+=1
        print('closure','m',m)
    return closurefunc#去掉了括号
a=outter1()#也可以理解,outter()返回函数closurefunc的地址
a()#调用函数closurefunc()完成
#问题是,在a=outter1()中outter1()函数已经调用完成,对应的内存没有回收,这就是闭包的特点,并且属于outter1()函数的变量m也没有回收,直到del a就会回收所有
#去掉变量n的注解之后报错,因为n是不可变数据类型,不可变数据类型在闭包函数中使用时,需要指明这个变量是nonlocal,而可变数据类型m就不用

print('——————以下是装饰器——————')
print('1、最简单的装饰器,函数没有参数,装饰器没有参数,在函数执行前后做一些事情')
def deco(func):
    print('deco begin')
    def wrapper():
        print('wrapper begin')
        func()
        print('wrapper end')
    print('deco end')
    return wrapper

@deco
def pri():
    print('hello')

pri()

print('2、函数有参数且有返回值,装饰器没有参数,在函数执行前后做一些事情')
def deco1(func):
    print('deco1 begin')
    def wrapper1(a,b):#参数从这里传入
        print('wrapper1 begin')
        return func(a,b)
        print('wrapper1 end')
    print('deco1 end')
    return wrapper1

@deco1
def mysum(a,b):
    print('hello')
    return a+b
print(mysum(1,2))

print('2、函数有数量不同的参数且有返回值,装饰器没有参数,在函数执行前后做一些事情')
def deco2(func):
    print('deco2 begin')
    def wrapper2(*var,**keyv):#传参就两种形式一个是1,2,4一个是a=1,b=2,所以这里var是一个元祖,keyv是一个字典,加上*和**是解开的意思
        print('wrapper2 begin')
        return func(*var)
        print('wrapper2 end')
    print('deco2 end')
    return wrapper2

@deco2
def mysum(a,b):
    print('hello')
    return a+b

@deco2
def mysum1(a,b,c):
    print('hello')
    return a+b+c
print(mysum(1,2))
print(mysum1(1,2,3))


print('3、装饰器有参数')
def deco3(parm):#这里传装饰器参数
    print(parm)
    print('deco3 begin')
    def out3(func):#这里传函数
        print('out3 begin')
        def innerfun(*var,**keyv):#这里传函数参数
            print('innerfun begin')
            return func(*var)
            print('innerfun end')
        print('out3 end')
        return innerfun
    print('deco3 end')
    return out3

@deco3('两数相加')
def mysum(a,b):
    print('hello')
    return a+b
print(mysum(1,2))


print('——————以下是多层装饰器迭代——————')

def mydecorate10(func):
    print("mydecorate begin10")
    def mywrapper(*data,**index):#参数先传在这里
        print("mywrapper begin10")
        return func(*data)#再传在这里
        print("mywrapper end10")
    print("mydecorate end10")
    return mywrapper

def mydecorate11(func):
    print("mydecorate begin11")
    def mywrapper(*data,**index):#参数先传在这里
        print("mywrapper begin11")
        return func(*data)#再传在这里
        print("mywrapper end11")
    print("mydecorate end11")
    return mywrapper

@mydecorate11
@mydecorate10
def mysum2(data,index):
    mysum=data+index
    print('mysum2',mysum)
    return mysum

print(mysum2(10,2)) 

print("——————基于类的装饰器————")
class logger(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("the function {func}() is running..."\
            .format(func=self.func.__name__))
        return self.func(*args, **kwargs)

@logger
def say(something):
    print("say {}!".format(something))

say("hello")

print("——————带参数的类装饰器————")
class logger(object):
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func): # 接受函数
        def wrapper(*args, **kwargs):
            print("[{level}]: the function {func}() is running..."\
                .format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
        return wrapper  #返回函数

@logger(level='WARNING')
def say(something):
    print("say {}!".format(something))

say("hello")



print('————以下是偏函数————')

def mysum(a,b,c,d):
    return a+b+c+d

import functools
mysum_2_1=functools.partial(mysum,2,1)#如果前两个参数始终是2,1那就构造一个偏函数以后省略这两个
print(mysum_2_1(3,4))
mysum____3_4=functools.partial(mysum,c=3,d=4)
print(mysum____3_4(1,2))

mysum_5_6=functools.partial(mysum,a=5,b=6)
#print(mysum_5_6(1,2)),报错因为a有值是5这里默认还要把1给a
print(mysum_5_6(c=1,d=2))


print('————偏函数类装饰器————')
import time
import functools

class DelayFunc:
    def __init__(self,  duration, func):
        self.duration = duration
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f'Wait for {self.duration} seconds...')
        time.sleep(self.duration)
        return self.func(*args, **kwargs)

    def eager_call(self, *args, **kwargs):
        print('Call without delay')
        return self.func(*args, **kwargs)

def delay(duration):
    """
    装饰器:推迟某个函数的执行。
    同时提供 .eager_call 方法立即执行
    """
    # 此处为了避免定义额外函数,
    # 直接使用 functools.partial 帮助构造 DelayFunc 实例
    return functools.partial(DelayFunc, duration)

@delay(duration=2)
def add(a, b):
    return a+b

print(type(add))
print(type(DelayFunc))
print(type(add.func))
print(add(2,3))


print('————————装饰类的装饰器————')

instances = {}

def singleton(cls):
    def get_instance(*args, **kw):
        cls_name = cls.__name__
        print(cls_name)
        print('===== 1 ====')
        if not cls_name in instances:
            print('===== 2 ====')
            instance = cls(*args, **kw)
            instances[cls_name] = instance
        return instances[cls_name]
    return get_instance

@singleton
class User:
    _instance = None

    def __init__(self, name):
        print('===== 3 ====')
        self.name = name


User('ok')


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值