文章目录
多继承和MRO顺序
多继承
在python中,一个子类可以同时继承多个父类,这种继承方式就叫做多继承。
class A(object):
def a(self):
print('aa')
class B(object):
def b(self):
print('bb')
def a(self):
print('ba')
class C(A, B):
pass
class D(B, A):
pass
obj1 = C()
obj2 = D()
obj1.a()
obj1.b()
obj2.a()
obj2.b()
以上就是一个多继承的例子,C类继承了A类和B类,就同时拥有了A类和B类的方法。并且,若多个父类中有同样的方法或属性,子类将会按照顺序来继承第一个。
class A(object):
def a(self):
print('aa')
class B(object):
def b(self):
print('bb')
class C(B, A):
def a(self):
print('cc')
obj = C()
obj.a()
obj.b()
以上,子类中定义了和父类中同样名称的方法,这称作“子类重写父类同名方法”,这种情况下,子类将不再继承父类的方法,而使用自己的方法。
然而有时我们会遇到重写之后还需要原来的父类方法的情况。这种情况下,就需要我们重新调用父类方法。
class A(object):
def a(self):
print('aa')
def b(self):
print('bb')
def c(self):
print('cc')
class C(A):
def a(self):
print('33')
# 父类名.方法名()
A.a(self)
def b(self):
print('33')
# super(子类名, self).方法名()
super(C, self).b()
def c(self):
print('33')
# super().方法名()
super().c()
c = C()
c.a()
c.b()
c.c()
以上是调用父类同名方法的三种方式。
以上三种方法看似并无差别,但是一档用到多继承,就会有可能出现问题。
class A(object):
def a(self):
print('AAAA')
class B1(A):
def a(self):
print('B1B1')
A.a(self)
class B2(A):
def a(self):
print('B2B2')
A.a(self)
class C(B1, B2):
def a(self):
print('CCCC')
B1.a(self)
B2.a(self)
cc = C()
cc.a()
C 类继承与两个 B 类,两个B类又同样在继承A类的同时给A类中的同一个方法添加了不同的功能。这种情况下,如果C类继承B类后想要同时使用两个B类的方法,就不免要重复调用两次A类的原始方法。这并不是我们想要的结果。
MRO顺序
为了解决上述问题,Python官方采用了一个算法将复杂结构上所有的类全部都映射到一个线性顺序上,而根据这个顺序就能够保证所有的类都会被构造一次。
这个顺序就是MRO顺序。
查看MRO顺序
类名.__mro__()
类名.mro()
super本质上就是使用MRO这个顺序去调用 当前类在MRO顺序中下一个类。 super().方法名()
则调用了下一个类的初始化方法进行构造。
class A(object):
def a(self):
print('AAAA')
class B1(A):
def a(self):
print('B1B1')
super().a()
print(11111)
class B2(A):
def a(self):
print('B2B2')
super().a()
print(22222)
class C(B1, B2):
def a(self):
print('CCCC')
super().a()
print(33333)
cc = C()
cc.a()
print(C.mro())
打印结果:
CCCC
B1B1
B2B2
AAAA
22222
11111
33333
[<class '__main__.C'>, <class '__main__.B1'>, <class '__main__.B2'>, <class '__main__.A'>, <class 'object'>]
打印结果的最后一行就是所谓的MRO顺序,在super调用父类方法时,他不会直接去父类里面找方法,而是会按照MRO顺序列表里的顺序去查找。
property属性
property属性是一种用起来像是使用的实例属性一样的特殊属性,可以对应于某个方法。
class Foo:
def func(self):
pass
# 定义property属性
@property
def prop(self):
pass
foo_obj = Foo()
foo_obj.func() # 调用实例方法
foo_obj.prop # 调用property属性
property属性的定义和调用要注意一下几点:
- 定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
- 调用时,无需括号
其实:property的作用就是 将一个属性的操作方法封装为一个属性,用户用起来就和操作普通属性完全一致,非常简单。
property在类中有三种常见的方式:
class A(object):
def __init__(self):
self.sc = 'world'
@property
def a(self):
return self.sc
@a.setter
def a(self, hi):
self.sc += hi
@a.deleter
def a(self):
print('deleted')
obj = A()
# 第一种方式
print(obj.a)
# 第二种方式
obj.a = 'hello'
print(obj.a)
# 第三种方式
del obj.a
print(obj.a)
使用不同的方式,只是调用不同方法下面的语句而已,并不是真的实现赋值或者删除。
python中一些魔法属性
__doc__
表示类的描述信息
__module__
和 __class__
__module__
表示当前操作的对象在那个模块
__class__
表示当前操作的对象的类是什么
__init__
初始化方法,通过类创建对象时,自动触发执行
__del__
当对象在内存中被释放时,自动触发执行
__call__
对象后面加括号,触发执行
__dict__
类或对象中的所有属性
__str__
如果一个类中定义了__str__
方法,那么在打印 对象 时,默认输出该方法的返回值
__getitem__
、__setitem__
、__delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据
with 上下文管理器
我们知道,打开文件的有两种常见的方式,分别是直接open和with open
且with open相对于open有一个极大的优势就是当文件遇到意外崩溃时,可以自动关闭文件。这就是利用了with上下文管理器。
任何实现了 __enter__()
和__exit__()
方法的对象都可称之为上下文管理器,因此,我们也可以模拟实现一个自己的文件类,让该类实现 __enter__()
和 __exit__()
方法。
class File():
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
print("entering")
self.f = open(self.filename, self.mode)
return self.f
def __exit__(self, *args):
print("will exit")
self.f.close()
__enter__()
方法返回资源对象,这里就是你将要打开的那个文件对象,__exit__()
方法处理一些清除工作。
因为 File 类实现了上下文管理器,现在就可以使用 with 语句了。
with File('out.txt', 'w') as f:
print("writing")
f.write('hello, python')
Python 还提供了一个 contextmanager 的装饰器,更进一步简化了上下文管理器的实现方式。通过 yield 将函数分割成两部分,yield 之前的语句在__enter__
方法中执行,yield 之后的语句在__exit__
方法中执行。紧跟在 yield 后面的值是函数的返回值。
from contextlib import contextmanager
@contextmanager
def my_open(path, mode):
f = open(path, mode)
yield f
f.close()