面向对象进阶

面向对象进阶

一、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类。

posted @ 2019-08-06 17:05  流浪代码  阅读(256)  评论(0编辑  收藏  举报