面向对象进阶2

 

__setitem__,__getitem,__delitem__

把对象属性的操作模拟成字典的操作方式,不多说,老规矩,上代码来举例说明其中的奥妙之处:
class Foo:
    def __init__(self,name):            
        self.name = name
    def __setitem__(self, key, value):     #当对象用字典的形式设置值时触发执行
        self.__dict__[key] = value         #以字典的形式设置对象的值
    def __getitem__(self, item):           #当对象用字典的形式获取值时触发执行
        return self.__dict__[item]         #返回想要获取的对应的值
    def __delitem__(self, key):            #当对象用字典的形式删除某个值时触发执行
        self.__dict__.pop(key)             #删除对应的键和值
下面我们用不同的操作来验证我们的结果:
f = Foo('egon')
print(f['name'])
print(f.__dict__)

我们再添加一个数据属性age

f = Foo('egon')
print(f['name'])
print(f.__dict__)
f['age'] = 18
print(f.__dict__)

我们最后实验一下删除操作:

del f['age']
print(f.__dict__)

 -----------------------------------------------------------------------------------------------------------------------

__slots__方法
 使用__slots__之后实例化出来的对象中就没有自己的字典名称空间了,只能设置slots里面设置的那几个属性值了
1.优点省内存,不用每次实例化对象都建立那个对象的名称空间了
2.实例化出来的对象属性都是相同的
 
我们用实际代码举例来对比:
 1 #未使用__slots__的类
 2 class People:
 3     x = 1
 4     def __init__(self,name):
 5         self.name = name
 6 #使用__slots__的类
 7 class Foo:
 8     __slots__=['x','y','z']
 9     def __init__(self,name):
10         self.name = name
11 
12 print('这是People类的字典:%s'%People.__dict__)
13 p = People('alex')
14 print('这是p对象的字典:%s'%p.__dict__)
15 p.age = 18
16 print('这是p对象添加age属性后的字典:%s'%p.__dict__)
17 
18 print('这是Foo类的字典:%s'%Foo.__dict__)
19 f = Foo('egon')
20 print('这是p对象的字典:%s'%f.__dict__)
21 f.age = 18
22 print('这是f对象添加age属性后的字典:%s'%f.__dict__)

下面是结果截图,我们可以看到,我们可以看出对比,有__slots__定义属性的类实例化的时候报错了:

下面我们做下更改看看到底是怎么个用法:

class Foo:
    __slots__=['x','y','z']
    def __init__(self,name):
        self.name = name
f = Foo('egon')

这种初始化方式也会报错:

我再次更改:

class Foo:
__slots__=['x','y','z']

print(Foo.__dict__)
f = Foo()
print(f.__dict__)

从上面的结果我们可以看出,类实例化之前是有__dict__方法来查看它字典里面的内容的,然而,在实例化之后,f中并没有了__dict__这个方法。

下面我们继续验证:

print(Foo.__dict__)
f = Foo()
f.x = 1
f.y = 2
f.z = 3
print(f.x,f.y,f.z)

从上面结果我们可以看出实例化之后,我们可以正常使用x,y,z变量,下面我们增加属性可不可以呢?

f.u = 4

我们增加新的属性是不可行的,但这时候我们是能够通过类来直接添加属性的:

Foo.u = 4
print(f.u)

但是这种方式不建议使用,这样会打破对实例化出来的对象的限制和一致性。

 ----------------------------------------------------------------------------------------------------------------------

__next__和__iter__实现迭代器协议

 我们之前说过含有__iter__和__next__方法的对象就是一个迭代器,所以我们举例自己来定制一个迭代器:

 1 from collections import Iterable,Iterator
 2 class Foo:
 3     def __init__(self,start):
 4         self.start = start
 5     def __iter__(self):
 6         return self
 7     def __next__(self):
 8         if self.start>10:
 9             raise StopIteration
10         n = self.start
11         self.start+=1
12         return n
13 
14 f = Foo(0)
15 print(isinstance(f,Iterator)) #判断是不是一个生成器
16 print(isinstance(f,Iterable)) #判断是不是一个可迭代对象
17 for i in f:
18     print('------->',i)

结果截图:

