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函数是正确的,有没有返回值是无所谓的。