day30 继承、派生与多态,类中方法和内置函数
一、多继承出现的问题(mixins机制)
继承从人的思维逻辑上看,就是什么什么(子类)是什么(父类),具体一点就是人类和狗类都属于动物类。
但是如果有特殊情况导致出现多继承,就会违反人类的思维逻辑,一类事物是多种事物
从代码层面来讲
class Vehicle:
pass
class FlyableMixin:
def fly(self):
pass
class CivilAircraft(FlyableMixin,Vehicle): # 民航飞机
pass
class Helicopter(FlyableMixin,Vehicle): # 直升飞机
pass
class Car(Vehicle): # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
pass
民航飞机和直升飞机和汽车都属于交通工具,如果我们要解决民航飞机和直升飞机的代码冗余把飞行功能提取到交通工具类中。会导致汽车也能飞,这就违反了我们设置汽车类的原则。
多继承解决了我们代码冗余问题,也没有违反汽车类的原则。
但是多继承又给我们带来新的问题,程序可读性差。
这里引入一种规范,如果我们需要为多种类去总结一类功能,我们可以定义一个特殊的功能类,以Mixin为结尾,在集成时放在唯一父类的左边。
二、派生与方法重用
子类可以派生出自己新的属性,在进行属性查找的时候,子类的属性会优先父类查找。
class People:
def __init__(self,name,age):
self.name = name
self.age = age
class Teacher(People):
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
teacher里面的init和父类的init方法有重复的地方,我们可以通过以下两种方法实现父类方法的重用
方法一:‘指名道姓’的调用某一个类的函数
class People:
def __init__(self,name,age):
self.name = name
self.age = age
class Teacher(People):
def __init__(self,name,age,sex):
People.__init__(self,name,age)
self.sex = sex
方式二:super()
super()调用父类提供给自己的方法=》严格依赖继承关系
调用super()会得到一个特殊的对象,该对象会参照发起属性查找的那个类的mro,去当前类的父类中找属性
class Teacher(People):
def __init__(self,name,age,sex):
super().__init__(name,age)
self.sex = sex
这两种方式的区别是:方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super()仍然会按照MRO继续往后查找
class A:
def text(self):
super().text()
class B:
def text(self):
print("text B")
class C(A,B):
pass
obj = C()
obj.text()
# 此刻调用是根据类C的mro列表查找顺序查找,即便A没有继承B,根据C的继承,还是会查到B中的text
>>>text B
三、多态
1 什么是多态
定义:同一种事物的多重形态
多态和继承是一种相对立的抽象概念
class Animal:
pass
class People(Animal):
pass
class Dog(Animal):
pass
class Pig(Animal):
pass
人类,狗类,猪类,他们继承于父类动物类,说明他们都有动物类的属性。
换一种说话,动物类有三种形态,人类,猪类,狗类,这就是多态
2 为什么要有多态
多态性指的是可以在不考虑对象具体类型的情况下而直接使用对象,我们在知道了该类是哪一种类型的不同形态,我们就可以用那种类型的方法进行操作。
class Animal: # 统一所有子类的方法
def say(self):
print('动物基本的发声频率。。。',end=' ')
class People(Animal):
def say(self):
super().say()
print('嘤嘤嘤嘤嘤嘤嘤')
class Dog(Animal):
def say(self):
super().say()
print('汪汪汪')
class Pig(Animal):
def say(self):
super().say()
print('哼哼哼')
更进一步,我们可以通过定义统一的接口,接受传入的对象
def animal_say(animal):
animal.say()
#只要是动物类所属其他多态类的对象,都可使用这个方法,不需要任何条件
综上我们得知,多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象,可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名
import abc
# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法
def talk(self): # 抽象方法中无需实现具体的功能
pass
class Cat(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
def talk(self):
pass
cat=Cat() # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
3 python中多态的鸭子类型
鸭子类型:如果一个东西,他看起来像鸭子,叫起来也像鸭子,我们就可以把他看做鸭子
放在程序中,如果一个类,看起来像a,有a的特性,那我们就可以把它当做a来使用(可以去使用a中的方法,不管他是否继承于a)
#二者看起来都像文件,因而就可以当文件一样去用,然而它们并没有直接的关系
class Txt: #Txt类有两个与文件类型同名的方法,即read和write
def read(self):
pass
def write(self):
pass
class Disk: #Disk类也有两个与文件类型同名的方法:read和write
def read(self):
pass
def write(self):
pass
四、绑定方法与非绑定方法
定义
类中定义的函数有两大类:绑定方法与非绑定方法
绑定方法也分为两类:对象的绑定方法,类的绑定方法
1 绑定方法
1.1对象的绑定方法
类中的方法都是默认绑定给对象的
class Peopel:
#say就是类中对象的绑定方法
def say(self):#self是对象绑定方法规范第一个传参写法
pass
特性:
对象在使用时,作为绑定方法,会把对象作为第一个参数传入
类在调用时,作为函数,要严格按照函数的传参对应传值
1.2 类的绑定方法
为类中某个函数加上装饰器@classmethod后,该函数就绑定到了类
class Peopel:
@classmethod
#text就是类中对象的绑定方法
def text(cls):#cls是类绑定方法规范第一个传参写法
pass
特性:
对象在试用时和类在使用时,都会把类作为第一个参数传入,所以这种方法对象调用没有意义,只是用来给类使用的。
2 非绑定方法
为类中某个函数加上装饰器@staticmethod后,该函数就变成了非绑定方法,也称为静态方法。该方法不与类或对象绑定,类与对象都可以来调用它,但它就是一个普通函数而已,因而没有自动传值那么一说
import uuid
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
@staticmethod
def create_id():
return uuid.uuid1()
总结:
如果我们这个方法是给类用的,加上装饰器@classmethod,让这个方法自动绑定类
如果我们这个方法是给对象用的,不用管,python中类的方法默认都是绑定对象
如果我们这个方法谁都能用,加上装饰器@staticmethod,设置成静态方法谁调他都只是个普通的函数。
五、内置函数
1 zip
把传入的两个可迭代对象一一对应成元组放在字典里,多出的不管
v1='hello'
v2=[111,222,333,444,5555,6666]
res=zip(v1,v2)
print(list(res))
>>>[('h', 111), ('e', 222), ('l', 333), ('l', 444), ('o', 5555)]
2 divmod
传入的第一个值为被除数,第二个值为除数,返回一个元组,第一个值是商,第二个值是余数
print(divmod(10000,33))
>>>(303, 1)
3 dir
查看对象或者类有哪些方法
class Foo:
pass
obj=Foo()
obj.xxx=1111
print(dir(obj)) # obj.哪些属性
>>>['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'xxx']
4 enumerate
枚举,迭代循环一个可迭代对象,返回一个enumerate对象,内含可迭代对象的所在位置和值的一个一个元组
for v in enumerate(['a','b','c']):
print(v)
print(type(enumerate(['a','b','c'])))
>>>(0, 'a')
>>>(1, 'b')
>>>(2, 'c')
>>><class 'enumerate'>
5 eval
执行字符串中的表达式
res=eval('{"a":1}') # 执行字符串中的表达式
print(res,type(res))
>>>{'a': 1} <class 'dict'>
6 isinstance
判断类型和type区别使用,type是用来返回类型的
class Foo:
pass
obj=Foo()
print(isinstance(obj,Foo))
print(isinstance([],list)) # 类型判断推荐使用isinstance
print(type([]) is list) # 不推荐使用
>>>True
>>>True
>>>True