以上结果可以判断用__iter__和__next__方法就可以生成一个迭代器

下面我们自己来模拟一个简单功能的range函数:

 1 from collections import Iterator,Iterable
 2 class Range:
 3     def __init__(self,start,*args):
 4         self.start = start
 5         self.stop = args
 6     def __iter__(self):
 7         return self
 8     def __next__(self):
 9         if self.start>=self.stop:
10             raise StopIteration
11         n = self.start
12         self.start+=1
13         return n

-------------------------------------------------------------------------------------------------------------------------

__doc__

 这个方法就是查看给类或函数增加的描述信息

class Foo:
    '我是描述信息'
    pass

print(Foo.__doc__)
class Foo:
    '我是描述信息'
    pass

class Bar(Foo):
    pass
print(Bar.__doc__) #该属性无法继承给子类
View Code

------------------------------------------------------------------------------------------------------------------------

 __module__和__class__

   __module__ 表示当前操作的对象在那个模块

  __class__     表示当前操作的对象的类是什么

class Foo:
    def __init__(self,name):
        self.name = name

f = Foo('egon')
print(f.__module__)
print(f.__class__)

输出结果如下:

-----------------------------------------------------------------------------------------------------------------

 __del__

析构函数:

析构方法,当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

 

 1 import time
 2 class Open:
 3     def __init__(self,filepath,mode = 'r',encoding = 'utf-8'):
 4         self.f = open(filepath,mode=mode,encoding=encoding)
 5         self.filepath = filepath
 6         self.mode = mode
 7         self.encoding = encoding
 8     def write(self,value):
 9         t = time.strftime('%Y-%m-%d %X')
10         self.f.write('%s  %s'%(t,value))
11     def __getattr__(self, item):
12         return getattr(self.f,item)
13     def __del__(self):
14         print('__del__执行了!!')
15         self.f.close()

 

下面我们用各种方法来实验到底什么时候执行__del__方法:

正常情况下程序都执行完成后,执行了__del__方法。

f = Open('1.txt',mode='r',encoding='utf-8')
print(f.read())

输出结果:

再看一种在程序为执行完的情况下:

f = Open('1.txt',mode='r',encoding='utf-8')
print(f.read())
f.seek(0)
print(f.readline())
del f
print('------------>')

输出结果:

从这里我们可以看出,在程序未完全执行完成就执行了__del__方法,原因是我们将文件句柄f在内存中清除掉了,所以这个名字f对文件的句柄开辟的那一块内存空间没有了引用所以__del__会被触发执行,我们来进一步实验看看是不是这个原理。 

f = Open('1.txt',mode='r',encoding='utf-8')
print(f.read())
f.seek(0)
print(f.readline())
f_bak = f
del f
print('------------>')

输出结果为:

由于我们在删除f之前把它的指向复制给了另一个变量f_bak,所以这时候指向文件句柄开辟的内存空间变量是两个,这时候我们删掉f,还有另一个f_bak指向那块内存空间,所以,在程序执行完成之前并没有释放掉f_bak对那块内存空间的索引,所以__del__在程序的最后才执行。

---------------------------------------------------------------------------------------------------------------------

__enter__和__exit__

我们知道在操作文件对象的时候可以这样写:

with open(文件名) as f:
  '代码块'

上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

 

 1 import time
 2 class Open:
 3     def __init__(self,filepath,mode = 'r',encoding = 'utf-8'):
 4         self.f = open(filepath,mode=mode,encoding=encoding)
 5         self.filepath = filepath
 6         self.mode = mode
 7         self.encoding = encoding
 8     def write(self,value):
 9         t = time.strftime('%Y-%m-%d %X')
10         self.f.write('%s  %s'%(t,value))
11     def __getattr__(self, item):
12         return getattr(self.f,item)
13     def __enter__(self):
14         return self
15     def __exit__(self, exc_type, exc_val, exc_tb):
16         self.f.close()
17         return True
18 with Open('1.txt',mode='r',encoding='utf-8') as f:
19     print(f.read())

