面向对象进阶
面向对象进阶 |
一、isinstance(obj,cls)与issubclass(sub,super)
isinstance(obj,cls)检查是否obj是否是类cla的对象。具体代码如下所示:
class Foo: pass f1 = Foo() print(isinstance(f1,Foo)) #结果为:True
issubclass(sub,super)检查sub类是否是super类的派生类。具体代码如下:
class Foo: pass class Bar(Foo): pass print(issubclass(Bar,Foo)) #结果为:True
二、__getattribute__
我们在前面介绍过__getattr__的用法,当只有在使用点调用属性且属性不存在时候才会执行,那么__getattribute__不管存不存在都会执行,具体实现代码如下:
class Foo: def __init__(self,x): self.x = x def __getattribute__(self, item): print('执行的是getattribute') f1 = Foo(10) f1.x #结果为:执行的是getattribute f1.sss #结果为:执行的是getattribute
那么__getattr__与__getattribute__一起使用时,会先执行谁呢?我们通过一段代码来分析:
class Foo: def __init__(self,x): self.x = x def __getattr__(self, item): print('执行的是getattr') def __getattribute__(self, item): print('执行的是getattribute') f1 = Foo(10) f1.x #结果为:执行的是getattribute f1.sss #结果为:执行的是getattribute
从结果可以看出当__getattr__与__getattribute__一起使用时,只会执行__getattribute__。如果要执行__getattr__,除非__getattribute__在执行过程中抛出异常AttributeError。
三、类内置item函数
类内置item函数与之前学过的类内置attr函数用法十分相似,类内置attr函数是以点的方式调用,而类内置item函数以字典的方式调用
类内置item函数,分别是__getitem__、__setitem__、__delitem__。具体实现代码如下:
class Foo: def __getitem__(self, item): print('getitem') return self.__dict__[item] def __setitem__(self, key, value): print('setitem') self.__dict__[key] = value def __delitem__(self, key): print('delitem') self.__dict__.pop(key) f1 = Foo() print(f1.__dict__) #结果为:{} f1['name'] = 'alex' #结果为:setitem f1['age'] = 18 #结果为:setitem print(f1.__dict__) #结果为:{'name': 'alex', 'age': 18} del f1['name'] #结果为:delitem print(f1.__dict__) #结果为:{'age': 18} f1['age'] #结果为:getitem
四、__str__与__repr__
这两个方法是改变对象的字符串显示,从字面意思就可以看出把输出的对象用于字符串显示。
我们来对以下代码来分析:
class Foo: def __init__(self,name,age): self.name = name self.age = age def __str__(self): return '名字是%s年龄是%s' %(self.name,self.age) def __repr__(self): return '这是repr' f1 = Foo('alex',18) print(f1) #结果为:名字是alex年龄是18
上述代码使用print操作时执行了类中的__str__方法,那么将__str__方法先注释,我们来看看到底print打印了什么?
class Foo: def __init__(self,name,age): self.name = name self.age = age def __repr__(self): return '这是repr' f1 = Foo('alex',18) print(f1) #结果为:这是repr'
可以看出print操作执行了类中的__repr__方法,那么当__str__与__repr__一起使用时,一定是执行__str__吗?答案不是。下面就来介绍当什么情况的时候执行谁?
- 当str函数或者print函数时,执行的是__str__;
- 当repr函数或者交互式解释器时,执行的是__repr__;
- 如果__str__没有被定义,那么就会使用__repr__来替换输出;
- 这两方法的返回值必须是字符串,否则抛出异常
五、__format__
__format__可以自定制格式化字符串。具体实现如下:
format_dic = { 'ymd' : '{0.year}{0.mon}{0.day}', 'y:m:d' : '{0.year}:{0.mon}:{0.day}', 'y-m-d' : '{0.year}-{0.mon}-{0.day}' } class Date(): def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day def __format__(self, format_spec): if not format_spec or format_spec not in format_dic: #不写格式或者不符合字典的格式,即生成默认格式 format_spec = 'ymd' fm = format_dic[format_spec] return fm.format(self) d1 = Date(2019,7,30) print(format(d1)) print(format(d1,'ymd')) print(format(d1,'y-m-d')) print(format(d1,'dgdg'))
执行结果为:
2019730 2019730 2019-7-30 2019730
六、__slots__
__slots__是一个类变量,变量值可以是列表,元组,或者可迭代对象,也可以是一个字符串。
为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__。
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个。
class Foo: __slots__ = 'name' f1 = Foo() f1.name = 'alex' print(f1.name) #结果为:alex #print(f1.__dict__) #报错,因为类中定义了__slots__='name',所以name并没有存在属性字典中 print(f1.__slots__) #结果为:name
七、__doc__
__doc__是类的描述信息,该属性不能被继承。代码如下:
class Foo: '我是描述信息' pass class Bar(Foo): pass print(Foo.__doc__) #结果为:我是描述信息 print(Bar.__doc__) #结果为:None
八、__module__与__class__
__module__ 表示当前操作的对象在那个模块。
__class__表示当前操作的对象的类是什么。
例如aa.py文件下写如下代码:
class C: def __init__(self): self.name = 'alex'
在另一个文件下执行下面代码:
from aa import C print(c1.__module__) #结果为:aa print(c1.__class__) #结果为:<class 'aa.C'>
九、__del__
__del__被称为析构方法,当对象在内存中被释放时,自动触发执行。下面我们对两段代码进行分析:
第一段代码:
class Foo(): def __init__(self,name): self.name = name def __del__(self): print('我执行了') f1 = Foo('alex') print('-------------->')
执行结果为:
-------------->
我执行了
第二段代码:
class Foo(): def __init__(self,name): self.name = name def __del__(self): print('我执行了') f1 = Foo('alex') del f1 print('-------------->')
执行结果为:
我执行了
-------------->
上面两段代码为何输出结果相反?因为执行del了相当于提前释放对象。
十、__call__
__call__是指对象后面加括号,触发执行。
我们以前学过构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()。具体代码如下:
class Foo: def __call__(self, *args, **kwargs): print('实例执行了') f1 = Foo() f1() #结果为:实例执行了
十一、__iter__与__next__
这两个方法跟我们之前学的迭代器方法是一样的,它们就是迭代器协议。实现代码如下:
class Foo: def __init__(self,n): self.n = n def __iter__(self): return self def __next__(self): if self.n == 13: #当self.n等于13时抛出异常 raise StopIteration('终止了') self.n += 1 return self.n f1 = Foo(10) print(f1.__next__()) #结果为:11 print(f1.__next__()) #结果为:12 print(next(f1)) #结果为:13 print(next(f1)) #抛出异常
利用迭代器协议实现斐波那契数列,具体实现代码如下:
class Fib: def __init__(self): self._a = 1 self._b = 1 def __iter__(self): return self def __next__(self): if self._a > 10: raise StopIteration('终止') self._a,self._b = self._b,self._a+self._b return self._a f1 = Fib() print(next(f1)) print(next(f1)) print('-------------->') for i in f1: print(i)
执行结果为:
1 2 --------------> 3 5 8 13
十二、__enter__与__exit__
我们之前学的操作文件的时候可以这么写:
with obj as f: '代码块'
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法。即:
class Open: def __init__(self,name): self.name = name def __enter__(self): print('执行enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print('执行exit') with Open('a.txt') as f: #执行了__enter__ print(f) print(f.name) print('------------') #代码块执行结束后执行__exit__ print('结束')
执行上述代码结果为:
执行enter <__main__.Open object at 0x0000000002729748> a.txt ------------ 执行exit 结束
如果在代码块中间调用了不存在的属性则会直接执行__exit__并抛出异常,具体代码如下:
class Open: def __init__(self,name): self.name = name def __enter__(self): print('执行enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print('执行exit') with Open('a.txt') as f: #执行了__enter__ print(f) print(f.name) print(f.age) #不存在的属性 print('------------') print('结束')
执行结果为:
Traceback (most recent call last): File "C:/Users/Administrator/PycharmProjects/untitled1/day28/上下文管理协议.py", line 28, in <module> 执行enter print(f.age) AttributeError: 'Open' object has no attribute 'age' <__main__.Open object at 0x00000000021C9780> a.txt 执行exit
从上述执行结果可以看出代码到print(f.age)就执行了__exit__方法并抛出了异常,导致相同代码块中的print('------------')与外部代码print('结束')都没有执行,那么我想继续执行外部代码,那么该如果实现?即:
class Open: def __init__(self,name): self.name = name def __enter__(self): print('执行enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print('执行exit') return True #吞掉异常 with Open('a.txt') as f: #执行了__enter__ print(f) print(f.name) print(f.age) #不存在的属性 print('------------') print('结束')
执行结果为:
执行enter <__main__.Open object at 0x00000000004D9780> a.txt 执行exit 结束
可以看出异常也没有了并执行了外部的代码,不过在调用不存在属性时直接触发了__exit__方法。
讲到__exit__方法,那么它里面的三个参数各代表着什么?它们都是抛出异常才会产生的。具体看下图:
十三、元类
一个类可以实例化成对象,那么这个类也是一个对象,正是因为Python中一切皆对象。既然这个类是对象,那么这个类肯定也是另一个类实例化而成,而另一个类就是元类。
一个类没有声明自己的元类,默认它的元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个类指定元类。
下面我们简单地定义一个元类,即:
def __init__(self,name,age): self.name = name self.age = age Bar = type('Bar',(object,),{'x':1,'__init__':__init__}) #定义元类,格式:type(类名,(继承什么类),{属性}) print(Bar) #结果为:<class '__main__.Bar'> b1 = Bar('alex',18) print(b1.name) #结果为:alex
我们也来实现一下自定义的元类,即:
class MyType(type): def __init__(self,a,b,c): print('元类的构成函数执行了') def __call__(self, *args, **kwargs): print('===============') obj = object.__new__(self) #object.__new__(Foo) self.__init__(obj,*args,**kwargs) #Foo.__init__ return obj class Foo(metaclass=MyType): #相当于执行MyType('Foo',(object,),{}) #Foo=MyType('Foo',(),{})---》执行__init__,参数要设定好 def __init__(self,name): self.name = name print(type(Foo)) f1 = Foo('alex') #相对于对象后加括号,执行__call__方法 print(f1)
执行结果为:
元类的构成函数执行了 <class '__main__.MyType'> =============== <__main__.Foo object at 0x0000000002196F60>
从上述代码定义了Foo的元类就是MyType类。