今日内容:
如何查看对象的名称空间及对象名
继承的另一种使用
单继承与多继承
经典类与新式类
mro列表
菱形继承
接口
抽象类
鸭子类型
1、查看名称空间包含的变量:
使用类或对象名.__dict__查看
实际上在我们调用函数时就是在访问名称空间
比如Student.name实际上就是在调用 Student.__dict__["name"]
2、在使用类实例化对象时,代码的执行顺序为:
首先调用类产生一个空的对象
执行类的__init__方法,为对象添加自己独特的属性
#python为类内置的特殊属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
class Human:
def __init__(self,name):
self.name = name
def run(self):
print("%s is running..."%self.name)
p1 = Human("lee")
p1.run()
print(p1.__class__) # <class '__main__.Human'>
print(p1.__name__) # error:'Human' object has no attribute '__name__'
print(p1.__dict__) # {'name': 'lee'} --> 只展示对象自己的名称空间,
print(Human.__dict__) # {'__module__': '__main__', '__init__': <function Human.__init__ at 0x000001E64CEC52F0>, 'run': <function Human.run at 0x000001E64CEC5378>, '__dict__': <attribute '__dict__' of 'Human' objects>, '__weakref__': <attribute '__weakref__' of 'Human' objects>, '__doc__': None}
print(Human.__class__) # <class 'type'>
print(Human.__name__) # 类的名字
p1 = Human("lee")
p2 = Human("lee")
p3 = Human("lee")
print(p1.run) # <bound method Human.run of <__main__.Human object at 0x000001A209BBE710>>
print(p2.run) # <bound method Human.run of <__main__.Human object at 0x000001A209BBE828>>
print(p3.run) # <bound method Human.run of <__main__.Human object at 0x000001A209BBEF60>>
print(p1) # <__main__.Human object at 0x000001C1B0B5D7B8>
print(p2) # <__main__.Human object at 0x000001C1B0B5D710>
print(p3) # <__main__.Human object at 0x000001C1B0B5D828>
1、继承的另一种使用:
在昨天的课程中讲了继承自己写的类,除了可以继承自己写的类,还可以继承系统已有的类
练习:
class MyList(list):
def __init__(self,ele_name):
super().__init__(self)
self.ele_name = ele_name
def append(self, object):
if object.__class__ == self.ele_name:
# print(object.__class__)
super().append(object)
else:
print("%s不是%s类型"%(object,self.ele_name.__name__))
li = MyList(int)
li.append("123")
li.append(123)
li.append(11)
print(li)
print(int(10).__class__)
2、单继承与多继承
单继承:子类只继承自一个父类,在进行属性查找时,首先查找自己的名称空间,在根据继承顺序依次进行查找
多继承:在python中,子类除了可以继承一个父类外,还可以继承多个父类,在进行属性查找时不能单独的依靠肉眼判断查找顺序,此时只能依靠mro列表判断查找顺序
查看继承:
在存在继承关系时,可以使用__base__()查看子类的第一个父类
可以使用__bases__()查看子类的全部父类,会产生一个元组
例子:
class C:
pass
class B(C):
pass
class MyList(list,B):
def __init__(self,ele_name):
super().__init__(self)
self.ele_name = ele_name
def append(self, object):
if object.__class__ == self.ele_name:
# print(object.__class__)
super().append(object)
else:
print("%s不是%s类型"%(object,self.ele_name.__name__))
print(MyList.__base__) # <class 'list'>
print(MyList.__bases__) # (<class 'list'>, <class '__main__.B'>)
3、经典类与新式类
经典类与新式类的分别只有在py2中才会体现,在py3中所有的类都是新式类
经典类:在python2中,当一个类没有父类,又没有显性的显示继承自object类,那么这个类就是经典类(老式类)
新式类:在python2中,当一个类显性的显示它继承自object类,那么他就是新式类
在python3中,所有的类,如果没有显示他们有父类,那他们就会隐形的继承自object类,所以,在python3中,所有的类都是新式类
换句话说就是:所有没有直接或间接继承自object的类都是经典类,所有直接或间接继承自object的类就会新式类
4、super()方法与mro列表
在单继承中,我们会使用super()来继承父类的某个方法,但是在多继承中,存在继承顺序的问题,那么super()到底继承自谁就会有异议
下方的例子我们经过分析,判断可能会报错,因为C的父类是object类,但是运行时没有报错,这与我们的分析时不同的
这是因为在多继承中会使用到mro列表,在进行属性查找时会根据mro列表进行继承
查看mro列表使用的是:当前类名.mro()
# 举例:
class B:
def test(self):
print("B test")
class C:
def test(self):
print("C test")
super().test()
class A(C,B):
# def test(self):
# print("A test")
pass
a = A()
a.test()
print(A.mro()) # [<class '__main__.A'>, <class '__main__.C'>, <class '__main__.B'>, <class 'object'>]
5、菱形继承
类型继承就是当前类具有多个父类,而这些父类有具有相同的父类,那么这种继承关系就叫做菱形继承
菱形继承中属性的查找依赖于mro列表的顺序进行查找