__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

 
 1 import time
 2 class Open:
 3     def __init__(self,filepath,mode = 'r',encoding = 'utf-8'):
 4         self.f = open(filepath,mode=mode,encoding=encoding)
 5         self.filepath = filepath
 6         self.mode = mode
 7         self.encoding = encoding
 8     def write(self,value):
 9         t = time.strftime('%Y-%m-%d %X')
10         self.f.write('%s  %s'%(t,value))
11     def __getattr__(self, item):
12         return getattr(self.f,item)
13     def __enter__(self):
14         return self
15     def __exit__(self, exc_type, exc_val, exc_tb):
16         print('执行了__exit__方法')
17         print(exc_type)
18         print(exc_val)
19         print(exc_tb)
20 with Open('1.txt',mode='r',encoding='utf-8') as f:
21     print(f.read())
22     raise TypeError('我主动抛出了个异常看看结果')
23     f.seek(0)
24     print(f.read())
结果截图:

如果__exit__返回值为True,那么异常会被清空,就好像啥都没发生一样,with内异常后的语句无法正常执行,但是with语句以外后面的语句是正常执行的

 1 import time
 2 class Open:
 3     def __init__(self,filepath,mode = 'r',encoding = 'utf-8'):
 4         self.f = open(filepath,mode=mode,encoding=encoding)
 5         self.filepath = filepath
 6         self.mode = mode
 7         self.encoding = encoding
 8     def write(self,value):
 9         t = time.strftime('%Y-%m-%d %X')
10         self.f.write('%s  %s'%(t,value))
11     def __getattr__(self, item):
12         return getattr(self.f,item)
13     def __enter__(self):
14         return self
15     def __exit__(self, exc_type, exc_val, exc_tb):
16         print('执行了__exit__方法')
17         print(exc_type)
18         print(exc_val)
19         print(exc_tb)
20         return True
21 with Open('1.txt',mode='r',encoding='utf-8') as f:
22     print(f.read())
23     raise TypeError('我主动抛出了个异常看看结果')
24     f.seek(0)
25     print(f.read())
26 print('with操作文件的语句已经结束了,这里开始新的代码块的执行!!')

 

结果截图:

如果我们想正常操作一个文件,并在操作完后应该进行文件进行自动关闭的操作,我们要把代码写成第一段代码里面__exit__中的内容,将文件进行关闭操作。

--------------------------------------------------------------------------------------------------------------------

__call__

对象后面加括号,触发执行。

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:
    def __init__(self):
        pass
     def __call__(self, *args, **kwargs):
        print('__call__')

obj = Foo() # 执行 __init__
obj()       # 执行 __call__

----------------------------------------------------------------------------------------------------------------

元类:metaclass

平时我们创建一个类要遵守的三个要点是:

1、要有类的名称
2、要有字典格式的名称空间
3、类的继承关系
class Foo:
    pass

f1=Foo() #f1是通过Foo类实例化的对象

 python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)

上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?

#type函数可以查看类型,也可以用来查看对象的类,二者是一样的
print(type(f1)) # 输出:<class '__main__.Foo'>     表示,obj 对象由Foo类创建
print(type(Foo)) # 输出:<type 'type'>  

 什么是元类?

元类是类的类,是类的模板

元类是用来控制如何创建类的,正如类是创建对象的模板一样

元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例Foo类是 type 类的一个实例)

type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

 

 定义类有两种方式:

# 第一种方式:
class People:
    def func(self):
        print('from func')


# 第二种方式:
def run(self):
    print('run')
class_name = 'Foo'
class_dict = {
    'x' : 1,
    'run':run,
}
bases = (object,)
Foo = type(class_name,bases,class_dict)

一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)

class Foo:
    def __init__(self,name):
        self.name = name

f = Foo('egon')
print(type(Foo)) #输出结果:<class 'type'>

我们自定制一个元类:

 1 class My_class(type):
 2     def __init__(self,class_name,class_bases,class_dict):
 3         print('class_name:%s'%class_name)
 4         print('class_bases:',class_bases)
 5         print('class_dict:%s'%class_dict)
 6         print('self:%s'%self)
 7 
 8 class Foo(metaclass=My_class):
 9     x = 1
10     def __init__(self,name):
11         self.name = name
12     def run(self):
13         print('run')

