云上的天涯

导航

python 的类装饰器

我们都知道python的函数有装饰器,那么类也有装饰器吗?有的,为什么没有呢,来看下代码吧

def out(args):
    def inner(cls):
        cls._args = args
        return cls
    return inner

class Student:
    pass

print(Student.__dict__)
Student.name = "ALICE"
print(Student.__dict__)
###来看下执行结果###
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None, 'name': 'ALICE'}

我们定义了一个空类Student,里面没有任何属性,第一次打印类的dict属性时候大家看到是没有任何属性的,只有类的特殊属性

然后我们增加了一个name属性,然后再打印一次dict属性,就看到有一个常规的name属性,属性值是ALICE

然后看最上面的函数,这个函数是个装饰函数,out函数接收常规str参数,当然不限制类型,你也可以传入int参数等等。

Inner函数的参数值是cls,也就是一个类,我们把类当做一个参数传进去,既然函数装饰器都可以把函数当做 参数传进去,类也可以当做参数传进去,在python里万物皆对象,只要是对象就是可以被传入的

cls._args = args 这里就是给这个类增加一个新属性,新属性是_args 并且值是形参args的实参值

然后最重要的来了,必须要有  return cls 不然的话下面的类的调用就要出问题了,一会我们测试下,因为给类增加新的属性后,一定要返回类,具体为什么,我们一会测试下就明白了

最后我们使用装饰器的@方式来装饰类,我们来看下代码与执行结果

def out(args):
    def inner(cls):
        cls._args = args
        return cls
    return inner
@out("TOM") ##=>>这里必须要带上args的实参
class Student:
    pass

print(Student.__dict__)
Student.name = "ALICE"
print(Student.__dict__)
####执行结果如下#####
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None, '_args': 'TOM'}
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None, '_args': 'TOM', 'name': 'ALICE'}

我们可以看到,新增了_args属性,并且属性值是TOM。

我们来看下是否是在python内部新建了一个类?

def out(args):
    def inner(cls):
        cls._args = args
        print("The new class id is {}".format(id(cls)))
        return cls
    return inner
@out("TOM")
class Student:
    pass


print("The old class id is {}".format(id(Student)))
foo = out("TOM")
foo(Student)

###我们返回每个类的ID#####
The new class id is  32509208
The old class id is   32509208
The new class id is  32509208

ID值完全相同,看来在内部并没有创建一个新类,只是装饰器给其增加了一个属性

我们来测试下,在装饰器函数内部如果不返回类也就是cls呢?

def out(args):
    def inner(cls):
        cls._args = args
        print("The new class id is {}".format(id(cls)))
        #return cls
    return inner
@out("TOM")
class Student:
    pass


#print("The old class id is {}".format(id(Student)))
#foo = out("TOM")
#foo(Student)
print(Student.__dict__)
###看下执行结果####
Traceback (most recent call last):
The new class id is 7146776
  File "E:/python_learn/test1.py", line 15, in <module>
    print(Student.__dict__)
AttributeError: 'NoneType' object has no attribute '__dict__'

为什么会是NoneType呢?因为在inner函数里没有返回值,所以是空类型,所以不能调用类的任何属性

看下面代码就明白了

def out(args):
    def inner(cls):
        cls._args = args
        print("The new class id is {}".format(id(cls)))
        return cls
    return inner
#@out("TOM")
class Student:
    pass

foo = out("TOM")
print(id(foo(Student)))
print(foo(Student).__dict__)
###看下结果###
The new class id is 32967960
32967960
The new class id is 32967960
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None, '_args': 'TOM'}

我们注释掉了@调用

直接以赋值的方式来调用

首先定义foo = out("TOM") ,此时foo的值是inner函数

print(foo(Student)) 这时是打印inner函数的返回值,如果inner函数没有return的话,那么inner函数的返回值就是None,还记得吗?如果函数不写return那么默认返回就是空None,这也就是为什么上面代码会报NoneType error 了。

在inner函数里,我们把Student类以参数方式传入,再用return返回Student类也就是形参cls,如果不返回的话,下面装饰调用就无法调用到了,调用过程和装饰器一样,所以必须有return cls

def out(args):
    def inner(cls):
        cls._args = args
        print("The new class id is {}".format(id(cls)))
        #return cls
    return inner
#@out("TOM")
class Student:
    pass

foo = out("TOM")
print(id(foo(Student)))
print(foo(Student).__dict__)

###注释掉return,一样的报错###
The new class id is 32247064
1577322688
The new class id is 32247064
Traceback (most recent call last):
  File "E:/python_learn/test1.py", line 13, in <module>
    print(foo(Student).__dict__)
AttributeError: 'NoneType' object has no attribute '__dict__'

然后下面的调用方式是不会出错的

def out(args):
    def inner(cls):
        cls._args = args
        print("The new class id is {}".format(id(cls)))
        #return cls
    return inner
#@out("TOM")
class Student:
    pass

foo = out("TOM")
print(id(foo(Student)))
print(foo(Student))
#####来看下结果########
The new class id is 37621016
1577322688
The new class id is 37621016
None

看到了有个None了吗?那是inner函数的默认返回值,这样调用是不会出错的,因为你没调用特殊属性啊,比如__dict__属性,空类型一调用肯定出错啊,所以这里不调用就没事了

return cls 的作用是,你传入的cls参数是什么类型,它给你返回的也是什么类型,只不过你传入的参数类型是个类,返回的是个增加了一个新属性的类而已

可以测试下

def out(args):
    def inner(cls):
        cls._args = args
        print("The new class id is {}".format(id(cls)))
        print(type(cls))
        return cls
    return inner
#@out("TOM")
class Student:
    pass

foo = out("TOM")
print(id(foo(Student)))
print(foo(Student))
####看下执行结果###
The new class id is 37883160
<class 'type'>
37883160
The new class id is 37883160
<class 'type'>
<class '__main__.Student'>
###看到了吧,类型为class###
def out(args):
    def inner(cls):
        #cls._args = args
        print("The new class id is {}".format(id(cls)))
        print(type(cls))
        return cls
    return inner
#@out("TOM")
class Student:
    pass

foo = out("TOM")
print(id(foo("tools")))
#####传入的是str那么返回的也是str###
The new class id is 32593504
<class 'str'>
32593504

 

总的来说,多实践出真知,才能明白其中的道理

在函数装饰器里,如果不返回任何值是不会报错的

def out(fn):
    def inner(args):
        print("这个是个装饰器,是用来装饰其他函数用的")
        ret = fn(args)
        print("******************")
        #return ret
    return inner

#@out
def test(name):
    print("这个是fn函数,是被装饰的")
    return name
#print(test("Bob"))
foo = out(test)
print(foo("JOke"))
####来看下结果####
这个是个装饰器,是用来装饰其他函数用的
这个是fn函数,是被装饰的
******************
None

下面也一样

def out(fn):
    def inner(args):
        print("这个是个装饰器,是用来装饰其他函数用的")
        ret = fn(args)
        print("******************")
        #return ret
    return inner

@out
def test(name):
    print("这个是fn函数,是被装饰的")
    return name
print(test("SBB"))
###############

这个是个装饰器,是用来装饰其他函数用的
这个是fn函数,是被装饰的
******************
None

具体为什么,很简单,打印的是inner函数,只要inner函数是正确的,有没有返回值是无所谓的。

posted on 2018-06-20 11:03  云上的天涯  阅读(212)  评论(0编辑  收藏  举报