# 例子:
class I:
def test(self):
print("I test")
class K:
pass
class E:
pass
class F:
pass
class G:
pass
class H:
pass
class D(H, I):
pass
class C(F, H):
pass
class B(E,F,G):
pass
class A(B,C,D):
pass
a = A()
a.test()
print(A.mro())
# I test
# [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>,
# <class '__main__.C'>, <class '__main__.F'>, <class '__main__.G'>,
# <class '__main__.D'>, <class '__main__.H'>, <class '__main__.I'>,
# <class 'object'>]
菱形继承
6、抽象与接口
接口:接口实际上就是一种规范制度,接口类中的方法不能拥有方法体,只是将方法罗列出来,作为模板供借鉴,这样可以更好的提高其扩展性
例如:我们平时使用电脑上的USB接口,如果我们要使用这个接口,就需要遵守这个接口的规范,如果不遵守,就不能使用这个接口
在python中,我们会使用接口类创建一个模板类,所有想要使用这个接口的类都要继承并重写模板类中的方法或者是按照接口方法的格式自己重新编写
# 我们也可以不使用模板类,这时候只要是按照模板类的格式自己定义方法也是可以使用接口的
# 举例:
# 接口类
class USB:
def read(self):
pass
def write(self):
pass
# 依据接口类的具体设备类
class Key(USB):
def read(self):
print("reading...")
def write(self):
print("writing...")
# 提供接口的类,只要依据接口的格式进行定义就能够被PC使用,不依据这个接口就不能被PC使用
class PC:
def connect_device(self,device):
device.read()
device.write()
7、抽象类
上例中,我们如果想要可以被PC类使用,就需要遵守接口,按照接口的方法进行类的创建
但是,有时为了避免遗忘接口有哪些方法,我们会将接口类定义为抽象类,这样,如果遵循其模板,就会报错
抽象类:具有抽象方法的类就是抽象类,只要一个方法中包含抽象方法,那么他就是抽象类
抽象类不能实例化,必须要有一个类继承抽象类,这个子类才可以进行实例化
抽象方法:没有函数体的方法,并且被@abc.abstractmethod 装饰器进行装饰的方法
下例中:
say_hi()方法没有函数体,且被@abc.abstractmethod 装饰器进行装饰,那么这个方法就是抽象方法
Test中包含有抽象方法,那么这个类就是抽象类
接口类与抽象类的区别:
接口类中的方法都不能具有函数体,抽象类中可以有方法具有函数体
接口类可以实例化对象,抽象类不能实例化对象
接口类可以不进行定义,只要按照接口类的模板进行类的定义即可
抽象类也可以不进行定义
# 举例:
import abc
class Test(metaclass=abc.ABCMeta):
@abc.abstractmethod
def say_hi(self):
pass
class TT(Test):
def say_hi(self):
print("i am TT obj")
t = TT()
t.say_hi()
# 鸭子类型
说如果一个对象叫声像鸭子,走路像鸭子,长得像鸭子,那它就是鸭子
是python 推荐的方式,python不喜欢强行限制你
class PC():
def conntent_device(self, usb_device):
usb_device.open()
usb_device.work()
usb_device.close()
class Mouse:
# 实现接口规定的所有功能
def open(self):
print("mouse opened")
def work(self):
print("mouse working...")
def close(self):
print("mouse closed")
mouse = Mouse()
pc = PC()
pc.conntent_device(mouse)
class KeyBoard:
def open(self):
print("KeyBoard opened")
def work(self):
print("KeyBoard working...")
def close(self):
print("KeyBoard closed")
key1 = KeyBoard()
# 如果key1的特征和行为都像USB设备 那就把它当做USB设备来使用
# 对于使用者而言可以不用关心这个对象是什么类,是如如何是实现,
pc.conntent_device(key1)