python的魔术方法、类装饰器、单例

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__')
1
a = A()
call __new__
call __init__

从上边输出可知__new____init__方法是在类实例化时就被执行了。

1
a.mothod()   #普通方法被显示调用时被执行
call mothod

__del__方法是在对象被销毁时被执行,即是垃圾回收时,这里可以用del来模拟垃圾回收时删除实例a这个对象,如下:

1
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')
1
b = B('zhaochj')
1
b
call __repr__ name is zhaochj
1
print(b)
call __str__ name is zhaochj
1
str(b)
'call __str__ name is zhaochj'
1
bytes(b)
b'call __bytes__ name is zhaochj'

比较运算符重载

  • __lt__ 小于
  • __le__ 小于等于
  • __eq__ 等于
  • __ne__ 不等于
  • __gt__ 大于
  • __ge__ 大于等于

先看下边这个类:

1
2
3
class Person:
def __init__(self,age):
self.age = age
1
p1 = Person(30)
1
p2 = Person(20)
1
p1.age > p2.age
True

上边对实例变量可以进行比较,如果想对实例对象进行比较呢?如下:

1
p1 > p2
---------------------------------------------------------------------------

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
1
p3 = Person_1('30')
1
p4 = Person_1('20')
1
p3 > p4
call __gt__





True
1
p3 < p4
call __lt__





False
1
p3 == p4
call __eq__





True

如上,只要一个实例变量实现了__lt__这样的方法,那实例也可以进行比较。

bool函数

1
lst = []
1
bool(lst)
False
1
lst1 = [1,2,3]
1
bool(lst1)
True
1
str1 = 'zhaochj'
1
bool(str1)
True
1
str2 = ''
1
bool(str2)
False
1
bool(True)
True
1
bool(False)
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
1
g = Grok('')
1
bool(g)
call __bool__





True
1
g1 = Grok('zhaochj')
1
bool(g1)
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)
1
s1 = Seq(1,2,3)
1
len(s1)
call __len__





3

从上边的输出可知执行len(s1)时,实质是执行了__len__函数。

1
s2 = Seq()
1
len(s2)
call __len__





0
1
bool(s1)
call __len__





True
1
bool(s2)
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
1
s3 = Seq_1()
1
bool(s3)
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
1
h1 = Hash('zhaochj')
1
hash(h1)
123

从上边输出可知,执行hash(object)函数时,实质是调用了__hash__方法,如果类中不定义__hash__方法呢?如下:

1
2
3
class Hash_1:
def __init__(self,val):
self.val = val
1
h2 = Hash_1('zhaochj')
1
hash(h2)
8795459756152

依然得到了一个hash值,为什么呢?这是因为python中所有的类都继承了object基类,object类已实现了__hash__方法,可以用dir(object)查看object这个类的属性。

可调用对象

在python中可以用callable函数查看一个对象是不是可调用,如下:

1
2
def fn():
print('ha ha ha')
1
callable(fn)
True

输出为True,函数当然是一个可调用对象,如果是一个类呢?如下:

1
2
3
class Fun:
def __init__(self,name):
self.name = name
1
f = Fun('zhaochj')
1
callable(f)
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))
1
f1 = Fun_1('zhaochj')
1
callable(f1)
True

cool,f1这个实例现在已是一个可调用对象了。所以只要一个类中实现了__call__方法,那么类实例就是一个可调用对象。调用此对象试试,如下:

1
f1()
my name is zhaochj

如上,事实证明,调用此实例对象也就是执行了__call__方法。既然是可调用对象,就可以向调用函数一样传递参数来调用,只要在__call__方法中定义可接收参数即可。

类通过实现__call__方法可以让实例变成一个可调用对象,如果我们向这个可调用对象传递一个函数作为其参数,那__call__函数就可以写成一个装饰器,如下:

1
2
3
4
5
6
7
8
9
10
11
12
import functools
class 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'))
1
do_somthings()
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__方法实现单例

所谓单例,是指一个类的实例从始至终只能被创建一次。单例的实现有多种,这里以__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
1
grok1 = Grok1()
1
id(grok1)
140271956645592
1
grok2 = Grok1()
1
id(grok2)
140271956645592
1
id(grok1) == id(grok2)
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这个对象在被多次实例化后指向的实例化对象都是一样的,其实是只被实例化了一次而已。

文章目录
  1. 1. 魔术方法
    1. 1.1. 对象的创建与销毁
    2. 1.2. 可视化对象
    3. 1.3. 比较运算符重载
    4. 1.4. bool函数
    5. 1.5. hash()与可hash对象
    6. 1.6. 可调用对象
|