上面8-10行的代码相当于做了这件事情:

Foo = type('Foo',(object,),{'x':1,'run':run})

在我们执行这段代码的时候,会触发My_class中的__init__()这个方法的执行,所以执行的结果为:

我们现在将上面的代码加上一个功能,这个需求是这样的,规定每个类中的函数属性都必须加上注释,如果没加上注释说明的话就报错,代码是这样的:

 1 class My_class(type):
 2     def __init__(self,class_name,class_bases,class_dict):
 3         for key in class_dict:
 4             if not callable(class_dict[key]):continue
 5             if not class_dict[key].__doc__:
 6                 raise TypeError('你必须加上注释!!')
 7 
 8 class Foo(metaclass=My_class):
 9     x = 1
10     def __init__(self,name):
11         self.name = name
12     def run(self):
13         print('run')

加上以上的代码后,运行程序会报错:

如果我们给每个函数方法都加上注释的话就不会出现报错的情况:

 1 class My_class(type):
 2     def __init__(self,class_name,class_bases,class_dict):
 3         for key in class_dict:
 4             if not callable(class_dict[key]):continue
 5             if not class_dict[key].__doc__:
 6                 raise TypeError('你必须加上注释!!')
 7 
 8 class Foo(metaclass=My_class):
 9     x = 1
10     def __init__(self,name):
11         'func init'
12         self.name = name
13     def run(self):
14         'func run'
15         print('run')

元类的总结:

 1 #元类总结
 2 class Mymeta(type):
 3     def __init__(self,name,bases,dic):
 4         print('===>Mymeta.__init__')
 5 
 6 
 7     def __new__(cls, *args, **kwargs):
 8         print('===>Mymeta.__new__')
 9         return type.__new__(cls,*args,**kwargs)
10 
11     def __call__(self, *args, **kwargs):
12         print('aaa')
13         obj=self.__new__(self)
14         self.__init__(self,*args,**kwargs)
15         return obj
16 
17 class Foo(object,metaclass=Mymeta):
18     def __init__(self,name):
19         self.name=name
20     def __new__(cls, *args, **kwargs):
21         return object.__new__(cls)
22 
23 '''
24 需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的爹,然后执行:爹.__call__
25 
26 而爹.__call__一般做两件事:
27 1.调用name.__new__方法并返回一个对象
28 2.进而调用name.__init__方法对儿子name进行初始化
29 '''
30 
31 '''
32 class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行
33 Foo=Mymeta('foo',(...),{...})
34 因此我们可以看到,只定义class就会有如下执行效果
35 ===>Mymeta.__new__
36 ===>Mymeta.__init__
37 实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta('Foo',(...),{...})操作,
38 遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法
39 于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化
40 '''
41 
42 '''
43 obj=Foo('egon')
44 的原理同上
45 '''
46 
47 '''
48 总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了
49 1.谁后面跟括号,就从谁的爹中找__call__方法执行
50 type->Mymeta->Foo->obj
51 Mymeta()触发type.__call__
52 Foo()触发Mymeta.__call__
53 obj()触发Foo.__call__
54 2.__call__内按先后顺序依次调用儿子的__new__和__init__方法
55 '''

简单的过程分析:

 1 class Mymeta(type):
 2      def __init__(self,class_name,class_bases,class_dic):
 3             pass
 4      def __call__(self, *args, **kwargs):
 5         obj=self.__new__(self)            #实例化一个空对象
 6         self.__init__(obj,*args,**kwargs) #相当于调用Foo中的init方法,obj.name='egon'
 7         return obj                       #返回一个空对象
 8 class Foo(metaclass=Mymeta):
 9     x=1
10     def __init__(self,name):
11         self.name=name #obj.name='egon'
12     def run(self):
13         'run function'
14         print('running')
15 # print(Foo.__dict__)
16 
17 f=Foo('egon')  #这相当于调用Mymeta中的__call__方法,并传值
18 
19 print(f)
20 
21 print(f.name)

“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。”  —— Python界的领袖 Tim Peters

 

 

 

 

 

 

 












 

posted @ 2017-04-25 20:30  不老玩童萧龙  阅读(127)  评论(0编辑  收藏  举报