python学习笔记之魔术方法、类装饰器、单例
魔术方法
对象的创建与销毁
__new__
创建对象
__init__
初始化对象
__del__
当销毁对象时调用
以一个例子来说明上边各个魔术方法在哪时被调用,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 class A : def __new__ (cls ): print('call __new__' ) return object.__new__(cls) def __init__ (self ): print('call __init__' ) def mothod (self ): print('call mothod' ) def __del__ (self ): print('call __del__' )
call __new__
call __init__
从上边输出可知__new__
和__init__
方法是在类实例化时就被执行了。
call mothod
__del__
方法是在对象被销毁时被执行,即是垃圾回收时,这里可以用del
来模拟垃圾回收时删除实例a
这个对象,如下:
call __del__
在实际编程中,使用最多的是__init__
方法
可视化对象
__repr__
对应repr(object)这个函数,返回一个可以用来表示对象的可打印字符串
__str__
对应str(object)这个函数,返回一个字符串对象,适合用于print输出
__bytes__
对应bytes(object)这个函数,返回bytes对象
1 2 3 4 5 6 7 8 9 10 11 12 class B : def __init__ (self,name ): self.name = name def __repr__ (self ): return 'call __repr__ name is {0}' .format(self.name) def __str__ (self ): return 'call __str__ name is {0}' .format(self.name) def __bytes__ (self ): return 'call __bytes__ name is {0}' .format(self.name).encode('utf-8' )
call __repr__ name is zhaochj
call __str__ name is zhaochj
'call __str__ name is zhaochj'
b'call __bytes__ name is zhaochj'
比较运算符重载
__lt__
小于
__le__
小于等于
__eq__
等于
__ne__
不等于
__gt__
大于
__ge__
大于等于
先看下边这个类:
1 2 3 class Person : def __init__ (self,age ): self.age = age
True
上边对实例变量可以进行比较,如果想对实例对象进行比较呢?如下:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-42-64d46152e12c> in <module>()
----> 1 p1 > p2
TypeError: unorderable types: Person() > Person()
抛出了TypeError
,如果要实例能对实例对象进行比较,那要实现一些方法,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Person_1 : def __init__ (self,age ): self.age = age def __lt__ (self,other ): print('call __lt__' ) return self.age < other.age def __le__ (self,other ): print('call __le__' ) return self.age <= other.age def __eq__ (self,other ): print('call __eq__' ) return self.age == other.age def __ne__ (self,other ): print('call __ne__' ) return self.age != other.age def __gt__ (self,other ): print('call __gt__' ) return self.age > other.age def __ge__ (self,other ): print('call __ge__' ) return self.age >= other.age
call __gt__
True
call __lt__
False
call __eq__
True
如上,只要一个实例变量实现了__lt__
这样的方法,那实例也可以进行比较。
bool函数
False
True
True
False
True
False
从上边的输出可知,对空列表、空字符调用bool函数时返回False,对有元素的列表、字符调用bool函数时返回True。这其内部是什么原理呢?先来看一个类,如下:
1 2 3 4 5 6 7 class Grok : def __init__ (self,val ): self.val = val def __bool__ (self ): print('call __bool__' ) return not self.val
call __bool__
True
call __bool__
False
从上边的例子可知,在调用bool(object)
函数时其实是调用了__bool__
这个方法。
接下来再一个例子,如下:
1 2 3 4 5 6 7 class Seq : def __init__ (self,*args ): self.val = args def __len__ (self ): print('call __len__' ) return len(self.val)
call __len__
3
从上边的输出可知执行len(s1)
时,实质是执行了__len__
函数。
call __len__
0
call __len__
True
call __len__
False
从上边的输出可知,当执行bool(object)
函数时也是调用了__len__
函数,但是把结果进行了bool计算。如果__len__
和__bool__
方法同时存在类中时,当我们执行bool(object)
时是调用哪个方法呢?做如下测试:
1 2 3 4 5 6 7 8 9 10 11 class Seq_1 : def __init__ (self,*args ): self.val = args def __len__ (self ): print('call __len__' ) return len(self.val) def __bool__ (self ): print('call __bool__' ) return True
call __bool__
True
上边实例化Seq_1
类时传递了一个空序列,但调用bool函数时返回了True,这表明,当类中实现了__bool__
方法时会被调用,而__len__
方法则不会被调用,如果没有__bool__
时才调用__len__
方法。
hash()与可hash对象
1 2 3 4 5 6 class Hash : def __init__ (self,val ): self.val = val def __hash__ (self ): return 123
123
从上边输出可知,执行hash(object)
函数时,实质是调用了__hash__
方法,如果类中不定义__hash__
方法呢?如下:
1 2 3 class Hash_1 : def __init__ (self,val ): self.val = val
8795459756152
依然得到了一个hash值,为什么呢?这是因为python中所有的类都继承了object基类,object类已实现了__hash__
方法,可以用dir(object)
查看object这个类的属性。
可调用对象
在python中可以用callable
函数查看一个对象是不是可调用,如下:
1 2 def fn (): print('ha ha ha' )
True
输出为True,函数当然是一个可调用对象,如果是一个类呢?如下:
1 2 3 class Fun : def __init__ (self,name ): self.name = name
False
如上,一个类被实例化后,这个实例对象是一个不可调用对象。
有什么方法能让一个实例对象变成可调用对象呢?做如下修改:
1 2 3 4 5 6 class Fun_1 : def __init__ (self,name ): self.name = name def __call__ (self ): print('my name is {0}' .format(self.name))
True
cool,f1
这个实例现在已是一个可调用对象了。所以只要一个类中实现了__call__
方法,那么类实例就是一个可调用对象。调用此对象试试,如下:
my name is zhaochj
如上,事实证明,调用此实例对象也就是执行了__call__
方法。既然是可调用对象,就可以向调用函数一样传递参数来调用,只要在__call__
方法中定义可接收参数即可。
类通过实现__call__
方法可以让实例变成一个可调用对象,如果我们向这个可调用对象传递一个函数作为其参数,那__call__
函数就可以写成一个装饰器,如下:
1 2 3 4 5 6 7 8 9 10 11 12 import functoolsclass InjectUser : def __init__ (self,default_user ): self.user = default_user def __call__ (self,fn ): @functools.wraps(fn) def wrap (*args,**kwargs ): if 'user' not in kwargs.keys(): kwargs['user' ] = self.user return fn(*args,**kwargs) return wrap
1 2 3 @InjectUser('zhaochj') def do_somthings (*args,**kwargs ): print(kwargs.get('user' ))
zhaochj
分析一下上边的代码:
1 2 3 4 5 6 7 def __call__(self,fn): @functools.wraps(fn) def wrap(*args,**kwargs): if 'user' not in kwargs.keys(): kwargs['user'] = self.user return fn(*args,**kwargs) return wrap
上边的代码是定义一个装饰器。
1 2 3 @InjectUser('zhaochj') def do_somthings(*args,**kwargs): print(kwargs.get('user'))
这里的魔法等价执行了InjectUser('zhaochj')(do_somthings)
,实质是返回装饰器的wrap函数。执行do_somthings()
时,实质是执行了wrap()
,并返回fn(*args,**kwargs)
,到这里才真正执行
1 2 def do_somthings(*args,**kwargs): print(kwargs.get('user'))
函数中的print语句。
所谓单例,是指一个类的实例从始至终只能被创建一次。单例的实现有多种,这里以__call__
方法来实现。
1 2 3 4 5 6 7 8 9 10 class Single : __instance = None def __init__ (self,cls ): self.cls = cls def __call__ (self,*args,**kwargs ): if self.__instance is None : self.__instance = self.cls(*args,**kwargs) return self.__instance
1 2 3 @Single class Grok1 : pass
140271956645592
140271956645592
True
现在来分析一下上边的代码:
1 2 3 4 5 @Single class Grok1: pass grok1 = Grok1()
实例化Grok1
类时相当于执行了下边两步:
1 2 1. Grok1 = Single(Grok1) 2. grok1 = Grok1()
第一步:执行Single(Grok1)
是返回一个Single
类的实例,并用一个变量Grok1指向这个实例对象,此时的Grok1不是class Grok1:
里的Grok1类,只是名字相同而已,Single
类实例化时__init__
构建函数被调用,这里会把self.cls
这个实例属性指向Grok1
类。
第二步:因Single
类实现了__call__
方法,所以此实例是一个可调用对象,这里执行grok1 = Grok1()
,其中的Grok1
已不再是class Grok1:
里的类,而是Single
类的实例对象,这里调用实例对象就会执行__call__
方法,对此方法中的代码做进一步分析
1 2 3 4 def __call__(self,*args,**kwargs): if self.__instance is None: self.__instance = self.cls(*args,**kwargs) return self.__instance
当第一次调用时,if语句的结果为True,此时会执行self.__instance = self.cls(*args,**kwargs)
,其中的self.cls
指向Grok1
类,即这里表示实例化Grok1
类,并把self.__instance
指向实例化Grok1
类的对象,这样__instance
就不再是None
了,当第二次调用__call__
函数时,if语句的结果为False,所以直接执行return self.__instance
,所以Grok1
这个对象在被多次实例化后指向的实例化对象都是一样的,其实是只被实例化了一次而已。